beego websocket行情推送卡在连接后无数据,主因是默认controller依赖session/filter导致连接被提前中断;应绕过框架生命周期,用beego.handler挂载自定义handler,禁用filter、手动upgrade,并通过channel+goroutine安全广播,同时改用标准日志替代beelogger。

为什么用 Beego 的 WebSocket 实现行情推送会卡在连接建立后无数据?
Beego 默认的 WebSocketController 依赖于框架的 session 和 router 中间件,而行情推送服务通常要求长连接不走 session、不触发任何 filter(比如 auth、log),否则会在 Finish() 或 StopServe() 阶段被提前中断。常见现象是浏览器能连上 ws://host:port/ws,但 OnOpen 后立刻断开,控制台看不到错误,OnMessage 和 OnClose 完全不触发。
解决方式不是“重写 WebSocket”,而是绕过 Beego 的标准请求生命周期:
- 在
router.go中用beego.Handler("/ws", &WsHandler{})直接挂载自定义http.Handler,跳过 controller 路由解析 -
WsHandler内部使用gorilla/websocket(Beego 2.x 默认内置,无需额外 import)的Upgrader手动升级连接 - 务必设置
CheckOrigin: func(r *http.Request) bool { return true },否则跨域页面无法连接(生产环境应改为白名单校验)
如何安全地广播实时行情而不阻塞连接?
直接在 OnMessage 里调用 conn.WriteMessage() 广播,会导致所有客户端串行发送——一个慢连接(如弱网手机)拖垮整个 goroutine,后续消息积压、超时、panic。
正确做法是分离「接收」和「分发」逻辑,用 channel + 单独 goroutine 管理广播:
- 每个连接建立时,生成唯一
clientID(例如uuid.NewString()),注册进全局map[*websocket.Conn]string - 启动一个独立 goroutine 监听行情数据源(如 Redis Pub/Sub 的
subscribe("stock:600519")或本地内存 map 周期更新) - 行情更新时,不直接写 conn,而是发到
broadcastChan - 广播 goroutine 从 channel 拿到数据后,并发遍历连接 map,对每个 conn 启动
go writeConn(conn, data),并设SetWriteDeadline(time.Now().Add(5 * time.Second))
这样即使某个连接卡住,只影响它自己,不影响其他 client 和行情摄入。
Beego 日志和 panic 为何在 WebSocket 连接中完全不输出?
因为 beego.BeeLogger 默认绑定在 HTTP 请求上下文,而手动 Upgrader.Upgrade() 后的连接已脱离 Beego 的 request/response 生命周期,beego.Error()、beego.Trace() 调用不会落地到日志文件,panic 也会静默消失。
必须切换为底层日志实例:
- 在
main.go初始化阶段,保存原始 logger:stdLog := log.New(os.Stderr, "[WS] ", log.LstdFlags|log.Lshortfile) - 所有 WebSocket 相关操作(
OnOpen、writeConn错误分支、panic recover)统一用stdLog.Printf()记录 - 对每个连接启用
defer func() { if e := recover(); e != nil { stdLog.Printf("panic on client %s: %v", clientID, e) } }()
否则线上出问题时,你只会看到连接数缓慢归零,却找不到任何线索。
怎样让 Beego 应用同时支持 HTTP API 和 WebSocket 且不冲突?
很多人把 WebSocket 路由写成 beego.Router("/ws", &controllers.WsController{}, "get:WsHandler"),结果发现每次调用 beego.Run() 后,HTTP 接口返回 404 或 WebSocket 升级失败——本质是 Beego 的 Router 仍走 controller 流程,而 WsController.WsHandler() 方法若没显式调用 this.ServeJSON() 或类似,就会触发默认模板渲染,导致 upgrade header 被污染。
真正互不干扰的做法只有两种:
- 方案一(推荐):HTTP 走 Beego 标准路由(
beego.Router("/api/quote", &QuoteController{})),WebSocket 走beego.Handler("/ws", &WsHandler{}),两者完全解耦 - 方案二:如果必须共用 controller,需在
Prepare()中判断this.Ctx.Input.IsWebSocket(),是则立即this.StopRun()并手动 upgrade,且确保该方法不执行任何this.Data赋值或this.TplName设置
别试图在一个 handler 里“先处理 HTTP 再 fallback 到 WS”——HTTP/1.1 的 upgrade 是原子操作,中间任何 WriteHeader 或 body 输出都会让浏览器拒绝升级。











