我们使用spring-boot接入websocket有三种方式:使用@EnableWebSocket
、@EnableWebSocketMessageBroker
以及@ServerEndpoint
,本文主要介绍使用@ServerEndpoint
方式的流程以及碰到的问题解决
接入方式
添加依赖
确保spring-boot-starter-websocket
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
定义@ServerEndpoint类
这个是核心类方法,可以在这里定义生命周期方法(比如onOpen
,onMessage
等)
//定义成spring bean
@Component
@Slf4j
//定义websocket路径,这里的configurator后面再讲解
@ServerEndpoint(value = "/wss/conn/{cookieValue}",configurator = WebSocketConfigurator.class)
public class WebSocketServer {
//业务的spring bean注入
private static TestWrapper testWrapper;
//静态set注入spring bean
@Autowired
public void setTestWrapper(TestWrapper testWrapper) {
WebSocketServer.testWrapper = testWrapper;
}
@OnOpen
public void onOpen(Session session) {
System.out.println("连接建立: " + session.getId());
//有效性判断,鉴权等
}
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到消息: " + message);
//收到发送方消息
}
@OnClose
public void onClose(Session session) {
System.out.println("连接关闭: " + session.getId());
}
}
注册 ServerEndpointExporter
(关键!)
Spring Boot 默认不扫描@ServerEndpoint
,需通过ServerEndpointExporter
将其注册到 WebSocket 容器
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter(); // 自动注册 @ServerEndpoint
}
}
整个配置完成服务启动后(注意websocket的端口可以和自己的spring-web项目用同一个端口,spring会识别是http请求还是websocket请求,自动作对应的映射转发),正常情况能够收发消息
在WebSocketServer如何引用业务spring bean?
我们不能用类似的变量注入@autowire
方式来注入我们的业务spring bean,这是因为
@ServerEndpoint
实例不是由 Spring 容器管理@ServerEndpoint
标注的类是由 Java EE WebSocket 容器(Tomcat/Undertow)实例化的,不是由 Spring 容器实例化的。
- Spring 容器不会自动为它注入
@Autowired
的依赖(即使你加了@Component
,也没用)。- 虽然加了
@Component
,但 WebSocket 实例的生命周期和 Spring Bean 不一致
Spring 只会管理自己创建的 Bean,WebSocket 容器每次新连接都会 new 一个WebSocketServer
实例,Spring 不会自动注入依赖
- 虽然加了
所以,我们可以使用用静态 set 注入,注意:字段要 static,set 方法注入 static 字段。这样 Spring 容器启动时会把 Bean 注入到静态变量,WebSocketServer 的每个实例都能用,类似上述代码中的TestWrapper注入
如何在WebSocketServer建立链接onOpen 获取前端cookie?
我们有的时候会想在websocket建立链接时校验权限,比如校验登录态,这个时候便需要获取前端传递的cookie,WebSocket 的 Session.getUserProperties()
默认并不会自动包含 cookie 信息。对于使用@ServerEndpoint
注解的方式
- 首先创建一个 ServerEndpointConfig.Configurator 的子类:
public class WebSocketConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
// 获取请求头中的 cookie
List<String> cookies = request.getHeaders().get("Cookie");
if (cookies != null && !cookies.isEmpty()) {
String cookieStr = cookies.get(0);
Map<String, String> cookieMap = new HashMap<>();
// 解析 cookie 字符串
String[] cookieArray = cookieStr.split(";");
for (String cookie : cookieArray) {
String[] parts = cookie.trim().split("=");
if (parts.length == 2) {
cookieMap.put(parts[0].trim(), parts[1].trim());
}
}
// 将 cookie 信息存储到 ServerEndpointConfig 的 UserProperties 中
sec.getUserProperties().put("cookies", cookieMap);
}
super.modifyHandshake(sec, request, response);
}
}
- 在WebSocketServer启动类中配置这个Configurator
@Component
@Slf4j
@ServerEndpoint(value = "/wss/conn/{cookieValue}",configurator = WebSocketConfigurator.class)
public class WebSocketServer {
- 在
onOpen
中获取cookie,进行登录态校验
// 从 Session 的 UserProperties 获取 Cookie
Map<String, String> cookieMap = (Map<String, String>) sessionParam.getUserProperties().get("cookies");
if (MapUtils.isEmpty(cookieMap)) {
log.warn("WebSocketServer.onOpen no cookies");
return;
}
String cookie = cookieMap.get("LOGIN_COOKIE_NAME");
服务启动后访问提示Unexpected server response
我们启动服务后访问可能会出现,Unexpected server response: 200等错误,常见错误和原因汇总如下
问题表现 | 原因 | 解决 |
---|---|---|
Unexpected server response: 200 | 路径错误或未注册 WebSocket | 检查 @ServerEndpoint 路径和 ServerEndpointExporter |
Unexpected response code: 404 | 后端路径不存在 | 确保路径与前端一致 |
Unexpected response code: 500 | 后端代码异常 | 查看服务器日志排查异常 |
出现诸如200、404等错误,一个点首先确认你的路径是不是对的,建议先用一个最简单的,比如 /wss/test,然后服务启动再测试看,我就是因为路径问题排查了很久,用的路径是一个公共前缀,有很多业务的interceptor和filter都会处理这个前缀开头的路径,导致报错了,没有转到WebSocketServer处理
如何测试验证websocket是否正常
这里推荐一个工具postman,升级到最新版,支持websocket的请求访问,可以调试你的websocket服务是否正常