接上一篇,由於是用BIO的一個鏈接一個線程去處理,對於HTTP這種短鏈接協議來講CPU開銷是很是大的,就算加入了線程池也不能完美解決BIO的缺陷,因此能夠用NIO進行服務器的優化,NIO基於IO多路複用以實現單線程處理大量鏈接,可是編寫起來比較複雜,因此就選擇了netty,這裏就不在多敘netty是什麼了。git
netty有內置的http編解碼器,那就能夠輕易作到不僅是轉發原始數據,而是能夠修改響應內容,固然僅限http代理,由於https代理的話私鑰都存在客戶端和目標服務器上,代理服務器只能捕獲到雙方的公鑰,沒法解密成明文,除非代理服務器製做證書,並實現SSL/TLS握手。github
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上,歡迎startbootstrap