Netty 框架学习 —— 基于 Netty 的 HTTP/HTTPS 应用程序
通过 SSL/TLS 保护应用程序
SSL 和 TLS 安全协议层叠在其他协议之上,用以实现数据安全。为了支持 SSL/TLS,Java 提供了 javax.net.ssl 包,它的 SSLContext 和 SSLEngine 类使得实现解密和加密变得相当简单。Netty 通过一个名为 SsLHandler 的 ChannelHandler 实现了这个 API,其中 SSLHandler 在内部使用 SSLEngine 来完成实际工作
Netty 还提供了基于 OpenSSL 工具包的 SSLEngine 实现,比 JDK 提供的 SSLEngine 具有更好的性能。如果 OpenSSL 可用,可以将 Netty 应用程序配置为默认使用 OpenSSLEngine。如果不可用,Netty 将会退回到 JDK 实现

下述代码展示了如何使用 ChannelInitializer 来将 SslHandler 添加到 ChannelPipeline 中
public class SslChannelInitializer extends ChannelInitializer<Channel> {
private final SslContext context;
private final boolean startTls;
public SslChannelInitializer(SslContext context, boolean startTls) {
this.context = context;
this.startTls = startTls;
}
@Override
protected void initChannel(Channel ch) throws Exception {
SSLEngine engine = context.newEngine(ch.alloc());
ch.pipeline().addFirst("ssl", new SslHandler(engine, startTls));
}
}
大多数情况下,Sslhandler 将是 ChannelPipeline 中的第一个 ChannelHandler,这确保了只有在所有其他的 ChannelHandler 将它们的逻辑应用到数据之后,才会进行加密
SSLHandler 具有一些有用的方法,如表所示,例如,在握手阶段,两个节点将相互验证并且商定一种加密方式,你可以通过配置 SslHandler 来修改它的行为,或者在 SSL/TLS 握手一旦完成之后提供通知,握手阶段之后,所有的数据都将会被加密
| 方法名称 | 描述 |
|---|---|
| setHandshakeTimeout(long, TimeUnit) setHandshakeTimeoutMillis(long) getHandshakeTimeoutMillis() |
设置和获取超时时间,超时之后,握手 ChannelFuture 将会被通知失败 |
| setCloseNotifyTimeout(long, TimeUnit) setCloseNotifyTimeoutMillis(long) getCloseNotifyTimeoutMillis() |
设置和获取超时时间,超时之后,将会触发一个关闭通知并关闭连接,这也会导致通知该 ChannelFuture 失败 |
| handshakeFuture() | 返回一个在握手完成后将会得到通知的 ChannelFuture,如果握手先前已经执行过,则返回一个包含了先前握手结果的 ChannelFuture |
| close() close(ChannelPipeline) close(ChannelHandlerContext, ChannelPromise) |
发送 close_notify 以请求关闭并销毁底层的 SslEngine |
HTTP 编解码器
HTTP 是基于请求/响应模式的,客户端向服务器发送一个 HTTP 请求,然后服务器将会返回一个 HTTP 响应,Netty 提供了多种多种编码器和解码器以简化对这个协议的使用
下图分别展示了生产和消费 HTTP 请求和 HTTP 响应的方法


如图所示,一个 HTTP 请求/响应可能由多个数据部分组成,并且总以一个 LastHttpContent 部分作为结束
下表概要地介绍了处理和生成这些消息的 HTTP 解码器和编码器
| 名称 | 描述 |
|---|---|
| HttpRequestEncoder | 将 HTTPRequest、HttpContent 和 LastHttpContent 消息编码为字节 |
| HttpResponseEncoder | 将 HTTPResponse、HttpContent 和 LastHttpContent 消息编码为字节 |
| HttpRequestDecoder | 将字节编码为 HTTPRequest、HttpContent 和 LastHttpContent 消息 |
| HttpResponseDecoder | 将字节编码为 HTTPResponse、HttpContent 和 LastHttpContent 消息 |
下述代码中的 HttpPipelineInitializer 类展示了将 HTTP 支持添加到你的应用程序是多么简单 —— 只需要将正确的 ChannelHandler 添加到 ChannelPipeline 中
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());
}
}
}
聚合 HTTP 消息
在 ChannelInitializer 将 ChannelHandler 安装到 ChannelPipeline 中之后,你就可以处理不同类型的 HTTPObject 消息了。但由于 HTTP 请求和响应可能由许多部分组成,因此你需要聚合它们以形成完整的消息。Netty 提供了一个聚合器,它可以将多个消息部分合并为 FullHttpRequest 或者 FullHttpResponse 消息
由于消息分段需要被缓冲,直到可以转发下一个完整的消息给下一个 ChannelInboundHandler,所以这个操作有轻微的开销,其所带来的好处就是你可以不必关心消息碎片了
引入这种自动聚合机制只不过是向 ChannelPipeline 中添加另外一个 ChannelHandler 罢了,下述代码展示了如何做到这一点:
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());
}
// 将最大的消息大小为 512KB 的 HTTPObjectAggregator 添加到 ChannelPipeline
pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024));
}
}
HTTP 压缩
当使用 HTTP 时,建议开启压缩功能以尽可能多地减小传输数据的大小。虽然压缩会带来一些消耗,但通常来说它都是一个好主意,尤其是对于文本数据而言
Netty 为压缩和解压都提供了 ChannelHandler 实现,它们同时支持 gzip 和 deflate 编码
客户端可以通过提供以下头部信息来指示服务器它所支持的压缩格式
GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, deflate
然而,需要注意的是,服务器没有义务压缩它所发送的数据
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());
// 如果是服务器,则添加 HttpContentDecompressor 来压缩数据
pipeline.addLast("decompressor", new HttpContentDecompressor());
}
}
}
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());
pipeline.addLast("ssl", new SslHandler(engine));
if (isClient) {
pipeline.addLast("codec", new HttpClientCodec());
} else {
pipeline.addLast("codec", new HttpServerCodec());
}
}
}
WebSocket
WebSocket 解决了一个长期存在的问题:既然底层协议(HTTP)是一个请求/响应模式的交互序列,那么如何实时地发布信息呢?AJAX一定程度上解决了这个问题,但数据流仍然是由客户端所发送的请求驱动的
WebSocket 提供了在单个 TCP 连接上提供双向的通信,它为网页和远程服务器之间的双向通信提供了一种替代 HTTP 轮询的方案
要想向你的应用程序添加对于 WebSocket 的支持,你需要将适当的客户端或者服务器 WebSocketChannelHandler 添加到 ChannelPipeline 中。这个类将处理由 WebSocket 定义的称为帧的特殊消息类型,如表所示,WebSocketFrame 可以被归类为数据帧或者控制帧
| 名称 | 描述 |
|---|---|
| BinaryWebSocketFrame | 数据帧:二进制数据 |
| TextWebSocketFrame | 数据帧:文本数据 |
| ContinuationWebSocketFrame | 数据帧:属于上一个 BinaryWebSocketFrame 或者 TextWebSocketFrame 的文本或者二进制的数据 |
| CloseWebSocketFrame | 控制帧:一个 CLOSE 请求,关闭的状态码以及关闭的原因 |
| PingWebSocketFrame | 控制帧:请求一个 PongWebSocketFrame |
| PongWebSocketFrame | 控制帧:对 PingWebSocketFrame 请求的响应 |
因为 Netty 主要是一种服务器端技术,所以我们重点创建 WebSocket 服务器。下述代码展示了使用 WebSocketChannelHandler 的简单示例,这个类会处理协议升级握手,以及三种控制帧 —— Close、Ping 和 Pong,Text 和 Binary 数据帧将会被传递给下一个 ChannelHandler 进行处理
public class WebSocketServerInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(
new HttpServerCodec(),
new HttpObjectAggregator(65536),
// 如果被请求的端点是 /websocket,则处理该升级握手
new WebSocketServerProtocolHandler("/websocket"),
// TextFrameHandler 处理 TextWebSocketFrame
new TextFrameHandler(),
// BinaryFrameHandler 处理 BinaryWebSocketFrame
new BinaryFrameHandler(),
// ContinuationFrameHandler 处理 Continuation WebSocketFrame
new ContinuationFrameHandler());
}
public static final class TextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// do something
}
}
public static final class BinaryFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
@Override
protected void messageReceived(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {
// do something
}
}
public static final class ContinuationFrameHandler extends SimpleChannelInboundHandler<ContinuationWebSocketFrame> {
@Override
protected void messageReceived(ChannelHandlerContext ctx, ContinuationWebSocketFrame msg) throws Exception {
// do something
}
}
}
Netty 框架学习 —— 基于 Netty 的 HTTP/HTTPS 应用程序的更多相关文章
- Netty学习——基于netty实现简单的客户端聊天小程序
Netty学习——基于netty实现简单的客户端聊天小程序 效果图,聊天程序展示 (TCP编程实现) 后端代码: package com.dawa.netty.chatexample; import ...
- Netty 框架学习 —— 第一个 Netty 应用
概述 在本文,我们将编写一个基于 Netty 实现的客户端和服务端应用程序,相信通过学习该示例,一定能更全面的理解 Netty API 该图展示的是多个客户端同时连接到一台服务器.客户端建立一个连接后 ...
- Netty 框架学习 —— 预置的 ChannelHandler 和编解码器
Netty 为许多提供了许多预置的编解码器和处理器,几乎可以开箱即用,减少了在烦琐事务上话费的时间和精力 空闲的连接和超时 检测空闲连接以及超时对于释放资源来说至关重要,Netty 特地为它提供了几个 ...
- Netty 框架学习 —— 添加 WebSocket 支持
WebSocket 简介 WebSocket 协议是完全重新设计的协议,旨在为 Web 上的双向数据传输问题提供一个切实可行的解决方案,使得客户端和服务器之间可以在任意时刻传输消息 Netty 对于 ...
- Netty 框架学习 —— 传输
概述 流经网络的数据总是具有相同的类型:字节,这些字节如何传输主要取决于我们所说的网络传输.用户并不关心传输的细节,只在乎字节是否被可靠地发送和接收 如果使用 Java 网络编程,你会发现,某些时候当 ...
- Netty 框架学习 —— 引导
概述 前面我们学习了 ChannelPipeline.ChannelHandler 和 EventLoop 之后,接下来的问题是:如何将它们组织起来,成为一个可实际运行的应用程序呢?答案是使用引导(B ...
- Netty 框架学习 —— 编解码器框架
编解码器 每个网络应用程序都必须定义如何解析在两个节点之间来回传输的原始字节,以及如何将其和目标应用程序的数据格式做相互转换.这种转换逻辑由编解码器处理,编解码器由编码器和解码器组成,它们每种都可以将 ...
- Netty 框架学习 —— EventLoop 和线程模型
EventLoop 接口 Netty 是基于 Java NIO 的,因此 Channel 也有其生命周期,处理一个连接在其生命周期内发生的事件是所有网络框架的基本功能.通常来说,我们使用一个线程来处理 ...
- Netty 框架学习 —— ByteBuf
概述 网络数据的基本单位总是字节,Java NIO 提供了 ByteBuffer 作为它的字节容器,但这个类的使用过于复杂.Netty 的 ByteBuf 具有卓越的功能性和灵活性,可以作为 Byte ...
随机推荐
- 简单介绍下自动化框架:Robot Framework
一.简介: Robot Framework:Robot Framework是由Python编写的一款功能丰富并且扩展性强的自动化测试框架,也可以在Java和 .NET 上运行. HttpRunner: ...
- mybatis新手快速搭建成功详细操作
1.数据库建表 在数据库中新建一个名为mybatis的数据库,在mybatis数据库中新建一张 t_user 表,表中有3个字段,id,name,password,代码如下: 新建一个mybatis数 ...
- 使用git rebase去掉无谓的融合
git pull 預設的行為是將遠端的 repo. 與本地的 repo. 合併,這也是 DVCS 的初衷,將兩個 branch 合併.但是,很多時候會發生以下這種情形: 這是因為,我們團隊的開發模式是 ...
- 2020BUAA-团队介绍-采访
团队作业-团队介绍和采访 项目 内容 课程:北航2020软件工程 博客园班级地址 作业要求 团队作业-团队介绍和采访 团队介绍 姓名 有图有真相 个人介绍 刘y 精通(没那么熟悉)c++和python ...
- EventSource的自定义实现
前言: 前面两篇文章都介绍了.NET Core 性能诊断工具,其中诊断工具都用到了EventCounters来实时的收集服务器性能指标. 那么收集指标能否自己定义呢? 一.What's EventCo ...
- ES6新增数组的一些思考和使用
ES6数组的新增 伪数组转换为数组的两种方法 Array.from()把一个伪数组转换为一个真正的数组 伪数组:有下标和length,但是不能使用数组方法 let lis = document.que ...
- jQ的隐式迭代和设置样式属性
jQ中的隐式迭代 意义:不需要原生迭代了,在jQ内部自动帮你实现了循环 代码实现: let arr = document.querySelectorAll('li') for(let i = 0;i ...
- 什么是CPU缓存
一.什么是CPU缓存 1. CPU缓存的来历 众所周知,CPU是计算机的大脑,它负责执行程序的指令,而内存负责存数据, 包括程序自身的数据.在很多年前,CPU的频率与内存总线的频率在同一层面上.内存的 ...
- 系统区域设置 本地语言的支持依赖于 /etc/locale.conf,/etc/locale.conf 包含不少于此相关的环境变量
https://linux.cn/lfs/LFS-BOOK-7.7-systemd/chapter07/locale.html 7.7. 系统区域设置 本地语言的支持依赖于 /etc/locale.c ...
- Centos6下通过 oprofile分析CPU性能
Centos6下通过 oprofile分析CPU性能 2014-01-18 10:55:15 bobpen 阅读数 2218更多 分类专栏: linux 版权声明:本文为博主原创文章,遵循CC 4 ...