fiber需借助github.com/gofiber/contrib/websocket扩展实现websocket;核心是路由拦截、升级校验(检查connection、upgrade、sec-websocket-key/version)、生命周期管理;必须用websocket.new()包裹handler,不可直接使用app.get。

直接上结论:Fiber 本身不内置 WebSocket 服务器逻辑,必须用 github.com/gofiber/contrib/websocket 这个官方维护的扩展包;配置核心就三件事——路由拦截、连接升级校验、连接生命周期管理。
WebSocket 路由必须用 websocket.New() 包裹 handler
这是最容易漏掉的一步。Fiber 的 app.Get("/ws", ...) 只是普通 HTTP 路由,不自动处理 WebSocket 升级。你不能把裸函数传进去,否则客户端会卡在 400 或 426 错误。
正确写法是:
<pre class="brush:php;toolbar:false;">import "github.com/gofiber/contrib/websocket"
app.Get("/ws", websocket.New(func(c *websocket.Conn) {
// 这里才是真正的 WebSocket 连接上下文
defer c.Close()
for {
mt, msg, err := c.ReadMessage()
if err != nil {
break
}
c.WriteMessage(mt, msg) // 回显
}
}))
注意:websocket.New()
fiber.Handler,它内部做了两件事:1)调用 c.IsWebSocket() 做握手校验;2)接管连接,把底层 net.Conn 交给 gorilla/websocket 兼容的 *websocket.Conn 实例。
c.IsWebSocket() 的校验逻辑决定是否放行
这个方法不是装饰器,它是实际生效的握手检测。它检查四个关键请求头:
-
Connection头是否包含"upgrade"(不区分大小写) -
Upgrade头是否严格等于"websocket" -
Sec-WebSocket-Key是否存在(客户端必带) -
Sec-WebSocket-Version是否为"13"(当前唯一合法值)
如果任意一项失败,c.IsWebSocket() 返回 false,你得自己返回 fiber.StatusUpgradeRequired 或 fiber.StatusBadRequest。别指望 websocket.New() 替你兜底——它只在检测通过后才执行你的 handler。
中间件里不能直接操作 c.Locals 或 c.Set 传参给 WebSocket handler
因为 websocket.New() 创建的 handler 是独立生命周期的,它不共享普通 HTTP 中间件里的 c.Locals 数据。比如你写了:
<pre class="brush:php;toolbar:false;">app.Use("/ws", func(c *fiber.Ctx) error {
c.Locals("user_id", 123)
return c.Next()
})
app.Get("/ws", websocket.New(...))
这段代码中,c.Locals("user_id")
websocket.New() 启动的是全新连接上下文,和前面的 fiber.Ctx 没关系。
正确做法只有两个:
- 在 WebSocket 握手阶段,从
c.Query("token")或c.Get("Authorization")解析认证信息,再手动传入(例如用闭包捕获变量) - 或者,在
websocket.New()内部先读一次c.Fasthttp.Request的原始 header/query,再做校验和解析
别试图绕过握手直接复用 HTTP 上下文,Fiber 的 Ctx 对象是池化复用的,WebSocket 连接持续期间它早被归还回池了。
并发读写必须加锁或用 goroutine 分离
*websocket.Conn 不是线程安全的。如果你在 handler 里同时启动读循环和写循环(比如广播消息),直接调 c.WriteMessage() 可能 panic 报 "concurrent write to websocket connection"。
常见解法:
- 用一个
chan []byte做消息队列,单个 goroutine 专门消费并WriteMessage - 对
c加sync.Mutex,但会降低吞吐,仅适合低频场景 - 用
c.SetWriteDeadline()配合非阻塞写,避免 goroutine 积压
记住:WebSocket 连接一旦建立,就脱离了 Fiber 的请求生命周期,所有资源(goroutine、channel、timer)都得你自己 cleanup,defer c.Close() 是底线。
最常被忽略的点:WebSocket 连接断开时,ReadMessage() 返回的 err 可能是 *websocket.CloseError、net.OpError 或 io.EOF,不同错误类型要区别对待——比如 CloseError.Code 是 1000 才算正常关闭,其他码可能需要记录日志或触发重连逻辑。











