基于Netty构建HTTP应用程序
通常HTTP协议通信过程中,客户端和 服务器端的交互过程如下:
- 客户端(如 Postman工具、浏览器、 Java程序等)向 Server服务端发送 HTTP请求;
- Server服务端对 HTTP请求进行解析;
- Server服务端向Client客户端发送 HTTP响应报文;
- Client客户端解析HTTP响应的应用层协议内容;
Netty中内置的HTTP的解码和编码的处理器
以上交互过程中,Server端将涉及到HTTP请求的解码处理和HTTP响应的编码处理,Netty已经内置了这些解码和编码的处理器,大致如下:
- HttpRequestDecoder
HTTP请求编码器,这是一个入站处理器,间接的继承了ByteToMessageDecoder,将 ByteBuf缓冲区解码成代表请求的HttpRequest首部实例和HttpContent内容实例;并且,HttpRequestDecoder在解码时会处理好分块(Chunked)类型和固定长度(Content-Length)类型的 HTTP请求报文;
- HttpResponseEncoder
HTTP响应编码器,它把代表响应的HttpResonse首部实例和HttpContent内容实例编码成ByteBuf字节流,它是一个出站处理器;
- HttpServerCodec
HTTP的编解码器,它是HttpRequestDecoder解码器和HttpResponseEncoder编码器的结合体,换句话说,HttpServerCodec可以替代HttpRequestDecoder和HttpResponseEncoder;
- HttpObjectAggregator
由于HTTP的请求和响应可能由许多部分组成,因此开发人员需要聚合它们以形成完整的消息,为了消除这项繁琐的任务,Netty提供了一个聚合器,它可以将多个消息部分合并为FullHttpRequest或者FullHttpResponse消息,通过这样的方式,获取完整的消息内容;
- QueryStringDecoder
把HTTP的请求URI分割成Path路径和Key-Value参数键值对,不继承任何InboundHandler和OutboundHandler的基类,对于同一次请求,在自定义的Handler只有使用一次有效;
基于Netty的HTTP请求的处理流程
基于Netty的 HTTP请求的处理流程,大致如下:
- 二进制的 HTTP数据包从 Channel通道入站后,首先进入 Pipeline流水线的是ByteBuf字节流;
- HttpRequestDecoder首先将ByteBuf缓冲区中的请求行(RequestLine)和请求头Header解析成 HttpRequest首部对象,传入到 HttpObjectAggregator;然后再将 HTTP数据包的请求体Body解析出HttpContent对象(可能是多个),传入到 HttpObjectAggregator聚合器;解码完成之后,如果没有更多的请求体内容, HttpRequestDecoder会传递一个LastHttpContent结束实例到聚合器 HttpObjectAggregator,表示 HTTP请求数据已经解析完成;
- 当 HttpObjectAggregator发现有入站包为LastHttpContent实例入站时,代表 HTTP请求数据协议解析完成,此时,会将所收到的全部 HttpObject实例,封装一个FullHttpRequest整体请求实例,发送给下一站,这里的下一站基本上为业务处理器;
Netty中HTTP请求和响应的组成部分
一个HTTP 请求/响应可能由多个数据部分组成,并且它总是以一个LastHttpContent部分作为结束;FullHttpRequest和FullHttpResponse消息是特殊的子类型,分别代表了完整的请求和响应;所有类型的HTTP消息都实现了HttpObject接口;
Netty中HTTP请求组成部分
Netty内置的与HTTP请求报文相对应的,大致如下几个:
- FullHttpRequest
包含整个 HTTP请求的信息,包含对 HttpRequest首部和HttpContent请求体的组合;
- HttpRequest
请求首部,主要包含对 HTT请求行(Request Line)和请求头Header的组合;
- HttpContent
HTT请求体Body进行封装,本质上就是一个ByteBuf缓冲区实例;如果ByteBuf的长度是固定的,请求的Body过大,可能包含多个HttpContent,解码的时候,最后一个解码返回对象为LastHttpContent(空的HttpContent),表示对 Body的解码已经结束;
- HttpMethod
主要是对 HTTP请求方法 Method的封装;
- HttpVersion
对HTTP版本Version的封装,该类定义了HTTP/1.0和HTTP/1.1两个协议版本;
- HttpHeaders
包含对 HTTP报文请求头 Header的封装及相关操作;
HTTP报文各部分所对应的Netty类,如下
Netty的HttpRequest首部类中有一个 String类型的uri成员变量,主要是对请求URI的封装,该成员包含了HTTP请求的Path路径和跟随在其后的请求参数;
Netty中HTTP响应组成部分
FullHttpResponse与FullHttpRequest不同的是,FullHttpResponse中第一个部分为HttpResponse;
基于Netty的HTTP响应编码的流程
Netty的HTTP响应的处理流程,只需在流水线装配HttpResponseEncoder编码器即可,该编码器是一个出站处理器,有以下特点:
- 该编码器输入的是FullHttpResponse响应实例,输出是ByteBuf字节缓冲器,后面的处理器会将该ByteBuf数据写入Channel通道,最终会被发送到HTTP客户端;
- 该编码器按照HTTP协议对入站FullHttpResponse实例的响应行、响应头、 响应体进行序列化,通过Header响应头去判断是否含有Content-Length头或者Trunked头,然后将响应体Body按照相应的长度规则,对内容进行序列化;
大致的Pipeline的装配代码如下:
ChannelPipeline pipeline = ch.pipeline();
// HTTP请求解码器
pipeline.addLast(new HttpRequestDecoder());
// HTTP请求聚合器
pipeline.addLast(new HttpObjectAggregator(65535));
// HTTP响应编码器
pipeline.addLast(new HttpResponseEncoder());
// 自定义的Handler
pipeline.addLast(new HttpEchoHandler());
可以用HttpServerCodec替代HttpRequestDecoder和HttpResponseEncoder,如下:
ChannelPipeline pipeline = ch.pipeline();
// HTTP请求解码器和响应编码器
pipeline.addLast(new HttpServerCodec());
// HTTP请求聚合器
pipeline.addLast(new HttpObjectAggregator(65535));
// 自定义的Handler
pipeline.addLast(new HttpEchoHandler());
注:上述例子中的自定义Handler处理的msg是FullHttpRequest类型的;
HTTP压缩
Netty为压缩和解压缩提供了ChannelHandler实现,它们同时支持gzip和deflate编码;
注:当使用HTTP时,开启压缩功能以尽可能多地减小传输数据的大小,但压缩会带来一些CPU 时钟周期上的开销;
大致的Pipeline的装配代码如下:
客户端装配方式
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpClientCodec());
// 处理来自服务器的压缩内容
pipeline.addLast(new HttpContentDecompressor());
服务端装配方式
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpClientCodec());
// 添加HttpContentCompressor来压缩数据
pipeline.addLast(new HttpContentCompressor());
使用HTTPS协议安全协议通信实战
启用HTTPS只需要将SslHandler添加到ChannelPipeline的ChannelHandler组合中;
流水线装配方式如下:
查看代码
public class HttpCodecInitializer extends ChannelInitializer<Channel> {
private final SSLContext sslContext;
private final boolean isClient;
public HttpCodecInitializer(SSLContext sslContext, boolean isClient) {
this.sslContext = sslContext;
this.isClient = isClient;
}
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//通过上下文实例,创建服务端的 SSL 引擎
SSLEngine sslEngine = sslContext.createSSLEngine();
//在握手的时候,使用服务端模式
sslEngine.setUseClientMode(false);
//单向认证:在服务端设置不需要验证对端身份,无需客户端证实自己的身份
sslEngine.setNeedClientAuth(false);
//创建SslHandler处理器,并加入到流水线
pipeline.addLast(new SslHandler(sslEngine));
if (isClient) {
pipeline.addLast(new HttpClientCodec());
} else {
pipeline.addLast(new HttpServerCodec());
}
pipeline.addLast(new HttpContentCompressor());
// HTTP请求聚合器
pipeline.addLast(new HttpObjectAggregator(65535));
// 自定义的Handler
pipeline.addLast(new HttpEchoHandler());
}
}