构建HTTP(HTTPS)应用程序
为了支持 SSL/TLS,Java提供javax.net.ssl 包,它的 SSLContext 和 SSLEngine 类使得解密和加密相当简单和高效。SSLContext是SSL链接的上下文,SSLEngine主要用于出站和入站字节流的操作。
Netty还提供了使用 OpenSSL工具包的SSLEngine实现,该SSLEngine比JDK提供的SSLEngine实现有更好的性能。
Netty通过一个名为SslHandler
的ChannelHandler
实现加密和解密的功能,其中SslHandler
在内部使用SSLEngine来完成实际的工作,SSLEngine的实现可以是JDK的SSLEngine
,也可以是 Netty 的OpenSslEngine
,当然推荐使用Netty的OpenSslEngine,因为性能更好。
大多数情况下,SslHandler 将是 ChannelPipeline 中的第一个 ChannelHandler。只有在所有其他的 ChannelHandler 将它们的逻辑应用到数据之后,才会进行加密。
Netty为HTTP消息提供编码器和解码器:
HttpRequestEncoder
: 编码器:用于客户端,向服务器发送请求HttpResponseEecoder
: 编码器:用于服务端,向客户端发送响应HttpRequestDecoder
:解码器:用于服务端,接收来自客户端的请求HttpResponseDecoder
: 解码器:用于客户端,接收来自服务端的请求
编解码器
HttpClientCodec
: 用于客户端的编解码器,等效于HttpRequestEncoder
和HttpResponseDecoder
的组合HttpServerCodec
:用于服务端的编解码器,等效于HttpRequsetDecoder
和HttpResponseEncoder
的组合
HttpServerCodec 同时实现了 ChannelInboundHandler
和 ChannelOutboundHandler
接口,以达到同时具有编码和解码的能力。
聚合器
HttpObjectAggregator
: 聚合器,可以将多个消息部分合并为 FullHttpRequest
或者 FullHttpResponse
消息。使用该聚合器的原因是HTTP解码器会在每个HTTP消息中生成多个消息对象,如HttpRequest/HttpResponse,HttpContent,LastHttpContent
,使用聚合器将它们聚合成一个完整的消息内容,这样就不用关心消息碎片了。
服务端:
public class HttpsServer { public static void main(String[] args) { //使用Netty自带的证书工具生成一个数字证书 NioEventLoopGroup boss = new NioEventLoopGroup(1); NioEventLoopGroup worker = new NioEventLoopGroup(); try { SelfSignedCertificate certificate = new SelfSignedCertificate(); SslContext sslContext = SslContextBuilder.forServer(certificate.certificate(), certificate.privateKey()).build(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(boss, worker) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if (sslContext != null) { pipeline.addLast(sslContext.newHandler(ch.alloc())); } //添加一个HTTP的编解码器 pipeline.addLast(new HttpServerCodec()); //添加HTTP消息聚合器 pipeline.addLast(new HttpObjectAggregator(64 * 1024)); //添加一个自定义服务端Handler pipeline.addLast(new HttpsServerHandler()); } }); ChannelFuture future = bootstrap.bind(8089).sync(); future.channel().closeFuture().sync(); } catch (CertificateException e) { e.printStackTrace(); } catch (SSLException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } finally { boss.shutdownGracefully(); worker.shutdownGracefully(); } } }
自定义handler
public class HttpsServerHandler extends SimpleChannelInboundHandler<HttpObject> { private static final AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type"); private static final AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length"); private static final AsciiString CONNECTION = AsciiString.cached("Connection"); private static final AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive"); @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { if (msg instanceof HttpRequest) { String content = ""; HttpRequest request = (HttpRequest) msg; if ("/hello".equals(request.getUri())) { content = "hello world"; response2Client(ctx, request, content); } else { content = "Connect the Server"; response2Client(ctx, request, content); } } } private void response2Client(ChannelHandlerContext ctx, HttpRequest request, String content) { boolean keepAlive = HttpUtil.isKeepAlive(request); DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(content.getBytes())); response.headers().set(CONTENT_TYPE, "text/plain"); response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes()); if (!keepAlive) { ctx.write(response).addListener(ChannelFutureListener.CLOSE); } else { response.headers().set(CONNECTION, KEEP_ALIVE); ctx.write(response); } } @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.flush(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
AsciiString
AsciiString提供实现CharSequence
接口的 AsciiString
,至于 CharSequence
就是 String
的父类。AsciiString
包含的字符只占1个字节,当处理 US-ASCII 或者 ISO-8859-1 字符串时可以节省空间。例如,HTTP编解码器使用 AsciiString
处理 header name ,因为将AsciiString
编码到 ByteBuf
中不会有类型转换的代价,其内部实现就是用的 byte
,对于String
来说,内部是存 char[]
,使用 String就需要将 char转换成 byte,所以AsciiString
比String类型有更好的性能。
参考: