websocket 是怎么连接的
背景
最近项目新增了一个 websocket 服务,用 nginx 做了一个简单的端口转发,然后调用的时候发现报错:
error: Unexpected server response: 426
搜索引擎走一波,找到几篇相关文章:
https://nginx.org/en/docs/http/websocket.html
https://www.nginx.com/blog/websocket-nginx
解决方式也很简单,根据第一篇文章的说明,只要增加转发响应头的配置:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
因为 websocket 的连接建立是基于 HTTP/1.1 的,所以有必要指定 http 协议:
proxy_http_version: 1.1
当然这不是必须的,如果服务默认是 HTTP/1.1 的话
具体配置如下:
但多年踩坑经验告诉我,如果不了解某项技术的细节,早晚还要掉进下一个坑。所以打算好好研究下这里面的执行逻辑。
Websocket 建立连接的过程分析
下图是 websocket 建立连接-数据传输-断开连接 的整个过程
Http1.1 支持协议转换机制,具体说明参考 https://tools.ietf.org/html/rfc2616#section-14.42。
首先客户端发起 GET 请求
这里主要关注 Connection 和 Upgrade 两个请求头:
设置 Connection 头的值为 Upgrade 来指示这是一个升级请求
Upgrade 头制定要升级到的协议
服务端响应
如果服务端决定升级这次连接,就会返回 101 Switching Protocols 响应状态
经过以上过程,Websocket 的连接就建立成功了
但为什么 nginx 代理需要明确配置 Connection 和 Upgrade 请求头呢?
原来,http 协议将消息头分为了两类:end-to-end 和 hop-by-hop,具体说明参考:https://tools.ietf.org/html/rfc2616#section-13.5.1
他们的特性如下:
end-to-end
该类型的消息头会被代理转发
该类型的消息头会被缓存
hop-to-hop:
该类型的消息头不会被代理转发
该类型的消息头不会被缓存
这样就不难理解,nginx 配置代理转发后,默认并不能转发 Connection 和 Upgrade 消息头,这样转发后的请求到达 Websockt 服务后就没有这两个头,因此就不能由 http 协议升级为 websocket 协议。因此需要在 nginx 中进行手动配置。
以下为 HTTP/1.1 中 hop-to-hop 类型的消息头:Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailers, Transfer-Encoding, Upgrade
连接为什么断开了
从上面的抓包可以看到,最终 websocket 建立的连接被自动断开了,文档中也做出了解释,这是 nginx 的机制。
默认情况下,如果被代理的服务 60 秒内没有进行数据传输,连接就会被断开。这个超时时间可以通过 proxy_read_timeout 指令设置。
如果要保持建立的连接不断开,就要通过心跳包等手段进行维持。
注意
该升级机制只是 HTTP/1.1 有效,HTTP/2 已不支持该机制
nginx 会断开长时间没有数据传输的连接
版权声明: 本文为 InfoQ 作者【lockdown56】的原创文章。
原文链接:【http://xie.infoq.cn/article/b823d142cc526b9b474382e0d】。文章转载请联系作者。
评论