Websocket在Java中的实践——握手拦截器

大纲

《Websocket在Java中的实践------最小可行案例》一文中,我们看到如何用最简单的方式实现Websocket通信。本文中,我们将介绍如何在握手前后进行干涉,以定制一些特殊需求。

《Websocket在Java中的实践------最小可行案例》的基础上,我们希望建立"用户"的概念,即不同用户有自己的用户名。用户只能收到别人发的消息,而不能收到自己的消息。

这就要求我们服务可以处理ws://localhost:8080/websocket/{uid}这样的请求。而对于uid不存在或者不合法的场景,就要拒绝连接。

依赖

在pom.xml中新增

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

握手拦截器

在绑定接口时,我们可以通过addInterceptors方法给WebSocketHandlerRegistry指定一个握手拦截器。

java 复制代码
package com.nyctlc.withparam.config;

import java.util.Map;

import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;

import jakarta.servlet.http.HttpSession;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new com.nyctlc.withparam.handler.WebSocketHandler(), "/websocket/{uid}").setAllowedOrigins("*").addInterceptors(handshakeInterceptor());
    }

这个拦截器需要重载两个方法:beforeHandshake和afterHandshake。它们分别在握手前后被调用。

我们的需求要求我们在握手之前获取uid。如果uid不存在或者不合法,就会拒绝连接;如果合法,则将其保存到session的属性字段中。

java 复制代码
    private HandshakeInterceptor handshakeInterceptor() {
        return new HandshakeInterceptor() {
            @Override
            public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
			                            WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
                if (request instanceof ServletServerHttpRequest) {

                    String path = request.getURI().getPath();
                    String prefix = "/websocket/";
                    String uid = path.substring(path.indexOf(prefix) + prefix.length());
                    if (uid.isEmpty()) {
                        return false;
                    }

                    if (uid.contains("/")) {
                        return false;
                    }

                    ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
                    HttpSession session = servletRequest.getServletRequest().getSession();
                    attributes.put("sessionId", session.getId());
                    attributes.put("uid", uid);
                }
                return true;
            }

            @Override
            public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
			                            WebSocketHandler wsHandler, @Nullable Exception exception) {
            }
        };
    }

}

消息处理

这次我们在handleTextMessage方法中,判断接收消息的Session的Attributes中的uid是否和Session集合中的一致。这个uid是在上一个步骤中,通过握手拦截器设置的。

如果一致,说明它们来自同一个用户,则不将该消息发送回自己。

java 复制代码
package com.nyctlc.withparam.handler;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class WebSocketHandler extends TextWebSocketHandler {

    private static final Set<WebSocketSession> sessions = new HashSet<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        for (WebSocketSession webSocketSession : sessions) {
            String uid = session.getAttributes().get("uid").toString();
            if (webSocketSession.isOpen()) {
                String uidInSession = webSocketSession.getAttributes().get("uid").toString();
                if (uid.equals(uidInSession)) {
                    continue;
                }
                try {
                    webSocketSession.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

测试


参考资料

相关推荐
WanderInk4 分钟前
揭秘Java协变返回类型:让你的API少一点强转,多一点优雅
java·后端
paopaokaka_luck15 分钟前
基于SpringBoot+Vue的非遗文化传承管理系统(websocket即时通讯、协同过滤算法、支付宝沙盒支付、可分享链接、功能量非常大)
java·数据库·vue.js·spring boot·后端·spring·小程序
iteye_993917 分钟前
让 3 个线程串行的几种方式
java·linux
YuTaoShao24 分钟前
【LeetCode 热题 100】142. 环形链表 II——快慢指针
java·算法·leetcode·链表
找不到、了1 小时前
分布式理论:CAP、Base理论
java·分布式
天天摸鱼的java工程师1 小时前
2025已过半,Java就业大环境究竟咋样了?
java·后端
人生在勤,不索何获-白大侠1 小时前
day16——Java集合进阶(Collection、List、Set)
java·开发语言
游戏开发爱好者81 小时前
iOS 出海 App 安全加固指南:无源码环境下的 IPA 加固与防破解方法
websocket·网络协议·tcp/ip·http·网络安全·https·udp
Zedthm1 小时前
LeetCode1004. 最大连续1的个数 III
java·算法·leetcode
艺杯羹2 小时前
MyBatis之核心对象与工作流程及SqlSession操作
java·mybatis