JAVA寫HTTP代理服務器(一)-socket實現

HTTP代理服務器是一種特殊的網絡服務,容許一個網絡終端(通常爲客戶端)經過這個服務與另外一個網絡終端(通常爲服務器)進行非直接的鏈接。一些網關、路由器等網絡設備具有網絡代理功能。通常認爲代理服務有利於保障網絡終端的隱私或安全,防止攻擊。html

HTTP 代理有分兩種:

  1. RFC 7230 - HTTP/1.1: Message Syntax and Routing(即修訂後的 RFC 2616,HTTP/1.1 協議的第一部分)描述的普通代理。這種代理扮演的是「中間人」角色,對於鏈接到它的客戶端來講,它是服務端;對於要鏈接的服務端來講,它是客戶端。它就負責在兩端之間來回傳送 HTTP 報文。
  2. Tunneling TCP based protocols through Web proxy servers(經過 Web 代理服務器用隧道方式傳輸基於 TCP 的協議)描述的隧道代理。它經過 HTTP 協議正文部分(Body)完成通信,以 HTTP 的方式實現任意基於 TCP 的應用層協議代理。這種代理使用 HTTP 的 CONNECT 方法創建鏈接,但 CONNECT 最開始並非 RFC 2616 - HTTP/1.1 的一部分,直到 2014 年發佈的 HTTP/1.1 修訂版中,才增長了對 CONNECT 及隧道代理的描述,詳見 RFC 7231 - HTTP/1.1: Semantics and Content。實際上這種代理早就被普遍實現。

HTTP代理

http請求通過代理服務器,代理服務器只要負責轉發相應的http響應體就能夠了。git

HTTPS代理

https請求通過代理服務器,會發送一個CONNECT報文,用於和代理服務器創建隧道,若是代理服務器返回HTTP 200,則創建成功,後續代理服務器只要負責轉發數據就行,實際上SSL/TLS握手仍是發生在客戶端和真實服務器。
QQ截圖20170904111304.jpggithub

思路

建立SocketServer監聽端口,根據http請求頭方法若是是CONNECT就是HTTPS請求不然都爲HTTP請求,接着根據HOST頭創建代理服務器與目標服務器的鏈接,而後轉發數據。HTTPS請求須要特殊處理,由於CONNECT請求並不須要轉發,要返回一個HTTP 200的響應創建隧道,以後才進行轉發。web

實現

//監聽端口
ServerSocket serverSocket = new ServerSocket(port);
  for (; ; ) { 
    new SocketHandle(serverSocket.accept()).start(); 
  }
static class SocketHandle extends Thread {

        private Socket socket;

        public SocketHandle(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            OutputStream clientOutput = null;
            InputStream clientInput = null;
            Socket proxySocket = null;
            InputStream proxyInput = null;
            OutputStream proxyOutput = null;
            try {
                clientInput = socket.getInputStream();
                clientOutput = socket.getOutputStream();
                String line;
                String host = "";
                LineBuffer lineBuffer = new LineBuffer(1024);
                StringBuilder headStr = new StringBuilder();
                //讀取HTTP請求頭,並拿到HOST請求頭和method
                while (null != (line = lineBuffer.readLine(clientInput))) {
                    System.out.println(line);
                    headStr.append(line + "\r\n");
                    if (line.length() == 0) {
                        break;
                    } else {
                        String[] temp = line.split(" ");
                        if (temp[0].contains("Host")) {
                            host = temp[1];
                        }
                    }
                }
                String type = headStr.substring(0, headStr.indexOf(" "));
                //根據host頭解析出目標服務器的host和port
                String[] hostTemp = host.split(":");
                host = hostTemp[0];
                int port = 80;
                if (hostTemp.length > 1) {
                    port = Integer.valueOf(hostTemp[1]);
                }
                //鏈接到目標服務器
                proxySocket = new Socket(host, port);
                proxyInput = proxySocket.getInputStream();
                proxyOutput = proxySocket.getOutputStream();
                //根據HTTP method來判斷是https仍是http請求
                if ("CONNECT".equalsIgnoreCase(type)) {//https先創建隧道
                    clientOutput.write("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
                    clientOutput.flush();
                } else {//http直接將請求頭轉發
                    proxyOutput.write(headStr.toString().getBytes());
                }
                //新開線程轉發客戶端請求至目標服務器
                new ProxyHandleThread(clientInput, proxyOutput).start();
                //轉發目標服務器響應至客戶端
                while (true) {
                    clientOutput.write(proxyInput.read());
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (proxyInput != null) {
                    try {
                        proxyOutput.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (proxyOutput != null) {
                    try {
                        proxyOutput.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (proxySocket != null) {
                    try {
                        proxySocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (clientInput != null) {
                    try {
                        clientInput.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (clientOutput != null) {
                    try {
                        clientOutput.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }
static class ProxyHandleThread extends Thread {

        private InputStream input;
        private OutputStream output;

        public ProxyHandleThread(InputStream input, OutputStream output, CountDownLatch cdl) {
            this.input = input;
            this.output = output;
        }

        @Override
        public void run() {
            try {
                while (true) {
                    output.write(input.read());
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

後記

以上一個簡單的HTTP代理服務器就實現了,不過其中問題也有不少,如BIO模型的缺陷,異常處理機制。
下一篇會用netty來實現一個高性能的HTTP代理服務器。
代碼託管在github上,歡迎start安全

相關文章
相關標籤/搜索