百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分类 > 正文

Netty 框架学习——基于 Netty 的 HTTP/HTTPS 应用程序

ztj100 2024-12-14 16:12 24 浏览 0 评论

2|0 通过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


2|0HTTP 编解码器

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());
        }
    }
}



3|0聚合 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));
    }
}

4|0HTTP 压缩

当使用 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());
        }
    }
}


5|0HTTPS

启用 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());
        } 
    }
}



6|0WebSocket

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
        }
    }
}



相关推荐

Jquery 详细用法

1、jQuery介绍(1)jQuery是什么?是一个js框架,其主要思想是利用jQuery提供的选择器查找要操作的节点,然后将找到的节点封装成一个jQuery对象。封装成jQuery对象的目的有...

前端开发79条知识点汇总

1.css禁用鼠标事件2.get/post的理解和他们之间的区别http超文本传输协议(HTTP)的设计目的是保证客户机与服务器之间的通信。HTTP的工作方式是客户机与服务器之间的请求-应答协议。...

js基础面试题92-130道题目

92.说说你对作用域链的理解参考答案:作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。...

Web前端必备基础知识点,百万网友:牛逼

1、Web中的常见攻击方式1.SQL注入------常见的安全性问题。解决方案:前端页面需要校验用户的输入数据(限制用户输入的类型、范围、格式、长度),不能只靠后端去校验用户数据。一来可以提高后端处理...

事件——《JS高级程序设计》

一、事件流1.事件流描述的是从页面中接收事件的顺序2.事件冒泡(eventbubble):事件从开始时由最具体的元素(就是嵌套最深的那个节点)开始,逐级向上传播到较为不具体的节点(就是Docu...

前端开发中79条不可忽视的知识点汇总

过往一些不足的地方,通过博客,好好总结一下。1.css禁用鼠标事件...

Chrome 开发工具之Network

经常会听到比如"为什么我的js代码没执行啊?","我明明发送了请求,为什么反应?","我这个网站怎么加载的这么慢?"这类的问题,那么问题既然存在,就需要去解决它,需要解决它,首先我们得找对导致问题的原...

轻量级 React.js 虚拟美化滚动条组件RScroll

前几天有给大家分享一个Vue自定义滚动条组件VScroll。今天再分享一个最新开发的ReactPC端模拟滚动条组件RScroll。...

一文解读JavaScript事件对象和表单对象

前言相信做网站对JavaScript再熟悉不过了,它是一门脚本语言,不同于Python的是,它是一门浏览器脚本语言,而Python则是服务器脚本语言,我们不光要会Python,还要会JavaScrip...

Python函数参数黑科技:*args与**kwargs深度解析

90%的Python程序员不知道,可变参数设计竟能决定函数的灵活性和扩展性!掌握这些技巧,让你的函数适应任何场景!一、函数参数设计的三大进阶技巧...

深入理解Python3密码学:详解PyCrypto库加密、解密与数字签名

在现代计算领域,信息安全逐渐成为焦点话题。密码学,作为信息保护的关键技术之一,允许我们加密(保密)和解密(解密)数据。...

阿里Nacos惊爆安全漏洞,火速升级!(附修复建议)

前言好,我是threedr3am,我发现nacos最新版本1.4.1对于User-Agent绕过安全漏洞的serverIdentitykey-value修复机制,依然存在绕过问题,在nacos开启了...

Python模块:zoneinfo时区支持详解

一、知识导图二、知识讲解(一)zoneinfo模块概述...

Golang开发的一些注意事项(一)

1.channel关闭后读的问题当channel关闭之后再去读取它,虽然不会引发panic,但会直接得到零值,而且ok的值为false。packagemainimport"...

Python鼠标与键盘自动化指南:从入门到进阶——键盘篇

`pynput`是一个用于控制和监控鼠标和键盘的Python库...

取消回复欢迎 发表评论: