在使用Netty开发Websocket服务时,通常需要解析来自客户端请求的URL、Headers等等相关内容,并做相关检查或处理。本文将讨论两种实现方法。

方法一:基于HandshakeComplete自定义事件

特点:使用简单、校验在握手成功之后、失败信息可以通过Websocket发送回客户端。

1.1 从netty源码出发

一般地,我们将netty内置的WebSocketServerProtocolHandler作为Websocket协议的主要处理器。通过研究其代码我们了解到在本处理器被添加到PiplinehandlerAdded方法将会被调用。此方法经过简单的检查后将WebSocketHandshakeHandler添加到了本处理器之前,用于处理握手相关业务。

我们都知道Websocket协议在握手时是通过HTTP(S)协议进行的,那么这个WebSocketHandshakeHandler应该就是处理HTTP相关的数据的吧?

下方代码经过精简,放心阅读

package io.netty.handler.codec.http.websocketx;

public class WebSocketServerProtocolHandler extends WebSocketProtocolHandler {

    @Override
public void handlerAdded(ChannelHandlerContext ctx) {
ChannelPipeline cp = ctx.pipeline();
if (cp.get(WebSocketServerProtocolHandshakeHandler.class) == null) {
// Add the WebSocketHandshakeHandler before this one.
cp.addBefore(ctx.name(), WebSocketServerProtocolHandshakeHandler.class.getName(),
new WebSocketServerProtocolHandshakeHandler(serverConfig));
}
//...
}
}

我们来看看WebSocketServerProtocolHandshakeHandler都做了什么操作。

channelRead方法会尝试接收一个FullHttpRequest对象,表示来自客户端的HTTP请求,随后服务器将会进行握手相关操作,此处省略了握手大部分代码,感兴趣的同学可以自行阅读。

可以注意到,在确认握手成功后,channelRead将会调用两次fireUserEventTriggered,此方法将会触发自定义事件。其他(在此处理器之后)的处理器会触发userEventTriggered方法。其中一个方法传入了WebSocketServerProtocolHandler对象,此对象保存了HTTP请求相关信息。那么解决方案逐渐浮出水面,通过监听自定义事件即可实现检查握手的HTTP请求。

package io.netty.handler.codec.http.websocketx;

/**
* Handles the HTTP handshake (the HTTP Upgrade request) for {@link WebSocketServerProtocolHandler}.
*/
class WebSocketServerProtocolHandshakeHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
final FullHttpRequest req = (FullHttpRequest) msg;
if (isNotWebSocketPath(req)) {
ctx.fireChannelRead(msg);
return;
} try { //... if (!future.isSuccess()) { } else {
localHandshakePromise.trySuccess();
// Kept for compatibility
ctx.fireUserEventTriggered(
WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE);
ctx.fireUserEventTriggered(
new WebSocketServerProtocolHandler.HandshakeComplete(
req.uri(), req.headers(), handshaker.selectedSubprotocol()));
}
} finally {
req.release();
}
}
}

1.2 解决方案

下面的代码展示了如何监听自定义事件。通过抛出异常可以终止链接,同时可以利用ctx向客户端以Websocket协议返回错误信息。因为此时握手已经完成,所以虽然这种方案简单的过分,但是效率并不高,耗费服务端资源(都握手了又给人家踢了QAQ)。

private final class ServerHandler extends SimpleChannelInboundHandler<DeviceDataPacket> {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
// 在此处获取URL、Headers等信息并做校验,通过throw异常来中断链接。
}
super.userEventTriggered(ctx, evt);
}
}

1.3 ChannelInitializer实现

附上Channel初始化代码作为参考。

private final class ServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast("http-codec", new HttpServerCodec())
.addLast("chunked-write", new ChunkedWriteHandler())
.addLast("http-aggregator", new HttpObjectAggregator(8192))
.addLast("log-handler", new LoggingHandler(LogLevel.WARN))
.addLast("ws-server-handler", new WebSocketServerProtocolHandler(endpointUri.getPath()))
.addLast("server-handler", new ServerHandler());
}
}

方法二:基于新增安全检查处理器

特点:使用相对复杂、校验在握手成功之前、失败信息可以通过HTTP返回客户端。

2.1 解决方案

编写一个入站处理器,接收FullHttpMessage消息,在Websocket处理器之前检测拦截请求信息。下面的例子主要做了四件事情:

  1. 从HTTP请求中提取关心的数据
  2. 安全检查
  3. 将结果和其他数据绑定在Channel
  4. 触发安全检查完毕自定义事件
public class SecurityServerHandler extends ChannelInboundHandlerAdapter {
private static final ObjectMapper json = new ObjectMapper(); public static final AttributeKey<SecurityCheckComplete> SECURITY_CHECK_COMPLETE_ATTRIBUTE_KEY =
AttributeKey.valueOf("SECURITY_CHECK_COMPLETE_ATTRIBUTE_KEY"); @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof FullHttpMessage){
//extracts device information headers
HttpHeaders headers = ((FullHttpMessage) msg).headers();
String uuid = Objects.requireNonNull(headers.get("device-connection-uuid"));
String devDescJson = Objects.requireNonNull(headers.get("device-description"));
//deserialize device description
DeviceDescription devDesc = json.readValue(devDescJson, DeviceDescriptionWithCertificate.class);
//check ...... //
SecurityCheckComplete complete = new SecurityCheckComplete(uuid, devDesc);
ctx.channel().attr(SECURITY_CHECK_COMPLETE_ATTRIBUTE_KEY).set(complete);
ctx.fireUserEventTriggered(complete);
}
//other protocols
super.channelRead(ctx, msg);
}
@Getter
@AllArgsConstructor
public static final class SecurityCheckComplete {
private String connectionUUID;
private DeviceDescription deviceDescription;
}
}

在业务逻辑处理器中,可以通过组合自定义的安全检查事件和Websocket握手完成事件。例如,在安全检查后进行下一步自定义业务检查,在握手完成后发送自定义内容等等,就看各位同学自由发挥了。

 private final class ServerHandler extends SimpleChannelInboundHandler<DeviceDataPacket> {

    public final AttributeKey<DeviceConnection>
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof SecurityCheckComplete){
log.info("Security check has passed");
SecurityCheckComplete complete = (SecurityCheckComplete) evt;
listener.beforeConnect(complete.getConnectionUUID(), complete.getDeviceDescription());
}
else if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
log.info("Handshake has completed");
SecurityCheckComplete complete = ctx.channel().attr(SecurityServerHandler.SECURITY_CHECK_COMPLETE_ATTRIBUTE_KEY).get();
DeviceDataServer.this.listener.postConnect(complete.getConnectionUUID(),
new DeviceConnection(ctx.channel(), complete.getDeviceDescription()));
}
super.userEventTriggered(ctx, evt);
}
}

2.2 ChannelInitializer实现

附上Channel初始化代码作为参考。

private final class ServerInitializer extends ChannelInitializer<SocketChannel> {

    @Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast("http-codec", new HttpServerCodec())
.addLast("chunked-write", new ChunkedWriteHandler())
.addLast("http-aggregator", new HttpObjectAggregator(8192))
.addLast("log-handler", new LoggingHandler(LogLevel.WARN))
.addLast("security-handler", new SecurityServerHandler())
.addLast("ws-server-handler", new WebSocketServerProtocolHandler(endpointUri.getPath()))
.addLast("packet-codec", new DataPacketCodec())
.addLast("server-handler", new ServerHandler());
}
}

总结

上述两种方式分别在握手完成后和握手之前拦截检查;实现复杂度和性能略有不同,可以通过具体业务需求选择合适的方法。

Netty增强了责任链模式,使用userEvent传递自定义事件使得各个处理器之间减少耦合,更专注于业务。但是、相比于流动于各个处理器之间的"主线"数据来说,userEvent传递的"支线"数据往往不受关注。通过阅读Netty内置的各种处理器源码,探索其产生的事件,同时在开发过程中加以善用,可以减少冗余代码。另外在开发自定义的业务逻辑时,应该积极利用userEvent传递事件数据,降低各模块之间代码耦合。

探讨Netty获取并检查Websocket握手请求的两种方式的更多相关文章

  1. 第二节:SSL证书的申请、配置(IIS通用)及跳转Https请求的两种方式

    一. 相关概念介绍 1. SSL证书服务 SSL证书服务由"服务商"联合多家国内外数字证书管理和颁发的权威机构.在xx云平台上直接提供的服务器数字证书.您可以在阿里云.腾讯云等平台 ...

  2. C#中Post请求的两种方式发送参数链和Body的

    POST请求 有两种方式 一种是组装key=value这种参数对的方式 一种是直接把一个字符串发送过去 作为body的方式 我们在postman中可以看到 sfdsafd sdfsdfds publi ...

  3. 解决 SharePoint 2010 拒绝访问爬网内容源错误的小技巧(禁用环回请求的两种方式)

    这里有一条解决在SharePoint 2010搜索爬网时遇到的“拒绝访问错误”的小技巧. 首先要检查默认内容访问帐户是否具有相应的访问权限,或者添加一条相应的爬网规则.如果目标资源库是一个ShareP ...

  4. Springboot 创建的maven获取resource资源下的文件的两种方式

    Springboot 创建的maven项目 打包后获取resource下的资源文件的两种方式: 资源目录: resources/config/wordFileXml/wordFileRecord.xm ...

  5. JAVA发送http GET/POST请求的两种方式+JAVA http 请求手动配置代理

    java发送http get请求,有两种方式. 第一种用URLConnection: public static String get(String url) throws IOException { ...

  6. @Autowired获取配置文件中被注入实例的两种方式

    一.说明 二.那么在JavaBean中如何通过@Autowired获取该实例呢?有两种方式: 1.直接获取 @RunWith(SpringJUnit4ClassRunner.class) @Conte ...

  7. java发送http get请求的两种方式

    长话短说,废话不说 一.第一种方式,通过HttpClient方式,代码如下: public static String httpGet(String url, String charset) thro ...

  8. httpURLConnection-网络请求的两种方式-get请求和post请求

    GET请求 /** * 从网络获取json数据,(String byte[}) * @param path * @return */ public static String getJsonByInt ...

  9. HTTP请求的两种方式get和post的区别

    1,get从服务器获取数据:post向服务器发送数据: 2,安全性,get请求的数据会显示在地址栏中,post请求的数据放在http协议的消息体: 3,从提交数据的大小看,http协议本身没有限制数据 ...

随机推荐

  1. 奇怪DP之步步为零

    题目 思路 很明显的dp就是不会跑啊,所以最后dfs救了一下场,不出所料,最后果然T了,现在说一下正解. 为什么说是奇怪dp呢,这道题的dp数组是布尔型的,f[i][j][k]代表在到第i行第j列之前 ...

  2. P2607[ZJOI2008] 骑士 题解

    题目 Z 国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英.他们劫富济贫,惩恶扬善,受到社会各界的赞扬. 最近发生了一件可怕的事情,邪恶的 Y 国发动了一场针对 Z 国的侵略战争.战火绵延五 ...

  3. 三分钟了解 Python3 的异步 Web 框架 FastAPI

    快速编码,功能完善.从启动到部署,实例详解异步 py3 框架选择 FastAPI 的原因. FastAPI 介绍 FastAPI 与其它 Python-Web 框架的区别 在 FastAPI 之前,P ...

  4. Rust String(官方文档翻译)

    学习Rust,官方文档全英文,查询不太方便,索性直接翻译完,方便查询使用.有需要自由转载,本人英文水平有限,文档是在谷歌翻译的基础上加个人理解完成,不敢保证正确.文档翻译错误的地方欢迎指出: 原文地址 ...

  5. 数据可视化实例(五): 气泡图(matplotlib,pandas)

    https://datawhalechina.github.io/pms50/#/chapter2/chapter2 关联 (Correlation) 关联图表用于可视化2个或更多变量之间的关系. 也 ...

  6. 前端02 /HTML标签

    前端02 /HTML标签 目录 前端02 /HTML标签 1.特殊字符 2.标签分类 标签嵌套 1.块级标签(行内标签) 1.1div标签(块标签) 1.2p标签(块标签) 2.内联标签 2.1 sp ...

  7. Java并发编程实践

    最近阅读了<Java并发编程实践>这本书,总结了一下几个相关的知识点. 线程安全 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任 ...

  8. Tomcat的基本使用及相关知识的概述(超详细版)

    絮絮叨叨: 在深入了解Tomcat源码之前,本来是想亲自写一篇Tomcat的基本使用教程的,在网上兜兜转转发现了这篇博客:https://blog.csdn.net/weixin_40396459/a ...

  9. .Net Core 读取文件中文乱码

    首先,要知道.Net Core和.Net Framework两个环境下很多东西是不同的,接下来要说的这个就是其中一个. Encoding.Default,在  .NET Framework 中,Def ...

  10. SpringCloud全家桶介绍及手绘架构

    文章目录 概述 一.业务场景介绍 二.Spring Cloud核心组件:Eureka 三.Spring Cloud核心组件:Feign 四.Spring Cloud核心组件:Ribbon 五.Spri ...