内容简介:这篇文章主要介绍如何用Netty构建一个HTTP/HTTPS应用程序,用一个HelloWorld级Demo进行阐述因为要同时构建HTTPS应用程序,所以我们需要通过使用 SSL/TLS保护Netty应用程序,这里先简单介绍下 SSL/TLS协议。SSL和TLS都是运输层的安全协议, 它们发展历史如下:
这篇文章主要介绍如何用Netty构建一个HTTP/HTTPS应用程序,用一个HelloWorld级Demo进行阐述
SSL/TLS协议简介
因为要同时构建HTTPS应用程序,所以我们需要通过使用 SSL/TLS保护Netty应用程序,这里先简单介绍下 SSL/TLS协议。
SSL和TLS都是运输层的安全协议, 它们发展历史如下:
- 1995: SSL 2.0 ,由Netscape提出,这个版本由于设计缺陷,并不安全,很快被发现有严重漏洞,已经废弃
- 1996:SSL 3.0写成RFC,开始流行,目前(从2015年)已经不安全,必须禁用
- 1999:TLS1.0互联网标准化组织ISOC接替NetScape公司,发布了 SSL 的升级版TLS1.0版
- 2006: TLS 1.1. 作为 RFC 4346 发布。主要fix了CBC模式相关的如BEAST攻击等漏洞
- 2008: TLS 1.2. 作为RFC 5246 发布 。增进安全性。目前(2015年)应该主要部署的版本,请确保你使用的是这个版本
- 2015之后: TLS 1.3,还在制订中,支持0-rtt,大幅增进安全性,砍掉了aead之外的加密方式
由于SSL的2个版本都已经退出历史舞台, 现在一般所说的SSL就是TLS
SSL/TLS安全协议示意图如下:
SSL/TLS协议是一个位于HTTP层与TCP层之间的可选层,其提供的服务主要有:
- 认证用户和服务器,确保数据发送到正确的客户机和服务器
- 加密数据以防止数据中途被窃取
- 维护数据的完整性,确保数据在传输过程中不被改变
关于SSL/TLS协议更加详细的介绍可以查找相关资料,这里就不细说了。
JDK的javax.net.ssl包 VS Netty的OpenSSL/SSLEngine
为了支持 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进行解密和加密的过程如下图所示(摘自《Netty In Action》):
大多数情况下,SslHandler 将是 ChannelPipeline 中的第一个 ChannelHandler。这确保了只有在所有其他的 ChannelHandler 将它们的逻辑应用到数据之后,才会进行加密。
HTTP请求和响应组成部分
HTTP是基于请求/响应模型的的: 客户端向服务端发送一个HTTP请求,然后服务端将会返回一个HTTP响应,Netty提供了多种编码器和解码器以简化对这个协议的使用。
HTTP请求的组成部分如下图:
HTTP响应的组成部分如下图:
如上面两图所示,一个HTTP请求/响应可能由多个数据部分组成,并且它总是以一个 LastHttpContent 部分作为结束。 FullHttpRequest
和 FullHttpResponse
消息是特殊的子类型,分别代表了完整的请求和响应。
所有类型的HTTP消息都实现了 HttpObject
接口
HTTP解码器、编码器和编解码器
Netty为HTTP消息提供了编码器和解码器:
HttpRequestEncoder HttpResponseEecoder HttpRequestDecoder HttpResponseDecoder
编解码器:
-
HttpClientCodec
: 用于客户端的编解码器,等效于HttpRequestEncoder
和HttpResponseDecoder
的组合 -
HttpServerCodec
:用于服务端的编解码器,等效于HttpRequsetDecoder
和HttpResponseEncoder
的组合
以 HttpServerCodec
为例,它的类继承结构图如下:
HttpServerCodec 同时实现了 ChannelInboundHandler
和 ChannelOutboundHandler
接口,以达到同时具有编码和解码的能力。
聚合器:
-
HttpObjectAggregator
: 聚合器,可以将多个消息部分合并为FullHttpRequest
或者FullHttpResponse
消息。使用该聚合器的原因是HTTP解码器会在每个HTTP消息中生成多个消息对象,如HttpRequest/HttpResponse,HttpContent,LastHttpContent
,使用聚合器将它们聚合成一个完整的消息内容,这样就不用关心消息碎片了。
应用程序代码
构建基于Netty的HTTP/HTTPS 应用程序的源代码出自于Netty官方提供的demo,我略微做了一些改动,原地址是: github.com/netty/netty…
源代码:
public class HttpHelloWorldServer { static final boolean SSL = System.getProperty("ssl") != null; static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "8080")); public static void main(String[] args) throws Exception { final SslContext sslContext; //判断SSL是否为true,为true表示使用HTTPS连接,反之,使用HTTP if (SSL) { //使用Netty自带的证书 工具 生成一个数字证书 SelfSignedCertificate certificate = new SelfSignedCertificate(); sslContext = SslContextBuilder.forServer(certificate.certificate(), certificate.privateKey()).build(); } else { sslContext = null; } EventLoopGroup boss = new NioEventLoopGroup(1); EventLoopGroup worker = new NioEventLoopGroup(); try { 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 HttpHelloWorldServerHandler()); } }); ChannelFuture future = bootstrap.bind(PORT).sync(); System.err.println("Open your web browser and navigate to " + (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/'); future.channel().closeFuture().sync(); } finally { boss.shutdownGracefully().sync(); worker.shutdownGracefully().sync(); } } } 复制代码
代码解读
首先判断系统属性ssl是否存在,如果存在,则表明使用安全连接,反之,则使用一般的HTTP连接。
final SslContext sslContext; if (SSL) { SelfSignedCertificate certificate = new SelfSignedCertificate(); sslContext = SslContextBuilder.forServer(certificate.certificate(), certificate.privateKey()).build(); } else { sslContext = null; } 复制代码
上面代码所示,当SSL为true时,使用Netty自带的签名证书工具自定义服务端发送给客户端的数字证书。
接下来和一般的Netty服务端程序步骤一样,先创建 ServerBootstrap
启动类,设置和绑定 NioEventLoopGroup
线程池,创建服务端 Channel,添加ChannelHandler。值得注意的是,添加的ChannelHandler都是与HTTP相关的Handler。
HttpHelloWorldServerHandler
自定义的Handler代码如下:
public class HttpHelloWorldServerHandler 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) { HttpRequest req = (HttpRequest) msg; System.out.println("浏览器请求方式:"+req.method().name()); String content = ""; if ("/hello".equals(req.uri())) { content = "hello world"; response2Client(ctx,req,content); } else { content = "Connect the Server"; response2Client(ctx,req,content); } } } private void response2Client(ChannelHandlerContext ctx, HttpRequest req, String content) { boolean keepAlive = HttpUtil.isKeepAlive(req); FullHttpResponse 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(); } } 复制代码
在此Handler中处理入站数据流,但该代码只是处理 GET
请求,没有对 POST
请求做出处理,所以当浏览器发送一个 GET
请求时,此Handler定义一个HTTP响应体 FullHttpResponse
,设置一些响应头,如· Content-type
、 Connection
、 Content-Length
等,设置响应内容,然后通过 ctx.write
方法写入HTTP消息
AsciiString
在设置响应头时我们用到了 AsciiString ,从Netty 4.1开始,提供了实现了 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类型有更好的性能。
以上所述就是小编给大家介绍的《Netty系列文章之构建HTTP(HTTPS)应用程序》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 构建一个混合的费用跟踪应用程序
- 构建安全计划 提升应用程序安全能力
- 第三章-构建Markdown应用程序
- 构建大型 React 应用程序的最佳实践
- 构建大型 React 应用程序的最佳实践
- 构建Kubernetes有状态应用程序的不同方法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Rapid Web Applications with TurboGears
Mark Ramm、Kevin Dangoor、Gigi Sayfan / Prentice Hall PTR / 2006-11-07 / USD 44.99
"Dear PHP, It's over between us. You can keep the kitchen sink, but I want my MVC. With TurboGears, I was able to shed the most heinous FileMaker Pro legacy 'solu-tion' imaginable. It has relationshi......一起来看看 《Rapid Web Applications with TurboGears》 这本书的介绍吧!