为什么HTTP Upgrade的时候,需要Connection: upgrade
很久之前,在看HTTP头部的时候,发现WebSocket等协议的Upgrade请求,需要同时带上Connection和Upgrade头部。但是,如果是仅仅Upgrade的话,Connection头部不就是多余的设计了么?
比如一个典型的WebSocket升级请求如下:
1 | GET /chat HTTP/1.1 |
当时在知乎提了一个问题,结果到现在没有满意了回答,只能自己来回答了。
Connection的起源
最开始,在HTTP/1.0出现没多久,人们就意识到HTTP持久连接的重要性(毕竟三次握手还是很慢的),所以各个服务器实现都采用了Keep-Alive头部来表示这个请求支持连接持久化。
HTTP/1.1中的Connection
在HTTP/1.1中,正式标准化了Connection头部:
Connection头部一般表示那些头部是属于逐跳头部的,比如Connection: Custom-Header,就表示在这个连接中,Custom-Header是一个逐跳头部,不应当被代理原样传递给upstream。
有两个例外:close表示会话不持久化,keep-alive表示会话支持持久化(虽然有一个Keep-Alive头部,但是大小写不一样)。
上面,我们提到了逐跳头部:
逐跳头部(hop-by-hop header),用来描述当前浏览器与直连服务器(比如Nginx反向代理)的连接信息。比如Keep-Alive头部,仅仅表示浏览器尝试和Nginx之间连接持久化,而不管Nginx和后端服务器之间的连接。proxy要处理这些头部,并按照自己的需要来修改这些头部。默认的逐条头部如下:
其他的头部都是端到端头部(end-to-end header),用来描述这个浏览器和最终处理请求的服务器之间的信息,比如Accept头部,表示客户端想从后端服务器得到的数据类型,而和中间的Nginx无关。proxy不能修改这些头部。
再回到HTTP 1.1的Connection头部,这儿有一个兼容性问题:我们以Upgrade头部为例,某个proxy实现了HTTP 1.0协议,将Upgrade原样转发给后端,后端和proxy升级协议,但是这个情况下,proxy不认识升级后的协议啊。
所以,RFC有增加了一条规定:
如果只有Upgrade: xxx
,而没有Connection: Upgrade
,那么就当作普通请求来处理。
Connection头部的扩展
然后很多地方就开始使用了,比如开始提到的WebSocket的Upgrade请求:
1 | GET /chat HTTP/1.1 |
结论
Connection
头部和Upgrade
头部有不同的语义和使用场景:
-
Connection: Upgrade
表示Upgrade是一个hop-by-hop的字段。这个头部是给proxy看的 -
Upgrade: websocket
表示浏览器想要升级到WebSocket协议。这个头部是给最终处理请求的程序看的。 - 如果只有
Upgrade: websocket
,说明proxy不支持WebSocket升级,按照标准应该视为普通HTTP请求。
参考资料:
为什么HTTP Upgrade的时候,需要Connection: upgrade