在使用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 {@Overridepublic 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 {@Overridepublic 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 compatibilityctx.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> {@Overridepublic 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> {@Overrideprotected 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");@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if(msg instanceof FullHttpMessage){//extracts device information headersHttpHeaders headers = ((FullHttpMessage) msg).headers();String uuid = Objects.requireNonNull(headers.get("device-connection-uuid"));String devDescJson = Objects.requireNonNull(headers.get("device-description"));//deserialize device descriptionDeviceDescription 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 protocolssuper.channelRead(ctx, msg);}@Getter@AllArgsConstructorpublic static final class SecurityCheckComplete {private String connectionUUID;private DeviceDescription deviceDescription;}
}

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

 private final class ServerHandler extends SimpleChannelInboundHandler<DeviceDataPacket> {public final AttributeKey<DeviceConnection> @Overridepublic 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> {@Overrideprotected 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传递事件数据,降低各模块之间代码耦合。

琐碎时间想看一些技术文章,可以去公众号菜单栏翻一翻我分类好的内容,应该对部分童鞋有帮助。同时看的过程中发现问题欢迎留言指出,不胜感谢~。另外,有想多了解哪些方面内容的可以留言(什么时候,哪篇文章下留言都行),附菜单栏截图(PS:很多人不知道公众号菜单栏是什么)

END

我知道你 “在看”

探讨Netty获取并检查Websocket握手请求的两种方式相关推荐

  1. JavaScript实现同步Ajax请求的两种方式

    JavaScript的Ajax请求默认是异步的,有以下两种方式能让Ajax请求变成同步 方式一 使用ES7的Async和Await async function main(){const env = ...

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

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

  3. Vue3(撩课学院)笔记09-axios简介,发起get请求的两种方式,发起带参的get及post请求,发起并发请求,并发请求结果将数组展开,axios全局配置,axios配置及封装,请求和响应拦截

    1.axios简介 axios是基于promise可以用于浏览器和node.js的网络请求库,在服务器端使用原生node.js,在浏览气短使用ajax(即XMLHttpRequests) 2.axio ...

  4. JSP同步请求和html+ajax异步请求的两种方式

    war包:包括所有的项目资源,只要从浏览器发起的都是属于请求,然后把资源响应给浏览器,解析显示出来. 方式一:HTML+ajax(跳转静态html也是属于请求响应,把整个页面响应给浏览器.) html ...

  5. .Net Core下发送WebRequest请求的两种方式

    1.使用RestSharp.NetCore 2.使用WebApi请求方式 转载于:https://www.cnblogs.com/mailaidedt/p/6525501.html

  6. php 进行http请求,php模拟http请求的两种方式

    方法一:CURL $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_CONNECTTIM ...

  7. (七)Charles篡改请求(两种方式---Breakpoint和Rewrite)

    一.篡改请求之Breakpoint 1.抓取到的原请求 2.对上述请求设置breakpoint 3.设置Breakpoint 4.刷新 重新发送请求 5.运行到已经被设置断点的请求时,会被拦截住,此时 ...

  8. springboot 添加允许跨域_springboot设置cors跨域请求的两种方式

    1.第一种: public class CorsFilter extends OncePerRequestFilter { static final String ORIGIN = "Ori ...

  9. python获取文件夹下所有文件的两种方式

    需求:给你一个指定的文件夹路径,让你得到该路径下的所有文件,在这里我是用递归去实现的. 方法1:递归操作,代码可读性强,但是效率太慢 直接上代码: import osdef list_dir(text ...

最新文章

  1. 工具分享-自动生成正则表达式的各种代码,附带正则表达式介绍
  2. Facebook新推出AL语言,意在简化程序静态分析
  3. easyui关机图标_如何在Windows 10中创建关机图标
  4. 命令不识别_互助问答138期:GMM命令代码中如何识别年份国家及异方差检验问题...
  5. 冒泡排序 实现数据的由大到小排序
  6. CVPR 2019 CLIC 图像压缩挑战赛冠军方案解读
  7. 从架构设计理念到集群部署,全面认识KubeEdge
  8. 华为P30Pro开箱照曝光 四摄模组+徕卡镜头确认
  9. python的urllib2包基本使用方法
  10. IBM ThinkPad错误代码列表
  11. python清空列表_Python 内存分配时的小秘密
  12. 我的ActiveRecord学习之路(一)
  13. 量子力学计算机原理,量子力学的基本原理
  14. 谷歌浏览器使用charles抓包localhost
  15. 区分计算机和服务器的内存条,AMD专用内存是什么意思 AMD专用内存和普通内存条的区别及真相...
  16. Rust程序设计语言-使用包、Crate和模块管理不断增长的项目
  17. COMPA: Detecting Compromised Accounts on Social Networks 论文分析
  18. 教育培训行业使用CRM管理系统有什么好处
  19. 轻便简洁的电脑录音软件,免费收藏!
  20. python分析谷歌浏览器的历史记录

热门文章

  1. 被指抄袭后 新浪微博APP绿洲更换Logo 重新上架
  2. 苹果最新专利曝光:苹果可能正研发可折叠iPhone
  3. 拳王虚拟项目公社:如何如何打造虚拟自动盈利系统,用虚拟资源实现被动收入?
  4. RSS原理和实现[转]
  5. python二维向量运算模拟_Python数学基础之向量定义与向量运算(附代码)
  6. url中找出IP地址
  7. windows虚拟声卡直播_【韭菜爱镰刀】高性价比的录音/直播设备推荐
  8. 使用Tslib在触摸屏上显示汉字
  9. cad文字提取到excel_别怕!CAD表格与EXCEL之间的转化,有它就够了
  10. python编程设计_程序设计入门—Python