Netty学习摘记 —— 预置SSL / HTTP / WebSocket编解码器
本文参考
本篇文章是对《Netty In Action》一书第十一章"预置的ChannelHandler和编解码器"的学习摘记,主要内容为通过 SSL/TLS 保护 Netty 应用程序、构建基于 Netty 的 HTTP/HTTPS 和websocket应用程序、处理空闲的连接和超时、解码基于分隔符的协议和基于长度的协议、写大型数据
本篇先摘记前两块内容 —— 通过 SSL/TLS 保护 Netty 应用程序、构建基于 Netty 的 HTTP/HTTPS 和WebSocket应用程序
通过SSL/TLS保护Netty应用程序
Adds SSL · TLS and StartTLS support to a Channel.
有关SSL/TLS的内容可以参考这篇文章:https://www.jianshu.com/p/7158568e4867
Netty 通过一个名为SslHandler的ChannelHandler 实现javax.net.ssl 包的SSLContext 和SSLEngine 类,其中SslHandler在内部使用SSLEngine来完成实际的工作
Netty 还提供了使用 OpenSSL 工具包的 SSLEngine实现。这个OpenSslEngine类提供了比 JDK 提供的SSLEngine实现更好的性能。如果OpenSSL库可用,可以将Netty应用程序(客户端和服务器)配置为默认使用OpenSslEngine。 如果不可用,Netty 将会回退到 JDK 实现,不过无论使用 JDK 的SSLEngine还是使用 Netty 的OpenSslEngine,SSL API 和数据流都是一致的
它的实现过程示意图如下

在大多数情况下,SslHandler是ChannelPipeline中的第一个ChannelHandler。 这确保了只有在所有其他的ChannelHandler将它们的逻辑应用到数据之后,才会进行加密,示例代码如下
public class SslChannelInitializer extends ChannelInitializer<Channel> {
private final SslContext context;
private final boolean startTls;
//传入要使用的 SslContext
//如果startTls设置为 true,第一个写入的消息将不会被加密(客户端应该设置为 true)
public SslChannelInitializer(SslContext context, boolean startTls) {
this.context = context;
this.startTls = startTls;
}
@Override
protected void initChannel(Channel ch) throws Exception {
//对于每个 SslHandler 实例
//都使用 Channel 的 ByteBufAllocator 从 SslContext 获取一个新的 SSLEngine
SSLEngine engine = context.newEngine(ch.alloc());
//将 SslHandler 作为第一个 ChannelHandler 添加到 ChannelPipeline 中
ch.pipeline().addFirst("ssl", new SslHandler(engine, startTls));
}
}
我们可以看到,将SslHandler添加到ChannelPipeline是十分简单的,只需要将它的实例添加到ChannelPipeline即可
下面是有关SslHandler的api

在加密之前会先进行SSL/TLS握手,握手会在Channel被激活后自动执行,不需要我们手动配置
The handshake will be automatically issued for you once the Channel is active and SSLEngine.getUseClientMode() returns true. So no need to bother with it by your self.
握手的成功与否,既可以通过SslHandler的handshakeFuture()方法获得通知,也可以在下一个ChannelHandler的userEventTriggered()方法内检查是否有SslHandshakeCompletionEvent事件发生来获得通知
Beside using the handshake ChannelFuture to get notified about the completion of the handshake it's also possible to detect it by implement the ChannelInboundHandler.userEventTriggered(ChannelHandlerContext, Object) method and check for a SslHandshakeCompletionEvent.
HTTP编解码器
下图分别展示了生产和消费 HTTP 请求和 HTTP 响应的方法


一个 HTTP 请求/响应可能由多个数据部分组成,并且它总是以一个LastHttpContent部分作为结束。Netty中FullHttpRequest和FullHttpResponse消息是特殊的子类型,分别代表了完整的请求和响应
有关HTTP的编解码器有HttpRequestEncoder、HttpResponseEncoder、HttpRequestDecoder、HttpResponseDecoder
HttpClientCodec在结合了HttpRquestEncoder和HttpResponseDecoder的基础上,还提供了额外的状态管理
A combination of HttpRequestEncoder and HttpResponseDecoder which enables easier client side HTTP implementation. HttpClientCodec provides additional state management for HEAD and CONNECT requests, which HttpResponseDecoder lacks.
下面是将HTTP编解码器添加到ChannelPipline的示例代码
public class HttpPipelineInitializer extends ChannelInitializer<Channel> {
private final boolean client;
public HttpPipelineInitializer(boolean client) {
this.client = client;
}
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (client) {
//如果是客户端,则添加 HttpResponseDecoder 以处理来自服务器的响应
pipeline.addLast("decoder", new HttpResponseDecoder());
//如果是客户端,则添加 HttpRequestEncoder 以向服务器发送请求
pipeline.addLast("encoder", new HttpRequestEncoder());
} else {
//如果是服务器,则添加 HttpRequestDecoder 以接收来自客户端的请求
pipeline.addLast("decoder", new HttpRequestDecoder());
//如果是服务器,则添加 HttpResponseEncoder 以向客户端发送响应
pipeline.addLast("encoder", new HttpResponseEncoder());
}
}
}
HttpObjectAggregator聚合HTTP消息
A ChannelHandler that aggregates an HttpMessage and its following HttpContents into a single FullHttpRequest or FullHttpResponse (depending on if it used to handle requests or responses) with no following HttpContents. It is useful when you don't want to take care of HTTP messages whose transfer encoding is 'chunked'. Insert this handler after HttpResponseDecoder in the ChannelPipeline if being used to handle responses, or after HttpRequestDecoder and HttpResponseEncoder in the ChannelPipeline if being used to handle requests.
正如我们在上面HTTP的请求和响应组成部分图中所看到的,HTTP 的请求和响应可能由许多部分组成,因此需要聚合它们以形成完整的消息,Netty为此提供了HttpObjectAggregator类来简化这一操作,消息分段将被缓冲,直到可以转发一个完整的消息给下一个 ChannelInboundHandler
public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {
private final boolean isClient;
public HttpAggregatorInitializer(boolean isClient) {
this.isClient = isClient;
}
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (isClient) {
//如果是客户端,则添加 HttpClientCodec
pipeline.addLast("codec", new HttpClientCodec());
} else {
//如果是服务器,则添加 HttpServerCodec
pipeline.addLast("codec", new HttpServerCodec());
}
//将最大的消息大小为 512 KB 的 HttpObjectAggregator 添加到 ChannelPipeline
pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024));
}
}
压缩 / 解压HTTP消息
当使用 HTTP 时,可以开启压缩功能以尽可能多地减小传输数据的大小,Netty同样为压缩和解压缩提供了ChannelHandler实现,它们同时支持gzip和deflate编 码
HttpContentDecompressor提供了解压操作
Decompresses an HttpMessage and an HttpContent compressed in gzip or deflate encoding.
HttpContentCompressor提供了压缩操作,根据HTTP头信息中的Accept-Encoding来确定编码方式
Compresses an HttpMessage and an HttpContent in gzip or deflate encoding while respecting the "Accept-Encoding" header. If there is no matching encoding, no compression is done.
public class HttpCompressionInitializer extends ChannelInitializer<Channel> {
private final boolean isClient;
public HttpCompressionInitializer(boolean isClient) {
this.isClient = isClient;
}
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
if (isClient) {
//如果是客户端,则添加 HttpClientCodec
pipeline.addLast("codec", new HttpClientCodec());
//如果是客户端,则添加 HttpContentDecompressor 以处理来自服务器的压缩内容
pipeline.addLast("decompressor",
new HttpContentDecompressor());
} else {
//如果是服务器,则添加 HttpServerCodec
pipeline.addLast("codec", new HttpServerCodec());
//如果是服务器,则添加HttpContentCompressor 来压缩数据(如果客户端支持它)
pipeline.addLast("compressor", new HttpContentCompressor());
}
}
}
结合SSL / TLS启用HTTPS
启用 HTTPS 只需要将 SslHandler 添加到 ChannelPipeline 的 ChannelHandler组合中
public class HttpsCodecInitializer extends ChannelInitializer<Channel> {
private final SslContext context;
private final boolean isClient;
public HttpsCodecInitializer(SslContext context, boolean isClient) {
this.context = context;
this.isClient = isClient;
}
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
SSLEngine engine = context.newEngine(ch.alloc());
//将 SslHandler 添加到ChannelPipeline 中以使用 HTTPS
pipeline.addFirst("ssl", new SslHandler(engine));
if (isClient) {
//如果是客户端,则添加 HttpClientCodec
pipeline.addLast("codec", new HttpClientCodec());
} else {
//如果是服务器,则添加 HttpServerCodec
pipeline.addLast("codec", new HttpServerCodec());
}
}
}
WebSocket
在上一篇文章"编解码器"的MessageToMessage代码示例中已经接触了WebSocket的数据帧和控制帧,下面的表格做一个归纳

下面是服务器端支持 WebSocket的代码示例
public class WebSocketServerInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(
new HttpServerCodec(),
//为握手提供聚合的 HttpRequest
new HttpObjectAggregator(65536),
//如果被请求的端点是"/websocket",则处理该升级握手
new WebSocketServerProtocolHandler("/websocket"),
//TextFrameHandler 处理 TextWebSocketFrame
new TextFrameHandler(),
//BinaryFrameHandler 处理 BinaryWebSocketFrame
new BinaryFrameHandler(),
//ContinuationFrameHandler 处理 ContinuationWebSocketFrame
new ContinuationFrameHandler());
}
public static final class TextFrameHandler extends
SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
public void channelRead0(ChannelHandlerContext ctx,
TextWebSocketFrame msg) throws Exception {
// Handle text frame
}
}
public static final class BinaryFrameHandler extends
SimpleChannelInboundHandler<BinaryWebSocketFrame> {
@Override
public void channelRead0(ChannelHandlerContext ctx,
BinaryWebSocketFrame msg) throws Exception {
// Handle binary frame
}
}
public static final class ContinuationFrameHandler extends
SimpleChannelInboundHandler<ContinuationWebSocketFrame> {
@Override
public void channelRead0(ChannelHandlerContext ctx,
ContinuationWebSocketFrame msg) throws Exception {
// Handle continuation frame
}
}
}
要想为 WebSocket 添加安全性,只需要将 SslHandler 作为第一个 ChannelHandler 添加到 ChannelPipeline中
Netty学习摘记 —— 预置SSL / HTTP / WebSocket编解码器的更多相关文章
- Netty学习摘记 —— 初步认识Netty核心组件
本文参考 我在博客内关于"Netty学习摘记"的系列文章主要是对<Netty in action>一书的学习摘记,文章中的代码也大多来自此书的github仓库,加上了一 ...
- Netty学习摘记 —— 初识编解码器
本文参考 本篇文章是对<Netty In Action>一书第十章"编解码器框架"的学习摘记,主要内容为解码器和编码器 编解码器实际上是一种特殊的ChannelHand ...
- Netty学习摘记 —— 心跳机制 / 基于分隔符和长度的协议
本文参考 本篇文章是对<Netty In Action>一书第十一章"预置的ChannelHandler和编解码器"的学习摘记,主要内容为通过 SSL/TLS 保护 N ...
- Netty学习摘记 —— 简单WEB聊天室开发
本文参考 本篇文章是对<Netty In Action>一书第十二章"WebSocket"的学习摘记,主要内容为开发一个基于广播的WEB聊天室 聊天室工作过程 请求的 ...
- Netty学习摘记 —— 深入了解Netty核心组件
本文参考 本篇文章是对<Netty In Action>一书第三章"Netty的组件和设计"的学习摘记,主要内容为Channel.EventLoop.ChannelFu ...
- Netty学习摘记 —— UDP广播事件
本文参考 本篇文章是对<Netty In Action>一书第十三章"使用UDP广播事件"的学习摘记,主要内容为广播应用程序的开发 消息POJO 我们将日志信息封装成名 ...
- Netty学习摘记 —— 单元测试
本文参考 本篇文章是对<Netty In Action>一书第九章"单元测试"的学习摘记,主要内容为使用特殊的 Channel 实现--EmbeddedChannel来 ...
- Netty学习摘记 —— 再谈引导
本文参考 本篇文章是对<Netty In Action>一书第八章"引导"的学习摘记,主要内容为引导客户端和服务端.从channel内引导客户端.添加ChannelHa ...
- Netty学习摘记 —— 再谈EventLoop 和线程模型
本文参考 本篇文章是对<Netty In Action>一书第七章"EventLoop和线程模型"的学习摘记,主要内容为线程模型的概述.事件循环的概念和实现.任务调度和 ...
随机推荐
- 为Visual Studio 2019设置 更改皮肤
下载主题插件:Color Themes for Visual Studio 安装插件 下载完成后 关闭vs2019 完成初始化,初始化完成后,再次打开软件进行配置. 卸载插件 点击卸载,然后关闭vs2 ...
- 从零开始,开发一个 Web Office 套件(6):光标 & Click 事件
<从零开始, 开发一个 Web Office 套件>系列博客目录 这是一个系列博客,最终目的是要做一个基于 HTML Canvas 的.类似于微软 Office 的 Web Office ...
- Linux概述及简单命令
Linux概述及简单命令 转自https://www.cnblogs.com/ayu305/p/Linux_basic.html 一.准备工作 1.环境选择:VMware\阿里云服务器 2.Linux ...
- docker 搭建php 开发环境 添加扩展redis、swoole、xdebug
docker-compose搭建lnmp 先决条件 首先需要安装docker 安装docker-compost 1.创建lnmp工作目录 #创建三个目录 mkdir lnmp && c ...
- 国产化之银河麒麟安装达梦数据库DM8
背景 某个项目需要实现基础软件全部国产化,其中操作系统指定银河麒麟,数据库使用DM8. 虽然在之前的文章中已经成功模拟国产飞腾处理器,但是运行效率不高,所以这里的银河麒麟操作系统还是运行在x64平台上 ...
- JavaScript面向对象—对象的创建和操作
JavaScript面向对象-对象的创建和操作 前言 虽然说在JavaScript编程语言中,函数是第一公民,但是JavaScript不仅支持函数式编程,也支持面向对象编程.JavaScript对象设 ...
- 全面解读 AWS Private 5G 的革新理念
目录 目录 目录 前言 近几年 AWS 在 5G ICT 领域的部署 AWS 与 Verizon 合作推出的 Private MEC 解决方案 AWS 与 Vodafone Business 合作推出 ...
- Mysql 8.0 配置主从备份
my.ini文件的位置 mysql 8.0安装完过后没有my.ini疑惑了我好久,最后发现,配置文件在,C盘的一个隐藏文件夹里面 具体路径如下图 主库配置 修改主库INI文件 在[mysqld]节点添 ...
- Kubernetes:Ingress总结(一)
Blog:博客园 个人 参考:Ingress | Kubernetes.<Kubernetes进阶实战>.<Kubernetes网络权威指南 > 何谓Ingress?从字面意思 ...
- ArcMap操作随记(14)
1.ArcMap中模型转为Python脚本 [模型]→右键→[编辑]→[模型]→[导出]→[至Python脚本] 2.一般来说,植被指数NDVI,-1<=NDVI<=1. 3.用lands ...