gorilla/websocket.upgrader.checkorigin 必须显式配置,否则默认拒绝所有跨域请求,导致403错误且仅提示“origin not allowed”;生产环境应校验可信origin白名单,禁用无条件返回true。

gorilla/websocket.Upgrader.CheckOrigin 必须显式配置
不设 CheckOrigin 会导致生产环境跨域拦截,而默认值是拒绝所有 Origin —— 这和很多人直觉相反。服务端启动后,浏览器或 SockJS 客户端发来的请求会直接被 Upgrade 拒绝,错误日志里只显示 “origin not allowed”,没有更具体提示。
常见做法是临时设为 func(r *http.Request) bool { return true },但上线前必须收紧。真实场景中应校验 Host 或 Referer,例如只允许可信域名:
CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
return origin == "https://myapp.com" || origin == "http://localhost:3000"
}
- 空字符串
""也可能作为 Origin 出现在某些代理或测试工具中,需单独判断 - 若用 Nginx 做反向代理,注意它默认不透传 Origin 头,需加
proxy_set_header Origin $http_origin; - Spring WebFlux 等框架可能校验 Origin + Protocol 组合,仅匹配域名不够
每个连接必须独占 goroutine 读取消息
conn.ReadMessage() 是阻塞调用,放在主 goroutine 里会卡死整个 handler;多个连接共用一个读 goroutine 则无法区分来源、丢失上下文。高并发下最稳妥的模型是「一连接一读 goroutine」。
典型错误是把读循环写在 http.HandlerFunc 内部,没起 goroutine 就直接 for 循环,结果第一个连接就堵住后续所有请求。
- 读 goroutine 退出时,应通过
donechannel 通知管理器清理连接资源 - 避免用
time.AfterFunc做超时读,它无法中断正在阻塞的ReadMessage;正确方式是设置conn.SetReadDeadline() - 如果还要写消息,写操作必须加锁或走专用 write channel,否则并发写会 panic
连接数暴涨时 io wait 卡死的真实原因
看到 goroutine 状态是 [IO wait] 不代表出错,但持续数分钟不返回,基本可定位为 TCP 层问题:远端未发 Close 帧、网络中断后 KeepAlive 未生效、或防火墙静默丢包。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
Gorilla 默认不启用 TCP KeepAlive,连接空闲时不会主动探测对端是否存活。这在数百连接场景下极易积累半开连接,最终耗尽文件描述符或触发内核限制。
- 启用方法:在
Upgrader后手动设置底层 net.Conn 的 KeepAlive:conn.UnderlyingConn().(*net.TCPConn).SetKeepAlive(true) - 更推荐在 Dialer 侧统一控制(客户端)或监听器侧(服务端),比如
http.Server{ConnContext: ...}中包装连接 -
SetReadDeadline和SetWriteDeadline必须每次读写前重置,否则过期后下一次调用直接返回 error
消息结构化通道设计要带元信息
聚合数百个 WebSocket 源时,光把 []byte 往一个 chan []byte 里塞,后续根本分不清是谁发的、时间戳是多少、是否已重连过。硬编码拼接字符串(如 "[id-123]"+string(msg))又难解析、易冲突。
正确做法是定义结构体承载上下文:
type WSMessage struct {
ConnID string `json:"conn_id"`
Addr string `json:"addr"`
Received time.Time `json:"received"`
Data []byte `json:"data"`
}
- ConnID 不要用自增整数,建议用连接 URL 的 hash 或 UUID,避免多实例部署时 ID 冲突
- Data 字段保持原始
[]byte,不要提前string()转换,防止 UTF-8 解码失败导致乱码 - channel 类型应为
chan WSMessage,而非chan interface{},后者逃逸严重且类型断言成本高
真正麻烦的是连接异常恢复后如何延续 ConnID 和消息顺序——这需要额外状态机,不是靠 channel 能解决的。多数业务其实只需要“当前连接最新数据”,所以别过早抽象重播逻辑。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










