HTTP请求头中的Proxy-Connection 平时用Chrome开发者工具抓包时,经常会见到Proxy...

平时用Chrome开发者工具抓包时,经常会见到Proxy-Connection这个请求头。之前一直没去了解什么情况下会产生它,也没去了解它有什么含义。最近看完《HTTP权威指南》第四章“连接管理”和第六章“代理”之后,终于搞明白了这是因为给浏览器设置了代理(Proxy)。而神器Fiddler的抓包原理就是让浏览器请求走它开的本地代理,所以开了Fiddler必然会产生这个请求头。
代理改变了什么?为了彻底弄清这个问题,我们先来看下设置浏览器代理之后,HTTP请求头有那些变化。下面分别是设置代理前后访问同一URL的请求头(省略了无关内容):
GET / HTTP/1.1Host: www.example.comConnection: keep-aliveGET http://www.example.com/ HTTP/1.1Host: www.example.comProxy-Connection: keep-alive设置代理之后,浏览器连接的是代理服务器,不再是目标服务器,这个变化单纯从请求头中无法看出。请求头中的变化有两点:第一行中的request-URL变成了完整路径;Connection请求头被替换成了Proxy-Connection。我们分别来看这两个变化。
为什么需要完整路径?早期的HTTP设计中,浏览器直接与单个服务器进行对话,不存在虚拟主机。单个服务器总是知道自己的主机名和对应端口,为了避免冗余,浏览器只需要发送主机名之外的那部分URI就行了。代理出现之后,部分URI彻底杯具,代理服务器无法得知用户想要访问的URI在什么主机上。为此,HTTP/1.0要求浏览器为代理请求发送完整的URI,也就是说规范告诉浏览器的实现者必须这么做。
显式地给浏览器配置代理后,浏览器会为之后的请求使用完整URI,解决了代理无法定位资源的问题。但是代理可以出现在连接的任何位置,很多代理对浏览器来说不可见,如反向代理或路由器代理。所以实际上,几乎所有的浏览器都会为每个请求加上内容为主机名的HOST请求头,来彻底解决虚拟主机问题。对于HTTP/1.1请求,HOST请求头必须存在,否则会收到400错误;对于HTTP/1.0请求,如果连接的是代理服务器,使用相对URI,并且没有HOST请求头,会发生错误。
Proxy-Connection是什么?HTTP中的Connection,用来对HTTP连接进行说明,多个说明使用英文逗号隔开,如:
GET / HTTP/1.1Host: www.example.comConnection: my-header, close, my-connectionMy-Header: xxx其中,“my-header”是本次请求中其它Header的名字(不区分大小写),表示这个Header只与当前连接有关。实际上,Connection本身也只有当前连接有关。当客户端和服务端存在一个或多个中间实体(如代理)时,每个请求报文都会从客户端(通常是浏览器)开始,逐跳发给服务器;服务器的响应报文,也会逐跳返回给客户端。通常,即使通过了重重代理,请求头都会原封不动的发给服务器,响应头也会原样被客户端收到。但Connection,以及Connection定义的其它Header,只是对上个节点和当前节点之间的连接进行说明,必须在报文转给下个节点之前删除,否则可能会引发后面要提到的问题。其它不能传递的Header还有Prxoy-Authenticate、Proxy-Connection、Transfer-Encoding和Upgrade。
“close”表示操作完成后需要关闭当前连接;Connection还允许任何字符串作为它的值,如“my-connection”,用来存放自定义的连接说明。HTTP/1.0默认不支持持久连接,很多HTTP/1.0的浏览器和服务器使用“Keep-Alive”这个自定义说明来协商持久连接:浏览器在请求头里加上Connection: Keep-Alive,服务端返回同样的内容,这个连接就会被保持供后续使用。对于HTTP/1.1,Connection: Keep-Alive已经失去意义了,因为HTTP/1.1除了显式地将Connection指定为close,默认都是持久连接。
有了上面的背景知识,我们来看问题。互联网上,存在着大量简陋并过时的代理服务器在继续工作,它们很可能无法理解Connection——无论是请求报文还是响应报文中的Connection。而代理服务器在遇到不认识的Header时,往往都会选择继续转发。大部分情况下这样做是对的,很多使用HTTP协议的应用软件扩展了HTTP头部,如果代理不传输扩展字段,这些软件将无法工作。
如果浏览器对这样的代理发送了Connection: Keep-Alive,那么结果会变得很复杂。这个Header会被不理解它的代理原封不动的转给服务端,如果服务器也不能理解就还好,能理解就彻底杯具了。服务器并不知道Keep-Alive是由代理错误地转发而来,它会认为代理希望建立持久连接。这很常见,服务端同意了,也返回一个Keep-Alive。同样,响应中的Keep-Alive也会被代理原样返给浏览器,同时代理还会傻等服务器关闭连接——实际上,服务端已经按照Keep-Alive指示保持了连接,即时数据回传完成,也不会关闭连接。另一方面,浏览器收到Keep-Alive之后,会复用之前的连接发送剩下的请求,但代理不认为这个连接上还会有其他请求,请求被忽略。这样,浏览器会一直处于挂起状态,直到连接超时。
这个问题最根本的原因是代理服务器转发了禁止转发的Header。但是要升级所有老旧的代理也不是件简单的事,所以浏览器厂商和代理实现者协商了一个变通的方案:首先,显式给浏览器设置代理后,浏览器会把请求头中的Connection替换为Proxy-Connetion。这样,对于老旧的代理,它不认识这个Header,会继续发给服务器,服务器也不认识,代理和服务器之间不会建立持久连接(不能正确处理Connection的都是HTTP/1.0代理),服务器不返回Keep-Alive,代理和浏览器之间也不会建立持久连接。而对于新代理,它可以理解Proxy-Connetion,会用Connection取代无意义的Proxy-Connection,并将其发送给服务器,以收到预期的效果。
显然,如果浏览器并不知道连接中有老旧代理的存在,或者在老旧代理任意一侧有新代理的情况下,这种方案仍然无济于事。所以有时候服务器也会选择彻底忽略HTTP/1.0的Keep-Alive特性:对于HTTP/1.0请求,从不使用持久连接,也从不返回Keep-Alive。
最后通过上面的内容可以看到,浏览器对代理请求头的修改,都是为了尽可能的兼容网络中各种不规范的中转设备,使网络更健壮。
最后再提一句,用Fiddler和其它工具查看同一个请求头,会发现Fiddler显示的是Connection,而其它工具显示的是Proxy-Connection。这是因为大部分情况下,Fiddler会把Proxy-Connection换回Connection来显示,只是展现上的差别而已。
本文链接:http://www.imququ.com/post/the-proxy-connection-header-in-http-request.html
–EOF–