应选github.com/gofiber/contrib/websocket,因其封装gorilla/websocket并集成Fiber生命周期,自动处理连接升级、跨域校验、上下文透传;裸用gorilla需手动实现中间件、header解析及用户信息传递,易导致ctx.Locals为空、跨域拦截或鉴权失效。

直接用 github.com/gofiber/contrib/websocket,别碰 c.IsWebSocket() 做升级——Fiber 原生不支持 WebSocket 服务端,c.IsWebSocket() 只校验握手头,后续帧读写、心跳、并发、连接生命周期全得你手写,极易 panic 或丢消息。
为什么不能只靠 c.IsWebSocket() 建立连接
c.IsWebSocket() 只做四件事:检查 Connection: upgrade、Upgrade: websocket、Sec-WebSocket-Key 是否存在、Sec-WebSocket-Version 是否为 13。它不调用 c.Fasthttp.Upgrade(),不创建 *websocket.Conn,也不处理任何后续通信逻辑。
常见错误现象:
- 调用
c.IsWebSocket()返回true后,直接在 handler 里读写c,结果 panic:「context canceled」或静默失败 - 手动调用
c.Fasthttp.Upgrade()后,没设置websocket.Upgrader的CheckOrigin,前端报400 WebSocket handshake error - 以为握手完成就万事大吉,实际连接几秒后自动断开——因为没实现 ping/pong 心跳,代理(如 Nginx)或浏览器主动 kill 了空闲连接
websocket.New() 初始化时必须改的三个配置项
websocket.New() 封装了 gorilla/websocket,自动完成升级并注入 *websocket.Conn,但默认参数对生产环境极不友好:
-
CheckOrigin默认为nil,即拒绝所有跨域请求;开发时前端跑在localhost:5173,必须显式设为func(r *http.Request) bool { return true },否则握手直接返回 403 -
HandshakeTimeout默认 45 秒,但 Nginx 等代理常设为 10–15 秒超时,建议设为10 * time.Second,避免握手被中间件截断 -
MaxMessageSize默认 1MB;若业务要传日志流、二进制附件或大 JSON,需提前调大,否则c.ReadMessage()报错websocket: close 1009 (message too big)
如何把用户身份安全透传进 WebSocket 连接
WebSocket 连接一旦升级,就脱离 HTTP 请求上下文,c.Get("Authorization")、c.Cookies() 等方法全部失效。不能在 websocket.New() 闭包里临时解析 token——header 已不可读。
正确做法是用中间件提前鉴权并存入 c.Locals:
- 在
app.Use("/ws", ...)中解析 JWT 或 Cookie,验证通过后调用c.Locals("user_id", uid)和c.Locals("group_id", gid) - 确保中间件在
websocket.New()之前注册,且仅对 WebSocket 升级请求生效(可用websocket.IsWebSocketUpgrade(c)判断) - 在
websocket.New()的 handler 开头立刻取出c.Locals数据并保存为局部变量,避免在for { c.ReadMessage() }循环里反复访问c或查 DB
广播消息时 panic: send on closed channel 怎么防
典型原因是多个 goroutine 并发操作共享的 map 或 channel,或往已关闭的 send channel 发消息。WebSocket 连接关闭后,如果还有 goroutine 在往该连接的写 channel 推消息,就会 panic。
关键防御点:
- 不要用裸
map[string]*websocket.Conn存连接,改用带读写锁的结构体封装,或直接用sync.Map - 每个连接启动独立的读/写 goroutine;写操作统一走一个带缓冲的
chan []byte,并在连接关闭时 close 该 channel,并在写 goroutine 中用select { case msg := 避免阻塞 - 广播前检查连接状态:
if err := c.WriteControl(websocket.PingMessage, nil, time.Now().Add(10*time.Second)); err != nil { /* 关闭连接 */ },比直接WriteMessage更早发现断连
最易被忽略的是:连接关闭时,不仅要关自己的写 channel,还要从全局连接池中移除该连接——漏这一步,下次广播仍会尝试往已关闭的 *websocket.Conn 写数据。











