Netty实战(十一)预置的ChannelHandler和编解码器(一)

简介: 作为一个通讯框架,通讯数据的安全性也是不可或缺的一部分。一般常见的像TLS/SSL这样的安全协议我们都应该熟悉。 我们在访问安全网站时都遇到过这些协议,但是它们也可用于其他不是基于HTTP的应用程序,如安全SMTP(SMTPS)邮件服务器甚至是关系型数据库系统。

@TOC

一、SSL和TLS添加

作为一个通讯框架,通讯数据的安全性也是不可或缺的一部分。一般常见的像TLS/SSL这样的安全协议我们都应该熟悉。 我们在访问安全网站时都遇到过这些协议,但是它们也可用于其他不是基于HTTP的应用程序,如安全SMTP(SMTPS)邮件服务器甚至是关系型数据库系统。

像Java就提供了javax.net.ssl 包来支持SSL/TLS,它的 SSLContext 和 SSLEngine类使得实现解密和加密相当简单直接。Netty 则是通过一个名为 SslHandler 的 ChannelHandler实现利用了这个 API,其中 SslHandler 在内部使用 SSLEngine 来完成实际的工作。

Netty 的 OpenSSL/SSLEngine 实现:
Netty 还提供了使用 OpenSSL工具包(www.openssl.org)的 SSLEngine 实现。这个 OpenSslEngine 类提供了比 JDK 提供的SSLEngine 实现更好的性能。如果OpenSSL库可用,可以将Netty应用程序(客户端和服务器)配置为默认使用OpenSslEngine。 如果不可用,Netty将会回退到 JDK 实现。 注意,无论你使用 JDK 的 SSLEngine 还是使用 Netty 的 OpenSslEngine,SSL API 和数据流都 是一致的。

下面这张图展示了SslHandler 进行解密和加密数据流:
1.png

下面我们使用ChannelInitializer来将SslHandler添加到ChannelPipeline 中,ChannelInitializer在channel注册好时设置ChannelPipeline 我们之前说过,忘记可以看看前面的内容。

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;

import javax.net.ssl.SSLEngine;

/**
 * Author: lhd
 * Data: 2023/6/11
 * Annotate: Netty 添加SSL/TLS支持
 */
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同样包含其他的方法,例如:在握手阶段,两个节点将相互验证并且商定一种加密方式。我们可以配置 SslHandler 来修改它的行为,或者在 SSL/TLS握手一旦完成之后提供通知,握手阶段完成之后,所有的数据都将会被加密。SSL/TLS 握手将会被自动执行。

下面是一些SslHandler的方法:

方 法 名 称 描 述
setHandshakeTimeout (long,TimeUnit);setHandshakeTimeoutMillis (long);getHandshakeTimeoutMillis() 设置和获取超时时间,超时之后,握手ChannelFuture 将会被通知失败
setCloseNotifyTimeout (long,TimeUnit);setCloseNotifyTimeoutMillis (long);getCloseNotifyTimeoutMillis() 设置和获取超时时间,超时之后,将会触发一个关闭通知并关闭连接。这也将会导致通知该 ChannelFuture 失败
handshakeFuture() 返回一个在握手完成后将会得到通知的ChannelFuture。如果握手先前已经执行过了,则返回一个包含了先前的握手结果的 ChannelFuture
close();close(ChannelPromise);close(ChannelHandlerContext,ChannelPromise) 发送 close_notify 以请求关闭并销毁

底层的 SslEngine|

二、基于Netty的HTTP程序

HTTP/HTTPS大部分同学都不会陌生,它是我们常用的协议之一。我们熟悉的另一个协议 WebService API 一般也是基于HTTP/HTTPS的。

下面我们使用Netty 提供的 ChannelHandler,来处理 HTTP 和 HTTPS协议。

2.1 HTTP解码器、编码器和编解码器

HTTP 是基于请求/响应模式的:客户端向服务器发送一个 HTTP 请求,然后服务器将会返回一个 HTTP 响应。Netty 提供了多种编码器和解码器简化了对这个协议的使用。

我们先来看看如生产和消费HTTP请求以及HTTP响应的方法:

  • 下图是一个HTTP的请求的组成:

2.png

  • 下图是HTTP响应的组成:
    3.png

一个 HTTP 请求/响应可能由多个数据部分组成,并且它总是以一个 LastHttpContent部分作为结束。FullHttpRequest 和 FullHttpResponse 消息是特殊的子类型,分别代表了完整的请求和响应。所有类型的 HTTP 消息都实现了 HttpObject 接口。

那么HTTP的编码器和解码器都包含哪些方法呢?

名 称 描 述
HttpRequestEncoder 将HttpRequest、HttpContent 和 LastHttpContent 消息编码为字节
HttpResponseEncoder 将HttpResponse、HttpContent 和LastHttpContent 消息编码为字节
HttpRequestDecoder 将字节解码为HttpRequest、HttpContent 和 LastHttpContent 消息
HttpResponseDecoder 将字节解码为HttpResponse、HttpContent 和LastHttpContent 消息

了解了HTTP协议后,我们将它添加到我们的应用程序中:

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;

/**
 * Author: lhd
 * Data: 2023/6/11
 * Annotate: 添加HTTP协议支持
 */
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 {
   
   
            //如果是服务器,则添加 HttpResponseEncoder以向客户端发送响应
            pipeline.addLast("decoder", new HttpRequestDecoder());
            //如果是服务器,则添加 HttpRequestDecoder以接收来自客户端的请求
            pipeline.addLast("encoder", new HttpResponseEncoder());
        }
    }
}

2.2 聚合HTTP消息

为什么要聚合HTTP消息?
ChannelInitializer 将 ChannelHandler 安装到 ChannelPipeline中之后,便可以处理不同类型的 HttpObject 消息了。但是由于 HTTP
的请求和响应可能由许多部分组成,因此需要聚合它们以形成完整的消息。

Neey是如何做的?

Netty 提供了一个聚合器,它可以将多个消息部分合并为 FullHttpRequest 或者 FullHttpResponse 消息。通过这样的方式,我们将看到完整的消息内容。由于消息分段需要被缓冲,直到可以转发一个完整的消息给下一个 ChannelInboundHandler,所以这个操作有轻微的开销。

下面展示一下这种操作是如何进行的:

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;

/**
 * Author: lhd
 * Data: 2023/6/11
 * Annotate: HTTP消息聚合
 */
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));
    }
}

2.3 HTTP压缩

HTTP为什么要压缩?不是已经聚合了么?
当使用 HTTP 时,压缩可以尽可能的多地减小传输数据的大小。虽然压缩会带来一些 CPU时钟周期上的开销,但是通常来说它都是一个好主意,特别是对于文本数据来说。

Netty 为压缩和解压缩提供了 ChannelHandler 实现,它们同时支持 gzip 和 deflate 编 码。

HTTP 请求的头部信息
客户端可以通过提供以下头部信息来指示服务器它所支持的压缩格式:
GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, deflate
然而,需要注意的是,服务器没有义务压缩它所发送的数据。

下面展示一下如何自动的压缩HTTP消息:

import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpContentCompressor;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpServerCodec;

/**
 * Author: lhd
 * Data: 2023/6/11
 * Annotate: HTTP消息压缩
 */
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());
        }
    }
}

压缩及其依赖
如果你正在使用的是 JDK 6 或者更早的版本,那么你需要将 JZlib(www.jcraft.com/jzlib/)添加到CLASSPATH 中以支持压缩功能。
对于 Maven,请添加以下依赖项:

<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jzlib</artifactId>
<version>1.1.3</version>
</dependency>
目录
相关文章
|
2月前
|
Java Unix Linux
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
当涉及到网络通信和高性能的Java应用程序时,Netty是一个强大的框架。它提供了许多功能和组件,其中之一是JNI传输。JNI传输是Netty的一个特性,它为特定平台提供了高效的网络传输。 在本文中,我们将深入探讨Netty提供的特定平台的JNI传输功能,分析其优势和适用场景。我们将介绍每个特定平台的JNI传输,并讨论其性能、可靠性和可扩展性。通过了解这些特定平台的JNI传输,您将能够更好地选择和配置适合您应用程序需求的网络传输方式,以实现最佳的性能和可靠性。
57 7
【Netty技术专题】「原理分析系列」Netty强大特性之Native transports扩展开发实战
|
30天前
|
NoSQL Redis
Netty实战:模拟Redis的客户端
Netty实战:模拟Redis的客户端
14 0
|
3月前
|
分布式计算 前端开发 网络协议
13W字!腾讯高工手写“Netty速成手册”,3天能走向实战
在java界,netty无疑是开发网络应用的拿手菜。你不需要太多关注复杂的nio模型和底层网络的细节,使用其丰富的接口,可以很容易的实现复杂的通讯功能。
|
3月前
|
编解码 开发者
Netty Review - 深入理解Netty: ChannelHandler的生命周期与事件处理机制
Netty Review - 深入理解Netty: ChannelHandler的生命周期与事件处理机制
54 0
|
3月前
|
监控 网络协议 调度
Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析
Netty Review - 深入探讨Netty的心跳检测机制:原理、实战、IdleStateHandler源码分析
110 0
|
4月前
|
存储 编解码 Java
Netty使用篇:自定义编解码器
Netty使用篇:自定义编解码器
|
4月前
|
设计模式 JSON 编解码
Netty使用篇:编解码器
Netty使用篇:编解码器
|
4月前
|
网络协议 Java 容器
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
41 0
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
|
7月前
|
监控 Java Linux
由浅入深Netty基础知识NIO网络编程1
由浅入深Netty基础知识NIO网络编程
40 0
|
7月前
|
缓存 安全 Java
由浅入深Netty基础知识NIO三大组件原理实战 2
由浅入深Netty基础知识NIO三大组件原理实战
47 0