gin 的 context.writer 不能直接复用推送连接,因为 http 是无状态短连接,而消息推送需长连接维持;c.writer 在请求生命周期结束后会被关闭或复用,强行写入将触发 “http: response.writeheader on hijacked connection” 错误。

为什么 Gin 的 context.Writer 不能直接复用推送连接
因为 HTTP 是无状态短连接,而消息推送需要长连接维持。Gin 默认的 c.Writer 在请求生命周期结束时会被关闭或复用,强行写入会触发 http: response.WriteHeader on hijacked connection 错误。
正确做法是调用 c.Hijack() 获取底层 net.Conn,再自行管理读写——但 Gin 并不推荐这么做,它更倾向把长连接交给专用服务(如 WebSocket 或 SSE)处理。
- 若坚持用 HTTP 轮询,直接用
c.JSON(200, data)即可,无需接管连接 - 若需实时推送,优先选
gin-contrib/sse(服务端事件)或gorilla/websocket(WebSocket),而非硬撬Hijack() - Gin 的中间件和路由逻辑在
Hijack()后不再生效,日志、超时、panic 恢复等都会失效
用 gin-contrib/sse 实现浏览器端实时消息推送
SSE(Server-Sent Events)适合单向下行推送,比 WebSocket 简单,且原生支持 HTTP/1.1 和自动重连。
先安装依赖:
go get github.com/gin-contrib/sse
- 注册路由时用
c.SSEvent()发送事件,事件名可自定义(如"message"或"notice") - 客户端需用
EventSource接收,URL 必须支持 CORS(Gin 需加cors中间件) - 每个连接对应一个独立的
http.ResponseWriter,不要跨 goroutine 写同一个c - 务必设置
Cache-Control: no-cache和Content-Type: text/event-stream,否则浏览器可能缓存响应
示例服务端片段:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func sendMessages(c *gin.Context) {
c.Header("Cache-Control", "no-cache")
c.Header("Content-Type", "text/event-stream")
c.Header("Connection", "keep-alive")
c.Flush() // 确保 header 先发出去
for msg := range messageChan {
if !c.IsClientConnected() {
break
}
c.SSEvent("message", msg)
c.Flush()
}
}
如何安全地在多协程中广播消息到多个 SSE 连接
不能直接把所有 *gin.Context 存进全局 slice 并并发写——c.SSEvent() 不是线程安全的,且 Context 生命周期不可控。
正确方式是维护一个连接池:每个 SSE 连接启动一个专属 goroutine 监听 channel;广播时只往 channel 发,由各自 goroutine 负责写入。
- 用
sync.Map存储map[string]chan string,key 是用户 ID 或 session ID - 连接建立时生成唯一 channel,注册进 map;断开时 close channel 并从 map 删除
- 广播给某用户:查 map 得到其 channel,
select带 timeout 写入,避免阻塞 - 注意 channel 缓冲大小——设为 1~10 即可,太大易内存泄漏;不缓冲则需配合
select非阻塞判断
上线前必须检查的三个坑
本地跑通不等于生产可用。Nginx、负载均衡器、云服务商网关常默认关闭长连接或限制流式响应。
- Nginx 需显式配置:
proxy_http_version 1.1;、proxy_set_header Connection '';、chunked_transfer_encoding off; - 阿里云 SLB / AWS ALB 默认 60 秒空闲超时,需调大至 300 秒以上,并开启“长连接保持”
- 移动端 WebView(尤其 iOS)对 SSE 支持不稳定,建议 fallback 到轮询(如每 30s
GET /api/pull)并共用同一套消息存储(如 Redis Stream)
消息去重、投递确认、离线缓存这些,得靠业务层补,框架本身不提供。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










