JAVA写HTTP代理服务器(二)-netty实现
接上一篇,因为是用BIO的一个连接一个线程去处理,对于HTTP这种短连接协议来说CPU开销是非常大的,就算加入了线程池也不能完美解决BIO的缺陷,所以可以用NIO进行服务器的优化,NIO基于IO多路复用以实现单线程处理大量连接,但是编写起来比较复杂,所以就选择了netty,这里就不在多叙netty是什么了。
思路
netty有内置的http编解码器,那就可以轻易做到不只是转发原始数据,而是可以修改响应内容,当然仅限http代理,因为https代理的话私钥都存在客户端和目标服务器上,代理服务器只能捕获到双方的公钥,无法解密成明文,除非代理服务器制作证书,并实现SSL/TLS握手。
实现
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(2); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 100) .option(ChannelOption.TCP_NODELAY, true) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<Channel>() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast("httpCodec",new HttpServerCodec()); ch.pipeline().addLast("httpObject",new HttpObjectAggregator(65536)); ch.pipeline().addLast("serverHandle",new HttpProxyServerHandle()); } }); ChannelFuture f = b .bind(port) .sync(); f.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); }
public class HttpProxyServerHandle extends ChannelInboundHandlerAdapter { private ChannelFuture cf; private String host; private int port; @Override public void channelRead(final ChannelHandlerContext ctx, final Object msg) throws Exception { if (msg instanceof FullHttpRequest) { FullHttpRequest request = (FullHttpRequest) msg; String host = request.headers().get("host"); String[] temp = host.split(":"); int port = 80; if (temp.length > 1) { port = Integer.parseInt(temp[1]); } else { if (request.uri().indexOf("https") == 0) { port = 443; } } this.host = temp[0]; this.port = port; if ("CONNECT".equalsIgnoreCase(request.method().name())) {//HTTPS建立代理握手 HttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, NettyHttpProxyServer.SUCCESS); ctx.writeAndFlush(response); ctx.pipeline().remove("httpCodec"); ctx.pipeline().remove("httpObject"); return; } //连接至目标服务器 Bootstrap bootstrap = new Bootstrap(); bootstrap.group(ctx.channel().eventLoop()) // 注册线程池 .channel(ctx.channel().getClass()) // 使用NioSocketChannel来作为连接用的channel类 .handler(new HttpProxyInitializer(ctx.channel())); ChannelFuture cf = bootstrap.connect(temp[0], port); cf.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { future.channel().writeAndFlush(msg); } else { ctx.channel().close(); } } }); // ChannelFuture cf = bootstrap.connect(temp[0], port).sync(); // cf.channel().writeAndFlush(request); } else { // https 只转发数据,不做处理 if (cf == null) { //连接至目标服务器 Bootstrap bootstrap = new Bootstrap(); bootstrap.group(ctx.channel().eventLoop()) // 复用客户端连接线程池 .channel(ctx.channel().getClass()) // 使用NioSocketChannel来作为连接用的channel类 .handler(new ChannelInitializer() { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { @Override public void channelRead(ChannelHandlerContext ctx0, Object msg) throws Exception { ctx.channel().writeAndFlush(msg); } }); } }); cf = bootstrap.connect(host, port); cf.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { future.channel().writeAndFlush(msg); } else { ctx.channel().close(); } } }); } else { cf.channel().writeAndFlush(msg); } } } }
public class HttpProxyInitializer extends ChannelInitializer{ private Channel clientChannel; public HttpProxyInitializer(Channel clientChannel) { this.clientChannel = clientChannel; } @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(new HttpClientCodec()); ch.pipeline().addLast(new HttpObjectAggregator(6553600)); ch.pipeline().addLast(new HttpProxyClientHandle(clientChannel)); } }
public class HttpProxyClientHandle extends ChannelInboundHandlerAdapter { private Channel clientChannel; public HttpProxyClientHandle(Channel clientChannel) { this.clientChannel = clientChannel; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { FullHttpResponse response = (FullHttpResponse) msg; //修改http响应体返回至客户端 response.headers().add("test","from proxy"); clientChannel.writeAndFlush(msg); } }
后记
netty框架下开发简单的高性能http代理服务器,并且用内置的http编解码器实现了响应体的修改。后续会再深入HTTPS明文捕获,由代理服务器制作CA证书,并实现与目标服务器SSL/TLS握手。
代码托管在github上,欢迎start
相关推荐
fengshantao 2020-10-29
arctan0 2020-10-14
爱传文档 2020-07-28
gzx0 2020-07-05
fengshantao 2020-07-04
fengshantao 2020-07-02
jannal 2020-06-21
arctan0 2020-06-19
arctan0 2020-06-16
gzx0 2020-06-14
fengshantao 2020-06-13
gzx0 2020-06-12
arctan0 2020-06-11
fengshantao 2020-06-11
mbcsdn 2020-05-19
arctan0 2020-05-16
爱传文档 2020-05-08
爱传文档 2020-05-04