您的位置 首页 java

手撕HTTP_HTTPS代理(基于Netty实现)

HTTP/HTTPS Proxy Server

直接上代码:HttpProxyServer

 
import io. netty .bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * HTTP/ HTTPS 代理服务器
 * 三个角色:真实客户端,代理客户端,目标主机
 *
 * 数据流向:
 *
 *            ------->> 代理客户端  --------->>
 * 真实客户端                                  目标主机
 *           <<-------- 代理客户端  <<--------
 *
 */public class HttpProxyServer {

    int port;

    public HttpProxyServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws Exception {
        new HttpProxyServer(8443).run();
    }

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @ Override 
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(
                                    new LoggingHandler(LogLevel.DEBUG),
                                    new HttpProxyClientHandler());
                        }
                    })
                    .bind(port).sync().channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }


    /**
     * 代理客户端去请求目标主机
     */    private static class HttpProxyClientHandler extends ChannelInboundHandlerAdapter {

        /*代理服务端channel*/        private Channel clientChannel;
        /*目标主机channel*/        private Channel remoteChannel;
        /*解析真实客户端的 header */        private HttpProxyClientHeader header = new HttpProxyClientHeader();


        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            clientChannel = ctx.channel();
        }

        /**
         * 注意在真实客户端请求一个页面的时候,此方法不止调用一次,
         * 这是TCP底层决定的(拆包/粘包)
         */        @Override
        public void channelRead(ChannelHandlerContext ctx, Object  msg ) {
            if (header.is complete ()) {
                /*如果在真实客户端的本次请求中已经解析过header了,
                说明代理客户端已经在目标主机建立了连接,直接将真实客户端的数据写给目标主机*/                remoteChannel.writeAndFlush(msg); // just forward
                return;
            }

            ByteBuf in = (ByteBuf) msg;
            header.digest(in);/*解析目标主机信息*/

            if (!header.isComplete()) {
                /*如果解析过一次header之后未完成解析,直接返回,释放buf*/                in.release();
                return;
            }

             // disable AutoRead until remote connection is ready
            clientChannel.config().setAutoRead(false);

            if (header.isHttps()) { // if https, respond 200 to create tunnel
                clientChannel.writeAndFlush(Unpooled.wrappedBuffer("HTTP/1.1 200 Connection Established\r\n\r\n".get bytes ()));
            }
            /**
             *
             * 下面为真实客户端第一次来到的时候,代理客户端向目标客户端发起连接
             */            Bootstrap b = new Bootstrap();
            b.group(clientChannel.eventLoop()) // use the same EventLoop
                    .channel(clientChannel.getClass())
                    .handler(new HttpProxyRemoteHandler(clientChannel));
            ChannelFuture f = b.connect(header.getHost(), header.getPort());
            remoteChannel = f.channel();
            f.addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    // connection is ready, enable AutoRead
                    clientChannel.config().setAutoRead(true); 
                    // forward header and remaining bytes
                    if (!header.isHttps()) { 
                        //in读取一次缓冲区就没有了,header.byteBuf里面存了一份
                        remoteChannel.writeAndFlush(header.getByteBuf());
                    }
                } else {
                    in.release();
                    clientChannel.close();
                }
            });
        }


        @Override
        public void channelInactive(ChannelHandlerContext ctx) {
            flushAndClose(remoteChannel);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
            e.printStackTrace();
            flushAndClose(clientChannel);
        }

        private void flushAndClose(Channel ch) {
            if (ch != null && ch.isActive()) {
                ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
            }
        }
    }


    /**
     * 代理客户端请求目标主机处理器
     */    private static class HttpProxyRemoteHandler extends ChannelInboundHandlerAdapter {

        private Channel clientChannel;
        private Channel remoteChannel;

         public HttpProxyRemoteHandler(Channel clientChannel) {
            this.clientChannel = clientChannel;
        }

        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            this.remoteChannel = ctx.channel();
        }

        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            clientChannel.writeAndFlush(msg); // just forward
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) {
            flushAndClose(clientChannel);
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
            e.printStackTrace();
            flushAndClose(remoteChannel);
        }

        private void flushAndClose(Channel ch) {
            if (ch != null && ch.isActive()) {
                ch.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
            }
        }
    }


    /**
     * 真实主机的请求头信息
     */    private static class HttpProxyClientHeader {
        private String  method ;//请求类型
        private String host;//目标主机
        private int port;//目标主机端口
        private boolean https;//是否是https
        private boolean complete;//是否完成解析
        private ByteBuf byteBuf = Unpooled.buffer();


        private final StringBuilder  line Buf = new StringBuilder();


        public String getMethod() {
            return method;
        }

        public String getHost() {
            return host;
        }

        public int getPort() {
            return port;
        }

        public boolean isHttps() {
            return https;
        }

        public boolean isComplete() {
            return complete;
        }

        public ByteBuf getByteBuf() {
            return byteBuf;
        }

        /**
         * 解析header信息,建立连接
         HTTP 请求头如下
         GET  HTTP/1.1
         Host: www.baidu.com
         User-Agent: curl/7.69.1
          Accept : *//*
         Proxy-Connection:Keep-Alive

         HTTPS请求头如下
         CONNECT www.baidu.com:443 HTTP/1.1
         Host: www.baidu.com:443
         User-Agent: curl/7.69.1
         Proxy-Connection: Keep-Alive

         * @param in
         */        public void digest(ByteBuf in) {
            while (in.isReadable()) {
                if (complete) {
                    throw new IllegalStateException("already complete");
                }
                String line = readLine(in);
                System.out.println(line);
                if (line == null) {
                    return;
                }
                if (method == null) {
                    method = line.split(" ")[0]; // the first word is http method name
                    https = method.equalsIgnoreCase("CONNECT"); // method CONNECT means https
                }
                if (line.startsWith("Host: ")) {
                    String[] arr = line.split(":");
                    host = arr[1].trim();
                    if (arr.length == 3) {
                        port = Integer.parseInt(arr[2]);
                    } else if (https) {
                        port = 443; // https
                    } else {
                        port = 80; // http
                    }
                }
                if (line.isEmpty()) {
                    if (host == null || port == 0) {
                        throw new IllegalStateException("cannot find header \'Host\'");
                    }
                    byteBuf = byteBuf.asReadOnly();
                    complete = true;
                    break;
                }
            }
            System.out.println(this);
            System.out.println("--------------------------------------------------------------------------------");
        }

        private String readLine(ByteBuf in) {
            while (in.isReadable()) {
                byte b = in.readByte();
                byteBuf.writeByte(b);
                lineBuf.append((char) b);
                int len = lineBuf.length();
                if (len >= 2 && lineBuf.substring(len - 2).equals("\r\n")) {
                    String line = lineBuf.substring(0, len - 2);
                    lineBuf.delete(0, len);
                    return line;
                }
            }
            return null;
        }

        @Override
        public String toString() {
            return "HttpProxyClientHeader{" +
                    "method='" + method + '\'' +
                    ", host='" + host + '\'' +
                    ", port=" + port +
                    ", https=" + https +
                    ", complete=" + complete +
                    '}';
        }
    }



    public static String convertByteBufToString(ByteBuf buf) {
        String str;
        if (buf.hasArray()) { // 处理堆缓冲区
            str = new String(buf.array(), buf.arrayOffset() + buf.readerIndex(), buf.readableBytes());
        } else { // 处理直接缓冲区以及复合缓冲区
            byte[] bytes = new byte[buf.readableBytes()];
            buf.getBytes(buf.readerIndex(), bytes);
            str = new String(bytes, 0, buf.readableBytes());
        }
        return str;
    }
}
  

使用步骤

  1. 运行代理 java HttpProxy Server 即可在本机8443端口开启HTTP/HTTPS代理
  2. 测试HTTP请求 curl -v -x 127.0.0.1 8443
  3. 测试HTTPS请求 curl -v -x 127.0.0.1 8443

HTTP和HTTPS的代理请求过程差异是比较大的

HTTP代理请求的过程

  1. 给代理服务器发请求头
 > GET  HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.69.1
> Accept: */*
> Proxy-Connection: Keep-Alive
>  
  1. 代理客户端发送HTTP请求给目标服务器(带上上面的header)
  2. 目标服务器返回给代理服务器,代理服务器直接转发给客户端,响应请求
 < HTTP/1.1 200 OK
< Accept-Ranges: bytes
<  Cache-Control : private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: keep-alive
< Content-Length: 2381
< Content-Type: text/html
< Date: Sun, 19 Jul 2020 10:34:15 GMT
< Etag: "588604ec-94d"
< Last-Modified: Mon, 23 Jan 2017 13:28:12 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<  

HTTPS代理请求的过程

  1. 给代理服务器发送以下请求头
 > CONNECT www.baidu.com:443 HTTP/1.1
> Host: www.baidu.com:443
> User-Agent: curl/7.69.1
> Proxy-Connection: Keep-Alive
>  
  1. 代理服务器响应,表示可以建立HTTPS通信隧道,连接建立阶段完成
 < HTTP/1.1 200 Connection Established
<  
  1. TLS进行认证…
  2. 认证通过,客户端才开始发送真正的网页请求头,下面就和HTTP代理请求一样了
 > GET / HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.69.1
> Accept: */*
>  
  1. 代理客户端发送HTTP请求给目标服务器(带上上面的header)
  2. 目标服务器返回给代理服务器,代理服务器直接转发给客户端,响应请求
 < HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: keep-alive
< Content-Length: 2443
< Content-Type: text/html
< Date: Sun, 19 Jul 2020 10:42:49 GMT
< Etag: "588603e7-98b"
< Last-Modified: Mon, 23 Jan 2017 13:23:51 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<  

文章来源:智云一二三科技

文章标题:手撕HTTP_HTTPS代理(基于Netty实现)

文章地址:https://www.zhihuclub.com/193527.shtml

关于作者: 智云科技

热门文章

网站地图