WebSocket鉴权失败等业务异常应握手完成后发送CloseWebSocketFrame错误帧,状态码用4xxx/5xxx自定义码(如4001),原因文本UTF-8编码,前端通过onclose事件的code和reason获取;禁止握手前断连或抛异常。

WebSocket 连接建立过程中若发生鉴权失败、重复连接、参数校验不通过等业务异常,不应直接抛出 Java 异常(如 RuntimeException),而应主动构造 CloseWebSocketFrame 发送错误帧,让前端在 onerror 或 onclose 中捕获结构化错误信息。关键在于:握手必须完成,再发 Close 帧;状态码需为 4xxx 或 5xxx 范围的自定义码,原因字符串可携带提示文本。
必须先完成 WebSocket 握手
浏览器只有收到 101 Switching Protocols 响应后,才会将连接视为 WebSocket 协议。若未握手就断连,前端只会触发 onerror,但无法拿到具体错误码和原因——因为此时还处于 HTTP 阶段,不是真正的 WebSocket 错误帧。
- 使用
WebSocketServerHandshaker完成握手后再处理业务逻辑 - 禁止在握手前调用
ctx.close()或抛异常中断流程 - 握手成功后,通道已升级,此时发送
CloseWebSocketFrame才会被识别为标准关闭帧
用 CloseWebSocketFrame 封装自定义错误
Netty 中通过 new CloseWebSocketFrame(statusCode, reasonText) 构造错误帧,statusCode 推荐使用 4001、4002 等非标准但语义明确的业务码(避开 1000–1015 的保留码),reasonText 是 UTF-8 字符串,前端可通过 event.code 和 event.reason 获取。
前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。前端设计与 UI/UX 全方位优化专家。覆盖视觉层次、排版系统、色彩理论、响应式布局、交互体验、动画动效、无障碍访问、性能优化八大维度,帮助开发者将普通页面升级为高品质产品级界面。
- 示例:重复连接时发送
new CloseWebSocketFrame(4001, "user already connected") - 务必配合
.addListener(ChannelFutureListener.CLOSE),确保帧发出后立即关闭连接 - 避免使用
writeAndFlush(new TextWebSocketFrame(...))替代——前端onerror不会触发,只能靠onmessage解析,违背设计初衷
前端正确监听关闭原因
浏览器原生 WebSocket 对象的 onclose 回调会接收 CloseEvent,其中包含 code 和 reason 字段;onerror 仅在底层连接异常(如网络中断)时触发,不带业务信息。
立即学习“Java免费学习笔记(深入)”;
- 推荐统一在
socket.onclose = e => { console.log(e.code, e.reason) }中处理业务级拒绝 - 若需兼容旧版或调试,可在服务端额外记录日志,关联
ctx.channel().id()与错误上下文 - 注意:部分低版本浏览器对自定义 code 支持有限,reason 文本始终可靠
Spring WebSocket 场景下的替代方案
若使用 Spring 的 @ServerEndpoint(非 Netty 底层),无法直接发 CloseWebSocketFrame。此时应在 @OnOpen 方法中做校验,失败时调用 session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, "msg")) ——Spring 会将其转为标准关闭帧,前端同样能读取 code 和 reason。
-
CloseReason.CloseCodes提供了有限的标准码(如 4000、4001),也可传入自定义整数 - 不要在
@OnError中“补救”——此时连接已出错,再 close 已无意义 - 确保
session.isOpen()为 true 再调用 close,避免 IllegalStateException

















