gorilla/websocket.Upgrader.Upgrade() 返回 http.ErrUpgradeRequired 是因客户端未发送正确的升级请求头,必须包含 Upgrade: websocket 和 Connection: Upgrade。
gorilla/websocket 的 Upgrader.Upgrade() 会返回 http.ErrUpgradeRequired
这个错误不是连接失败,而是你没发对 HTTP 请求头。WebSocket 握手本质是 HTTP 协议升级,客户端必须带 Upgrade: websocket 和 Connection: Upgrade,否则 Upgrade() 直接拒绝并返回该错误。
new WebSocket("ws://...") 而非普通 fetch 或 XMLHttpRequest

curl -i -N \ -H "Connection: Upgrade" \ -H "Upgrade: websocket" \ -H "Sec-WebSocket-Version: 13" \ -H "Sec-WebSocket-Key: $(openssl rand -base64 16)" \ http://localhost:8080/ws
location /ws {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}*websocket.Conn
gorilla/websocket 的连接对象**不支持并发读或并发写**,但允许一个 goroutine 读、另一个写——这是设计前提。直接多个 goroutine 调用 conn.WriteMessage() 会 panic:「concurrent write to websocket connection」。
conn.SetWriteDeadline() 防僵死,再通过 channel 把消息统一交给一个「writer goroutine」处理conn.SetReadDeadline(),收到消息后投递到业务逻辑 channel(如 chan Message)ReadMessage() 循环里直接调用耗时函数(比如 DB 查询),容易阻塞整个连接读取conn.ReadMessage() 返回 websocket.CloseMessage 时该怎么做这不是错误,是对方主动关闭连接的信号。此时你不该再调用 WriteMessage(),否则会报 use of closed network connection。
messageType == websocket.CloseMessage 后,应立即跳出读循环,并调用 conn.Close()
conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")),再关连接conn.Close() 内部会发一次 CloseMessage,所以通常只需自己发一次响应帧,避免重复关闭别把用户 ID 存在全局 map 里裸用,容易竞态和泄漏。推荐用 sync.Map + 显式清理:
user_id,存入 conn 的自定义字段(例如用 context.WithValue() 包裹 http.HandlerFunc)sync.Map 存 map[string]*websocket.Conn,key 是 user_id;但注意:连接断开时必须显式 Delete(),不能只靠 GCdefer conn.Close() 前加清理逻辑:defer func() {
if userID != "" {
clients.Delete(userID)
}
}()defer conn.Close()。这两个点一旦出问题,连接不会立刻断,但内存和 goroutine 会缓慢上涨。