为什么HTTP Upgrade的时候,需要Connection: upgrade

很久之前,在看HTTP头部的时候,发现WebSocket等协议的Upgrade请求,需要同时带上Connection和Upgrade头部。但是,如果是仅仅Upgrade的话,Connection头部不就是多余的设计了么?

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

当时在知乎提了一个问题,结果到现在没有满意了回答,只能自己来回答了。

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请求:

GET /chat HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

结论

Connection和Upgrade有不同的语义和使用场景:

  • Connection: Upgrade 表示Upgrade是一个hop-by-hop的字段。这个头部是给proxy看的
  • Upgrade: websocket 表示浏览器想要升级到WebSocket协议。这个头部是给最终处理请求的程序看的。
  • 如果只有Upgrade: websocket,说明proxy不支持websocket升级,按照标准应该视为普通HTTP请求。

参考资料:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.