HTTP/HTTPS 是最常见的一种协议,这节主要是看一下 Netty 提供的 ChannelHaandler。

一、HTTP Decoder,Encoder 和 Codec

  HTTP 是请求-响应模式,客户端发送一个 HTTP 请求,服务就响应此请求。  HttpRequest 包格式如下:  
图片描述

  • 包头
  • 数据部分,后续可以有多个 HttpContent 部分
  • 包尾,标记 request 包结束,同时可能包含头的尾部信息
  • 完整的 HTTP request
      HttpResponce 包格式如下:  
    图片描述
  • 包头
  • 数据部分,后续可以有多个 HttpContent 部分
  • 包尾,标记 responce 包结束,同时可能包含头的尾部信息
  • 完整的 HTTP responce

  下面是 Netty 提供的解码器和编码器用来处理上述的包信息:
  
图片描述
  所以,如果我们想要在应用程序中支持 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) {
             // 客户端需要解码服务器响应,编码客户端请求
             pipeline.addLast("decoder", new HttpResponseDecoder());
             pipeline.addLast("encoder", new HttpRequestEncoder());
         } else {
             // 服务端需要解码客户端请求,编码服务端响应
             pipeline.addLast("decoder", new HttpRequestDecoder());
             pipeline.addLast("encoder", new HttpResponseEncoder());
         }
     }
 
 }

二、HTTP 消息聚合

  由于 HTTP 请求和响应消息部分可以由许多块组成,我们需要聚合它们形成完整的消息。Netty 提供了一个聚合器。如下为简单实现:

 /**
  * HTTP 消息聚合
  * HttpObjectAggregator
  */
 public class HttpAggregatorInitializer extends ChannelInitializer<Channel> {
     private final boolean client;
     
     public HttpAggregatorInitializer(boolean client) {
         this.client = client;
     }
 
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch.pipeline();
         if(client) {
             // 客户端
             pipeline.addLast("codec", new HttpClientCodec());
         } else {
             // 服务器
             pipeline.addLast("codec", new HttpServerCodec());
         }
         // HTTP聚合,设置最大消息值为512KB
         pipeline.addLast("aggegator", new HttpObjectAggregator(512 * 1024));
     }
 
 }

三、HTTP 压缩

  使用 HTTP 时建议压缩数据以减少传输流量,Netty 支持 “gzip”和“deflate”。简单实现如下:

 /**
  * HTTP 压缩
  * HttpContentDecompressor 用于客户端解压缩
  * HttpContentCompressor 用于服务器压缩
  */
 public class HttpCompressorInitializer extends ChannelInitializer<Channel> {
     private final boolean client;
     
     public HttpCompressorInitializer(boolean client) {
         this.client = client;
     }
     
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline = ch.pipeline();
         if(client) {
             // 客户端
             pipeline.addLast("codec", new HttpClientCodec());
             // 解压缩,用于处理来自服务器的压缩内容
             pipeline.addLast("decompressor", new HttpContentDecompressor());
         } else {
             // 服务端
             pipeline.addLast("codec", new HttpServerCodec());
             // 压缩,将要发送的消息压缩后再发出
             pipeline.addLast("compressor", new HttpContentCompressor());
         }
     }
 
 }

四、使用 HTTPS

  启动 HTTPS(比 HTTP 安全),只需添加 SslHandler。简单实现如下:

 /**
  * HTTPS
  */
 public class HttpsCodecInitializer extends ChannelInitializer<Channel> {
     private final SslContext context;
     private final boolean client;
     
     public HttpsCodecInitializer(SslContext context, boolean client) {
         this.context = context;
         this.client = client;    
     }
 
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ChannelPipeline pipeline  = ch.pipeline();
         SSLEngine engine = context.newEngine(ch.alloc());
         
         // 添加SslHandler以启用HTTPS
         pipeline.addFirst("ssl", new SslHandler(engine));
         if(client) {
             // 客户端
             pipeline.addLast("codec", new HttpClientCodec());
         } else {
             // 服务端
             pipeline.addLast("codec", new HttpServerCodec());
         }
     }
     
 }

五、WebSocket

  WebSocket 允许数据双向传输,而不需要请求-响应模式。当我们需要服务器主动向客户端发送消息,比如实时系统,WebSocket 就是一个不错的选择。下面是一个通用的 WebSocket 协议:
  
图片描述

  • Client(HTTP)与 Server 通讯
  • Server(HTTP)与 Client 通讯
  • Client 通过 HTTP(s) 来进行 WebSocket 握手,并等待确认
  • 连接协议升级至 WebSocket

  应用程序支持 WebSocket 只需要添加适当的客户端或服务器端 WebSocket ChannelHandler 到管道。这个类将处理 WebSocket 定义的信息类型,称为“帧”。帧类型可分为数据帧和控制帧,如下:
  
图片描述
  简单实现如下:

 /**
  * WebSocket
  * WebSocketServerProtocolHandler 处理其他类型帧
  * TextFrameHandler BinaryFrameHandler ContinuationFrameHandler
  */
 public class WebSocketServerInitializer extends ChannelInitializer<Channel> {
 
     @Override
     protected void initChannel(Channel ch) throws Exception {
         ch.pipeline().addLast(
                 new HttpServerCodec(),
                 new HttpObjectAggregator(65536),        // HTTP 聚合
                 // 处理除指定Frame之外的其他类型帧,比如Ping,Pong,Close等
                 new WebSocketServerProtocolHandler("/websocket"),
                 new TextFrameHandler(),
                 new BinaryFrameHandler(),
                 new ContinuationFrameHandler());
     }
 
     // Text Frame
     public static final class TextFrameHandler 
         extends SimpleChannelInboundHandler<TextWebSocketFrame> {
         @Override
         protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
             // TODO Handle Text Frame
         }        
     }
     
     // Binary Frame
     public static final class BinaryFrameHandler 
     extends SimpleChannelInboundHandler<BinaryWebSocketFrame> {
         @Override
         protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception {
             // TODO Handle Text Frame
         }
     }
     
     // Continuation Frame
     public static final class ContinuationFrameHandler 
     extends SimpleChannelInboundHandler<ContinuationWebSocketFrame> {
         @Override
         protected void channelRead0(ChannelHandlerContext ctx, ContinuationWebSocketFrame msg) throws Exception {
             // TODO Handle Text Frame
         }
     }
 }