nio实现http及https代理

一,回望BIO

           上篇博文用了java 阻塞模型socket实现了http及https代理,也简单的说了下其主要缺点是比较耗费资源或者更好的说法是资源利用率不高,为什么呢?一旦客户端和代理服务器每建立一个连接(基本上每请求一个url就会建立一个新的连接)而我们实现的代理服务器为了去监听客户端发给服务器端的消息并转发就建立一个线程去专门的监听相应的流并处理转发这些数据到目标服务器;同样,对于每一个目标服务器到我们实现的代理服务器的连接,上篇博文也是都新建一个线程去监听维护流发来到代理服务器的消息并转发到客户端。这样的设计很简单很直观——客户端和目标服务器每建立一个连接直接就新建一个线程去监听就行了,你不用去管http和https的中间沟通的细节,只需要知道https建立的握手环节就行。但是针对http和https都是一问一答式的交互,显然,我们可以把监听客户端连接的线程和监听服务器连接的线程合并到一个线程中,这样就又减少了线程的数量,减少了线程上下文切换的消耗。不过这样还是不得不用一个线程去维持客户端和目标服务器到我们的代理服务器的连接,即使他们中间不传数据或者说数据已经传完了(我们可以对socket设置so_timeout来让socket 过期,并在线程中抛出我们将捕获的异常来结束线程来回收一些线程资源,但是不可以设置过短,不然有些网站可能网速波动后,就会导致代理服务器断开连接了)。

二,NIO特点分析

          那么nio到底好在哪呢?简单来说,nio可以用一个线程来处理代理服务器与客户端以及目的服务器的所有连接。它为什么能做到,主要是因为nio中的selector,channel以及buffer。你要做的就是想办法把连接都附着在一个channel上然后注册到一个selector,让这个selector去管理这些channel,buffer则是内存缓冲的东西负责接收channel发来的信息或者承载你将要发送到channel的信息,我自己的实践中主要用了ByteBuffer,其他几个使用方式基本相同,之所以使用nio包下的这几个buffer是因为封装的操作很适合nio开发,因为nio不想bio每次读写内容都读写至结束,nio则需要记录这些读写过程点,而nio包下的几种buffer就提供这些很方便的api。这么说可能有点抽象,读者可以自己去动手实践便可体会。

三,NIO实现http(s)代理服务器分析

          把个人在实现http代理服务器过程中认为比较重要踩坑也较深的地方着重提一下,再给出实现代码,读者可以在看完我的这段话后自己试着实现,然后再看我的代码。

          1,理解http和https请求的请求响应过程,都是一问一答模式。2,https有一个握手过程是明文传递消息的,通过这个可以建立最初连接,之后都是在这个建立的通道上代理传递客户端传到服务器的消息的。3,要有清晰地认识,nio在传递消息的过程中一定要对buffer中存入的消息有明确的记录(这个记录意思是,读知道读到的第一个字节(字符)在哪,能读到的最后一个字节(字符)在哪;写:第一个应该写在的位置应该在哪,最后一个能写的位置在哪。)4,有一个意识,我们这里针对buffer的使用都是单线程的,所以不用担心会不会一边channel在读,另一边通道在写的情况的。建议的是时刻将channel的readable事件注册给selector,而其writable要在满足某种情况下你手动设定(比如你有内容读到buffer中时),不要在一开始在生成一个channel时就把channel的writable事件注册给selector,因为正常情况下,channel都是writable的(原谅我这么中英结合,因为我觉得翻译成中真的有点别),这就会导致你的cpu会被大量占用。5,打日志。日志要特别重要,特别体现你的思路,不然会很乱,不好分析(debug就别想了)6,异常捕获,因为我们这里使用单线程实现代理服务器的所以,一旦某个地方出错可能就会导致程序停掉,你肯定不希望某一个url访问错误就导致你的代理服务器停掉吧。

好了,有了上面的的一些经验,下面给出自己的实现。

四,NIO的https实现

package server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.swing.text.Position;

public class ServerMain {

	private st
下面是一个简单的 Java Netty 实现HTTP/HTTPS 代理服务代码示例: ```java import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.HttpClientCodec; import io.netty.handler.codec.http.HttpContent; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslHandler; import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import io.netty.util.CharsetUtil; import javax.net.ssl.SSLEngine; import java.net.InetSocketAddress; import java.util.HashMap; import java.util.Map; public class HttpsProxyServer { private final int port; public HttpsProxyServer(int port) { this.port = port; } public void start() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(port)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new HttpsProxyServerHandler()); } }) .childOption(ChannelOption.AUTO_READ, false) .bind().sync().channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void main(String[] args) throws Exception { if (args.length != 1) { System.err.println("Usage: " + HttpsProxyServer.class.getSimpleName() + " <port>"); return; } int port = Integer.parseInt(args[0]); new HttpsProxyServer(port).start(); } private static final class HttpsProxyServerHandler extends SimpleChannelInboundHandler<HttpObject> { private Channel clientChannel; private Channel serverChannel; private SSLEngine sslEngine; private boolean isHttps; private final Map<String, String> headers = new HashMap<>(); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { clientChannel = ctx.channel(); } @Override public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { if (msg instanceof DefaultHttpRequest) { DefaultHttpRequest request = (DefaultHttpRequest) msg; String requestUri = request.getUri(); isHttps = requestUri.startsWith("https://"); String[] hostParts = HttpHeaders.getHost(request).split(":"); String host = hostParts[0]; int port = isHttps ? 443 : (hostParts.length > 1 ? Integer.parseInt(hostParts[1]) : 80); Bootstrap b = new Bootstrap(); b.group(clientChannel.eventLoop()) .channel(NioSocketChannel.class) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { if (isHttps) { sslEngine = SslContextBuilder.forClient() .trustManager(InsecureTrustManagerFactory.INSTANCE) .build().newEngine(ch.alloc(), host, port); ch.pipeline().addLast(new SslHandler(sslEngine)); } ch.pipeline().addLast(new HttpClientCodec()); ch.pipeline().addLast(new HttpsProxyServerHandler()); } }) .connect(host, port) .addListener((ChannelFuture future) -> { if (future.isSuccess()) { serverChannel = future.channel(); } else { clientChannel.close(); } }); headers.clear(); for (Map.Entry<String, String> entry : request.headers()) { headers.put(entry.getKey(), entry.getValue()); } headers.remove(HttpHeaders.Names.HOST); b.channel(NioSocketChannel.class) .connect(host, port) .addListener((ChannelFuture future) -> { if (future.isSuccess()) { serverChannel = future.channel(); serverChannel.writeAndFlush(request.retain()); clientChannel.read(); } else { clientChannel.close(); } }); } else if (msg instanceof HttpResponse) { HttpResponse response = (HttpResponse) msg; response.headers().remove(HttpHeaders.Names.TRANSFER_ENCODING); response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.CLOSE); clientChannel.writeAndFlush(response.retain()); } else if (msg instanceof HttpContent) { HttpContent content = (HttpContent) msg; clientChannel.writeAndFlush(content.retain()); if (content instanceof LastHttpContent) { if (isHttps) { sslEngine.closeOutbound(); } clientChannel.flush(); clientChannel.close(); serverChannel.close(); } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (clientChannel.isActive()) { clientChannel.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_GATEWAY, Unpooled.copiedBuffer("Failure: " + cause.getMessage(), CharsetUtil.UTF_8))) .addListener(ChannelFutureListener.CLOSE); } } } } ``` 这是一个简单的 HTTP/HTTPS 代理服务,可以接收来自客户端的请求,并将其转发到目标服务器。如果请求是 HTTPS 请求,它还会与目标服务器建立安全连接。 在这个示例中,我们使用了 Netty 的 HTTP/HTTPS 编解码器和 SSL 处理程序来处理请求和响应,并将它们转发到目标服务器。在建立与目标服务器的连接时,我们还使用了 Netty 的 Bootstrap 类来创建客户端通道。 当客户端发送请求时,我们从请求中提取目标主机和端口,并使用 Bootstrap 类创建一个新的客户端通道,然后将请求发送到目标服务器。在接收到来自目标服务器的响应时,我们将响应转发给客户端。 如果请求是 HTTPS 请求,我们还需要使用 SSL 处理程序来建立与目标服务器的安全连接。我们使用 Netty 的 SslContextBuilder 类创建一个 SSL 引擎,并将其添加到客户端通道的管道中。在建立与目标服务器的连接时,我们还需要使用 SslHandler 将 SSL 引擎添加到客户端通道的管道中,以便进行 SSL 握手。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值