1. 程式人生 > >java通過sockt實現代理伺服器

java通過sockt實現代理伺服器

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響應體就可以了。

HTTPS代理

https請求經過代理伺服器,會發送一個CONNECT報文,用於和代理伺服器建立隧道,如果代理伺服器返回HTTP 200,則建立成功,後續代理伺服器只要負責轉發資料就行,實際上SSL/TLS握手還是發生在客戶端和真實伺服器。 
QQ截圖20170904111304.jpg

思路

建立SocketServer監聽埠,根據http請求頭方法如果是CONNECT就是HTTPS請求否則都為HTTP請求,接著根據HOST頭建立代理伺服器與目標伺服器的連線,然後轉發資料。HTTPS請求需要特殊處理,因為CONNECT請求並不需要轉發,要返回一個HTTP 200的響應建立隧道,之後才進行轉發。

實現

//監聽埠
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