JAVA写HTTP代理服务器(一)-socket实现

HTTP代理服务器是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击。

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();
            }
        }
    }
public class LineBuffer {
    private int size;

    public LineBuffer(int size) {
        this.size = size;
    }

    public String readLine(InputStream input) throws IOException {
        int flag = 0;
        int index = 0;
        byte[] bts = new byte[this.size];
        int b;
        while(flag!=2&&(b= input.read())!=-1){
            bts[index++] = (byte) b;
            if(b=='\r'&&flag%2==0){
                flag++;
            }else if(b=='\n'&&flag%2==1){
                flag++;
                if(flag==2){
                    return new String(bts,0,index-2);
                }
            }else{
                flag = 0;
            }
            if(index==bts.length){
                //满了扩容
                byte[] newBts = new byte[bts.length*2];
                System.arraycopy(bts,0,newBts,0,bts.length);
                bts = null;
                bts = newBts;
            }
        }
        return null;
    }
}

后记

以上一个简单的HTTP代理服务器就实现了,不过其中问题也有很多,如BIO模型的缺陷,异常处理机制。
下一篇会用netty来实现一个高性能的HTTP代理服务器。
代码托管在github上,欢迎start

阅读 10.2k

推荐阅读