iris v12 内置 websocket 包不兼容标准 websocket 协议,因其绕过 rfc 6455 握手流程,未返回 upgrade: websocket 等必要响应头,导致浏览器 new websocket() 握手失败并报错。

Iris v12 内置的 websocket 包不兼容浏览器原生 WebSocket 协议,直接用 new WebSocket("ws://...") 会报错 —— 这不是配置问题,是协议层不匹配。
为什么 app.Get("/ws", websocket.Handler()) 会失败
Iris 自带的 websocket 模块(位于 github.com/kataras/iris/v12/websocket)本质是个轻量连接管理器,它绕过了 RFC 6455 握手流程,只做内存级长连接收发,不暴露标准 WebSocket Upgrade 接口。前端发起 new WebSocket() 时,服务端返回的响应头不含 Upgrade: websocket 和正确 Sec-WebSocket-Accept,浏览器直接判定握手失败,控制台显示 WebSocket connection to 'ws://...' failed。
常见错误现象包括:
- Chrome 控制台报
net::ERR_CONNECTION_RESET或400 Bad Request - Nginx 日志出现
upstream sent no valid HTTP/1.0 header(因 Iris 内置 handler 未按 HTTP 协议返回 Upgrade 响应) - 即使路由注册成功,
ctx.ResponseWriter()在升级后不可再用 —— 它已被底层 TCP 连接接管
用 gorilla/websocket 手动接管升级流程
这是最直接、最低抽象的方式,适用于需要完全控制帧收发、对接小程序/uni-app/uni.connectSocket、或已有自定义二进制协议的场景。关键点在于:Iris 只负责路由分发,真正的 WebSocket 升级和通信由 gorilla/websocket 完成。
实操建议:
- 路由必须用
app.Any("/ws", wsHandler),不能用Get—— 因为 WebSocket 握手是 GET,但后续 ping/pong/text/binary 帧不是 HTTP 方法,Any才能兜住所有方法 -
upgrader.Upgrade(w, r, nil)第三个参数传nil,否则某些代理(如 Nginx)会拦截Sec-WebSocket-Extensions头导致 400 - 升级后拿到的
*websocket.Conn不属于 IrisContext生命周期,需自行调用conn.SetReadDeadline防僵死,用conn.WriteMessage发送,别用fmt.Fprint或ctx.WriteString - 生产环境务必实现
CheckOrigin,例如:CheckOrigin: func(r *http.Request) bool { return r.Header.Get("Origin") == "https://yourdomain.com" }
示例片段:
func wsHandler(ctx iris.Context) {
upgrader := websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
conn, err := upgrader.Upgrade(ctx.ResponseWriter(), ctx.Request(), nil)
if err != nil {
ctx.StatusCode(400)
return
}
defer conn.Close()
for {
_, msg, err := conn.ReadMessage()
if err != nil {
break
}
if err := conn.WriteMessage(websocket.TextMessage, append([]byte("echo: "), msg...)); err != nil {
break
}
}
}
用 go-socket.io 支持房间/命名空间/自动降级
如果你需要广播到房间、区分命名空间(如 /chat 和 /notify)、或要求在弱网下自动 fallback 到 long-polling,go-socket.io 是更合适的选择。但它要求前端必须使用 socket.io-client,不能用原生 WebSocket 构造函数。
实操建议:
- 挂载路径必须是
/socket.io/*any,且要同时支持GET和POST—— Socket.IO 协议在不同阶段会混合使用两种方法 - 不要把
go-socket.ioserver 当作普通 HTTP handler 注册;要用gin.WrapH(Gin)、echo.WrapHandler(Echo),对 Iris 则需用iris.FromStd包装: -
app.Any("/socket.io/*any", iris.FromStd(server)),其中server是socketio.NewServer(nil)实例 - 注意版本兼容性:
go-socket.io@v1.4+要求 Go 1.16+,且与 Iris v12.2.x 兼容,但不兼容旧版v1.0的 API
Nginx 和 TLS 配置容易被忽略的点
无论选 gorilla/websocket 还是 go-socket.io,反向代理层若没配对,前端永远连不上。Iris 本身不处理代理透传逻辑,全靠外部配置。
关键项:
- Nginx 必须开启
proxy_http_version 1.1和proxy_set_header Upgrade $http_upgrade,否则 Upgrade 请求被降级为 HTTP/1.0,握手失败 - HTTPS 环境下,前端地址必须用
wss://,且 Nginx 的proxy_ssl_server_name on要打开,否则 SNI 不匹配导致 TLS 握手失败 - 如果用
go-socket.io,Nginx 还需额外配proxy_buffering off和proxy_cache off,否则 polling 请求可能被缓存或截断 - Iris 启动时若监听
:443,必须显式传入 TLS 配置(iris.TLS("cert.pem", "key.pem")),不能依赖 Nginx 终止 TLS 后再传 HTTP —— 因为 WebSocket Upgrade 对 TLS 层有强依赖
最易被忽略的是:开发时本地直连 ws://localhost:8080 成功,一上 Nginx 就断,90% 是 Upgrade 和 Connection 头没透传过去,而不是代码问题。











