Beego中实现长轮询必须绕过Controller,直接操作http.ResponseWriter并手动flush,否则Controller方法执行完即关闭连接;需禁用读写超时、配置反向代理timeout、设置Cache-Control和Connection头,并用select监听消息、超时与客户端断开三重信号。

Beego 中实现长轮询,核心是手动管理 HTTP 连接生命周期,不能依赖框架默认的请求-响应短周期模型;否则会立刻返回、根本“长”不起来。
为什么 beego.Controller.Get() 直接 sleep 会失败
很多人尝试在 Get() 方法里加 time.Sleep(30 * time.Second) 等待数据,结果发现浏览器秒回空响应——因为 beego 默认启用了 beego.BConfig.RunMode == "dev" 下的自动超时(beego.BConfig.Listen.HTTPSCertFile 无关,真正起作用的是 beego.BConfig.Listen.ServerTimeOut,默认 60 秒,但更关键的是底层 http.Server.ReadTimeout 和 WriteTimeout 未显式配置时可能更短)。
更隐蔽的问题是:即使你调大超时,Get() 返回后连接就关闭了,无法做到“客户端发一次请求,服务端 hold 住直到有数据再发”。长轮询要求连接保持打开状态,而 beego 的控制器方法执行完即视为响应结束。
- 不要在
Controller.Get()里用time.Sleep模拟等待 - 不要依赖
beego.BConfig.Listen.ServerTimeOut单独调大来“撑住”连接 —— 它只控制整个请求生命周期,不保证底层 TCP 连接不被中间代理(如 Nginx)或负载均衡器断开 - 必须显式设置
http.Server.ReadHeaderTimeout、ReadTimeout、WriteTimeout,且需同步配置反向代理(如 Nginx 的proxy_read_timeout)
正确做法:绕过 Controller,直接操作 http.ResponseWriter
长轮询本质是底层 HTTP 连接控制,应跳过 beego 的 MVC 封装层,在 beego.AppInit() 或独立 HTTP handler 中注册原生路由。这样你能完全掌控 http.ResponseWriter 的写入时机和连接状态。
示例代码片段(放在 main.go 或初始化模块中):
func longPollHandler(w http.ResponseWriter, r *http.Request) {
// 关键:禁用 beego 自动 header 和 flush
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
<pre class="brush:php;toolbar:false;">// 关键:获取底层 writer 并禁用自动 flush
if f, ok := w.(http.Flusher); ok {
// 不要在这里 f.Flush() —— 等待数据时才 flush
}
// 模拟等待新消息(实际应监听 channel / redis pubsub / db poll)
select {
case msg := <-messageChan:
json.NewEncoder(w).Encode(map[string]interface{}{"data": msg})
f.Flush() // 真正触发响应并保持连接
case <-time.After(25 * time.Second): // 超时兜底,避免客户端无限等待
json.NewEncoder(w).Encode(map[string]interface{}{"status": "timeout"})
f.Flush()
}}
// 注册为原生 handler,不走 Controller 流程 beego.BeeApp.Handlers.ServeHTTP = func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/api/longpoll" { longPollHandler(w, r) return } // 其他路径仍交由 beego 默认路由处理 beego.BeeApp.Handlers.DefaultServeMux.ServeHTTP(w, r) }
- 必须手动设置
Cache-Control和Connection,否则某些浏览器或代理会缓存或提前关闭连接 - 务必使用
http.Flusher显式刷新响应体,否则数据卡在缓冲区,客户端收不到 - 永远配超时兜底(比如 25s),防止客户端因网络问题卡死;标准长轮询建议 timeout 设为 30s,留 5s 给客户端重连
与 WebSocket 对比时容易忽略的兼容性代价
长轮询在 Beego 中可行,但相比 beego.WebSocketController,它有不可忽视的运维成本:
- 每个长轮询请求都占用一个 goroutine + 一个 TCP 连接,高并发下内存和文件描述符增长快;WebSocket 是单连接复用,资源开销低一个数量级
- 需要额外维护心跳机制(如定期发空响应),否则 Nginx 默认 60s
proxy_read_timeout会断开静默连接 - 无法服务端主动推送 —— 必须等客户端发起下一轮请求,实时性上限受制于重连间隔(通常 1–3s),而 WebSocket 是真双向
- 移动端弱网环境下,长轮询的频繁重连比 WebSocket 的连接保活更耗电、更易失败
除非你明确受限于老旧代理、iOS Safari beego.WebSocketController 路线更可持续。


















