Gin 本身不支持 WebSocket,必须依赖 gorilla/websocket 完成协议升级和收发;漏掉 defer conn.Close() 或配错 Upgrader.CheckOrigin 会导致连接堆积、内存暴涨、goroutine 泄漏;gin.Context 绑定 HTTP 生命周期,Upgrade 后立即失效,所有读写必须交由 *websocket.Conn 执行,且 CheckOrigin 生产环境须严格白名单校验。

Gin 本身不支持 WebSocket,必须靠 gorilla/websocket 完成协议升级和收发;漏掉 defer conn.Close() 或配错 Upgrader.CheckOrigin,上线后几小时内就会出现连接堆积、内存暴涨、goroutine 泄漏。
为什么 Gin 的路由里不能直接调用 conn.ReadMessage()
Gin 的 *gin.Context 绑定 HTTP 生命周期,一旦 Upgrade 成功,c.Writer 和 c.Request 就失效了。继续用 c.JSON() 或 c.Writer.Write() 会报 http: response.WriteHeader called multiple times;更严重的是,后续所有读写必须交给原生 *websocket.Conn 对象。
- 升级后立刻丢弃
c,不再调用任何c.开头的方法 -
conn.ReadMessage()和conn.WriteMessage()是唯一合法的 I/O 接口 - 循环读写前必须加
defer conn.Close(),否则连接不会释放,goroutine 永不退出
Upgrader.CheckOrigin 在开发和生产环境怎么设才安全
CheckOrigin 不是可选项,是防跨站 WebSocket 劫持的第一道门。开发时设为 return true 很方便,但上线等于裸奔。
- 错误写法:
CheckOrigin: func(r *http.Request) bool { return true }—— 生产环境禁止 - 错误写法:
CheckOrigin: func(r *http.Request) bool { return r.Header.Get("Origin") == "https://myapp.com" }—— 忽略端口、子域名、协议变体(如https://admin.myapp.com:8080) - 推荐写法:用
switch显式列出允许的 Origin,支持多域名+端口
CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
switch origin {
case "https://app.example.com", "https://admin.example.com:8080":
return true
default:
return false
}
}
注意:Origin 头可被伪造,它只过滤请求来源,不能替代业务层身份校验(比如 JWT 验证)。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
立即学习“go语言免费学习笔记(深入)”;
WebSocket 连接生命周期管理最容易踩的坑
很多人以为升级完就万事大吉,其实连接建立只是开始。没处理好生命周期,几分钟内就能压垮服务。
- 必须在
handleWebSocket函数开头加defer conn.Close(),哪怕后面还有 for 循环 - 读消息失败(如
use of closed network connection)后要主动 break,避免无限重试 - 不要在
conn上并发调用WriteMessage()——gorilla/websocket的写操作不是 goroutine-safe,需加锁或用 channel 序列化 - 心跳机制建议用
conn.SetPingHandler()+conn.SetPongHandler(),别自己手写定时器轮询
最常被忽略的一点:HTTPS 环境下 WebSocket 必须用 wss://,且 Upgrader 的配置要和 TLS 证书域名严格匹配,否则浏览器直接拒绝连接,连握手都进不来。

















