beego本身不内置websocket实现,必须用gorilla/websocket手动接管;在get()中调upgrade()会因自动渲染试图写已劫持连接而报模板缺失和response.writeheader错误。

Beego 本身不内置 WebSocket 实现,必须用 github.com/gorilla/websocket 手动接管连接;直接调用 beego.Controller 的 Get() 方法升级协议时,若不关自动渲染,必报 can't find template file 和 response.WriteHeader on hijacked connection 错误。
为什么 Beego 的 Get() 里调 Upgrade() 会崩溃
Beego 默认在 Controller 方法执行完后自动调 Render() —— 它会尝试写 HTTP 响应头和模板内容。但 websocket.Upgrader.Upgrade() 已经“劫持”(hijack)了底层 TCP 连接,HTTP 生命周期实际已结束。此时再写响应头或模板,就会触发两个错误:
-
can't find template file in the path:因为没配this.TplName,且自动渲染开启 -
http: response.WriteHeader on hijacked connection:Render 强行往已被 hijack 的连接写 header
解决方式不是全局关掉 AutoRender(会影响其他 HTTP 接口),而是精准控制:
- 在 WebSocket Controller 的
Init()或Prepare()中加this.EnableRender = false - 或在
Get()开头立即写this.ServeEmpty()(提前终止 Beego 渲染流程) - 绝对不要在
Get()末尾留空——哪怕只写个return,Beego 仍可能走 Render
upgrader 配置哪些字段影响生产环境稳定性
websocket.Upgrader 的默认配置在开发中够用,但上线后容易因超时、缓冲区或跨域问题断连:
-
CheckOrigin必须显式设置,否则浏览器端 ws 连接会被拒绝(尤其 Vue/React 前端部署在不同域名时):upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, }(线上应改为校验r.Header.Get("Origin")) -
ReadBufferSize/WriteBufferSize建议设为4096或更高,避免大消息(如图片 base64)触发write: broken pipe - 不设
HandshakeTimeout会导致慢网用户握手失败;建议设10 * time.Second - 忽略
Subprotocols没问题,但若前端传了Sec-WebSocket-Protocol头而服务端没匹配,连接会静默关闭
如何安全地管理多个 WebSocket 连接而不泄漏 goroutine
每个连接启动 readPump() 和 writePump() 是标准做法,但容易漏掉 cleanup:
- 务必在
readPump()的for循环里捕获io.EOF和websocket.ErrCloseSent,然后主动close(client.messages)并从全局 map 中删 client -
writePump()的select必须带default:分支或time.After超时,否则client.messageschannel 堵塞时整个 goroutine 会卡死 - 不要用
defer conn.Close()在Get()里——它在函数返回时才触发,而连接早已被 hijack;应在readPump()或writePump()退出时立刻关 - 客户端断连时,
conn.ReadMessage()会返回 error,这是唯一可靠信号;别依赖心跳包来判断“是否还活着”
Nginx 反向代理 WebSocket 必须加的三行配置
如果用 Nginx 做前置,缺任何一行都会导致连接 10–30 秒后自动关闭:
-
proxy_set_header Upgrade $http_upgrade;:透传Upgrade: websocket头 -
proxy_set_header Connection "upgrade";:告诉 Nginx 这不是普通 HTTP,要保持长连接 -
proxy_read_timeout 86400;:默认 60 秒,不改就会频繁断连;设为 24 小时足够覆盖大多数场景
漏掉 Connection 头是最常见的线上故障点——Nginx 会把 WebSocket 当作普通 HTTP 处理,连接建立后立刻回收。
真正难的不是写通第一个连接,而是让成百上千个连接在内存不涨、goroutine 不堆积、Nginx 不静默杀连的前提下稳定跑满一周。所有“简单示例”都省略了连接清理、channel 关闭顺序、panic recover 这三处,而这三处出错时,日志里往往只有一行 write: broken pipe,根本看不出源头在哪。











