非HTTPS环境SpringBoot实现WebSocket连接404异常问题
问题根因说明
你遇到的两个报错原因非常明确:
ws://连接返回404:是因为SpringBoot内嵌Tomcat默认不会扫描@ServerEndpoint注解,你写的WebSocket端点根本没被注册到服务里,访问自然找不到路径wss://连接报方法名非法字符:是因为你本地运行的是普通HTTP服务,没有配置SSL证书,wss是加密WebSocket协议,对应HTTPS端口,你拿wss连HTTP端口时,Tomcat把TLS握手的二进制数据包当成普通HTTP请求解析,必然抛出这个异常,本地测试直接用ws即可,等后续部署HTTPS时再配置wss。
排查解决步骤
1. 补全SpringBoot WebSocket核心配置
在SpringBoot能扫描到的包路径下(和启动类同层级/子层级的config包下)新增配置类,注册ServerEndpoint扫描器:
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(); } }
注意:如果后续你把项目打war包部署到外部Tomcat,这个Bean可以去掉,外部Tomcat会自动扫描
@ServerEndpoint注解,但本地用内嵌Tomcat测试必须加这个配置,否则端点不会生效。
2. 检查路径拦截与上下文配置
- 如果你项目用了SpringSecurity、自定义Filter/Interceptor,必须把
/events/**路径加到放行规则里,WebSocket第一次握手是HTTP升级请求,被拦截器拦截会直接导致连接失败 - 检查
application.yml/application.properties里有没有配置server.servlet.context-path,如果配置了比如/api作为上下文路径,前端连接地址要对应改成ws://localhost:8080/api/events/1,漏写上下文路径也会报404。
3. 处理跨域问题
如果你的前端页面是跑在其他端口(比如本地前端调试用3000/5500端口),需要给WebSocket端点加跨域允许配置,修改@ServerEndpoint注解,新增跨域配置器:
@ServerEndpoint( value="/events/{boardid}", decoders = MessageDecoder.class, encoders = MessageEncoder.class, configurator = WebSocketCorsConfig.class )
新增跨域配置类:
import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; import java.util.Collections; public class WebSocketCorsConfig extends ServerEndpointConfig.Configurator { @Override public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) { // 本地测试允许所有跨域,生产环境替换为实际业务域名即可 response.getHeaders().put("Access-Control-Allow-Origin", Collections.singletonList("*")); super.modifyHandshake(sec, request, response); } }
4. 检查编解码器实现
确认你自定义的MessageDecoder、MessageEncoder符合规范:
- 解码器实现
Decoder.Text<Message>接口,重写decode方法,能正确将收到的字符串转为Message对象 - 编码器实现
Encoder.Text<Message>接口,重写encode方法,能正确将Message对象转为可传输的字符串 - 两个类都必须是public修饰,提供public无参构造方法,否则端点初始化会失败。
现有代码bug修复
你贴的业务代码里有两个小问题,顺手修了避免后续出问题:
@OnMessage方法里没有把前端传来的消息内容设置到广播的Message对象里,现在广播的是空消息,客户端收不到实际发送的内容,修改为:
@OnMessage public void onMessage(Session session, String message) throws IOException, EncodeException { Message msg = new Message(); msg.setFrom(users.get(session.getId())); msg.setContent(message); // 补全消息内容赋值 broadcast(msg); System.out.println("["+session.getId()+"]: "+message); }
@OnClose方法里没有清理users里的会话映射,长时间运行会内存泄漏,同时日志打印内容缺失,修改为:
@OnClose public void onClose(Session session) throws IOException, EncodeException { socketEndpoint.remove(this); String boardid = users.remove(session.getId()); // 移除失效的用户映射,避免内存泄漏 Message message = new Message(); message.setFrom(boardid); message.setContent("Disconnected!"); broadcast(message); System.out.println("Connection closed with: "+session.getId()); }
排查优先级建议
- 第一步先加
ServerEndpointExporter配置类,重启服务后测试ws连接,90%的概率404问题会直接解决 - 如果还是404,再检查拦截器放通、上下文路径配置
- 能连通但收不到消息时,再检查编解码器和上面提到的消息赋值问题
- wss相关的问题本地不用处理,等后续部署HTTPS环境时,给服务配置SSL证书,前端把连接地址换成wss即可。
内容的提问来源于stack exchange,提问作者Tyris
相关产品推荐
相关产品推荐

