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

简介: 探讨Netty获取并检查Websocket握手请求的两种方式

在使用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请求中提取关心的数据


  1. 安全检查


  1. 将结果和其他数据绑定在Channel


  1. 触发安全检查完毕自定义事件


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传递事件数据,降低各模块之间代码耦合。

目录
打赏
0
0
0
0
23
分享
相关文章
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
1967 1
netty整合websocket(完美教程)
本文是一篇完整的Netty整合WebSocket的教程,介绍了WebSocket的基本概念、使用Netty构建WebSocket服务器的步骤和代码示例,以及如何创建前端WebSocket客户端进行通信的示例。
876 2
netty整合websocket(完美教程)
Spring Boot+Netty+Websocket实现后台向前端推送信息
学过 Netty 的都知道,Netty 对 NIO 进行了很好的封装,简单的 API,庞大的开源社区。深受广大程序员喜爱。基于此本文分享一下基础的 netty 使用。实战制作一个 Netty + websocket 的消息推送小栗子。
Springboot+Netty+WebSocket搭建简单的消息通知
这样,你就建立了一个简单的消息通知系统,使用Spring Boot、Netty和WebSocket实现实时消息传递。你可以根据具体需求扩展和改进该系统。
250 1
Netty4 websocket 开启服务端并设置IP和端口号
Netty4 websocket 开启服务端并设置IP和端口号
369 0
良心分享:基于Java+SpringBoot+Netty+WebSocket+Uniapp轻松搭建在线互动问答程序
本文将详细介绍如何基于你自己的开源项目搭建一个在线互动问答程序,包括微信小程序和H5网页版。 该项目服务端主要使用了Java + Spring Boot + Netty + WebSocket等技术栈,聊天客户端使用的是UniApp来轻松搭建微信小程序和H5网页端。
169 1
如何使用SpringBoot和Netty实现一个WebSocket服务器,并配合Vue前端实现聊天功能?
如何使用SpringBoot和Netty实现一个WebSocket服务器,并配合Vue前端实现聊天功能?
396 0
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
13688 1
Netty源码—4.客户端接入流程
本文主要介绍了关于Netty客户端连接接入问题整理、Reactor线程模型和服务端启动流程、Netty新连接接入的整体处理逻辑、新连接接入之检测新连接、新连接接入之创建NioSocketChannel、新连接接入之绑定NioEventLoop线程、新连接接入之注册Selector和注册读事件、注册Reactor线程总结、新连接接入总结
AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等

登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问