http connection proxy-connection,get代理和connect代理
Posted on 2013-12-17 09:44 bw_0927 阅读(1593) 评论(0) 收藏 举报https://www.imququ.com/post/the-proxy-connection-header-in-http-request.html
平时用 Chrome 开发者工具抓包时,经常会见到 Proxy-Connection 这个请求头。之前一直没去了解什么情况下会产生它,也没去了解它有什么含义。最近看完《HTTP 权威指南》第四章「连接管理」和第六章「代理」之后,终于搞明白了这是因为给浏览器设置了代理(Proxy)。而神器 Fiddler 的抓包原理就是让浏览器请求走它开的本地代理,所以开了 Fiddler 必然会产生这个请求头。
代理改变了什么?
为了彻底弄清这个问题,我们先来看下设置浏览器代理之后,HTTP 请求头有那些变化。下面分别是设置代理前后访问同一 URL 的请求头(省略了无关内容):
GET / HTTP /1 .1 Host: www.example.com Connection: keep-alive GET http: //www .example.com/ HTTP /1 .1 Host: www.example.com Proxy-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 .1 Host: www.example.com Connection: my-header, close, my-connection My-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 来显示,只是展现上的差别而已。
本文链接:https://www.imququ.com/post/the-proxy-connection-header-in-http-request.html
--EOF--
========================
http://hi.baidu.com/resoft007/item/7fae76ef77bc27355a2d641a
最近在做有管Http代理客户端的东西。
先看http代理的原理。
简单得说HTTP协议是这样的:
client对WEB Server的80端口发送一个字符串 GET /index.htm,WEBServer就读/index.html并传给Client.
简单得说HTTP代理的协议是这样的:
client对HTTP代理的8080端口发送一个字符串 GET http://smth.org/index.html
HTTP代理就读http://smth.org/index.htm并传给Client.
所以client并没有直接去连目的,只是让代理去读一个文件自己再读代理上的这个文件.于是代理就可以cache这个文件等下次别人来读一样的
地址的时候就把上次得到的文件传给他只需要发送一个modify的命令去问一下是否改变就可以了。
http代理相当于一个桥,客户端通过这个桥去连接到自己想去的地方。
但是这个桥需要验证。1.http<Get> (http 1.0/http1.1)2.http<Connect>(http1.1)
1.HTTP GET 代理(HTTP1.0/HTTP1.1都支持) - socket首先与代理服务Ip地址:8080建立连接
GET http:/www.example.com/index.php HTTP/1.1
HOST:www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,**;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Proxy-Connection: keep-alive
Referer: xxxxxx
Cookie: xxxxxx
Cache-Control: max-age=0
2.HTTP CONNECT代理(只有HTTP1.1支持) - socket首先与代理服务器IP地址:8080建立连接
CONNECT http:/www.example.com/index.php HTTP/1.1
HOST:www.example.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.0.6) Gecko/2009011913 Firefox/3.0.6
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Proxy-Connection: keep-alive
Referer: xxxxxx
Cookie: xxxxxx
Cache-Control: max-age=0
HTTP CONNECT是在HTTP1.1协议上才新增的命令
现在切入正题,如何穿越Http代理连接到我们想去的地方。
GET方式:
socket.connect('HTTP代理服务器IP',HTTP代理服务器端口)
socket.send('GET http://XXX.XXX.XXX/XXX.EXE HTTP/1.1\r\n
HOST: XXX.XXX.XXX\r\n\r\n')
对于这种方式没有尝试过,但是听朋友说,很不稳定,传一些大的文件可能会经常断,采用断点续传的话,时间上也是一个问题。
还有就是在网络中传输的数据必须完全遵守HTTP协议。(具体的http的GET、POST方法传输,Http代理穿透的流程使用关键字"GET"和"POST"的过程一样,只是提取数据的时候,需要解析HTTP头部、对应的定制标志或字符,而整个发送过程与普通的Socket网络通讯一样,首先创建socket套接字,然后connect代理服务器和端口,接着send构造的特殊请求数据包,之后就等待接受数据包。此时还需要做的就是伪造相应的HTTP服务端。在接受特定的HTTP相应后,解析请求,然后将响应的HTTP头加上特定的响应数据发送回请求端。
WinInet实现方式应该更简便一些。我参参考的这篇文章http://hi.baidu.com/%D0%A4%C5%F4%BF%EC%C0%D6%E6%E4%D5%BB/home)
CONNECT方式:
socket.connect('HTTP代理服务器IP',HTTP代理服务器端口)
socket.send('CONNECT XXX.XXX.XXX:80 HTTP/1.1\r\n\r\n')
socket.recv(1024)
socket.send('GET /XXX.EXE HTTP/1.1\r\n
HOST: XXX.XXX.XXX\r\n\r\n')
HTTP代理服务器中能够提供一种HTTP CONNECT代理服务,能够允许用户建立TCP连接到任何端口。通过CONNECT方法穿透代理的实现方法为:
CONNECT代理服务器的代理端口(如:8080);如果成功返回就可以按照正常的Socket进行通讯。当然在此之前需要先获得代理相关的信息。
类似与socket连接一样,通过代理服务器建立好连接后,以后就用这条连接来发送接受数据了。简单方便易懂。
通过分析腾讯QQ的测试连接代理的方式,抓包得出腾讯采用的也是这种方式进行代理的,这种代理方式我个人感觉很适合客户端,服务端应用程序的代理,第一种的GET方式适用于网页Http应用程序的代理。
具体的连接代码网上很多,我是完全模仿腾讯的连接字符串
sprintf(buff, "CONNECT %s:%d HTTP/1.1\r\nHost: %s:%d\r\nAccept: */*\r\nContent-Type: text/html\r\nProxy-Connection: Keep-Alive\r\nContent-length: 0\r\n\r\n", lpszHost, nPort, lpszHost, nPort);
跟腾讯的格式一模一样,腾讯的客户端应该对返回来的数据包的处理方式更多一些。有些代理我的程序显示连接不成功的,腾讯能连接成功,我估计是腾讯对不同的代理服务器返回来的数据包的分析上下了很多工夫,基本上把所有返回的数据可能都包含了。
有的代理服务器会跟目标主机建立TCP连接后就会不发送数据包了,有的代理服务器跟目标主机建立连接后,还会再返回数据包(具体的数据包的作用还没有分析),如果客户端对这种数据包不处理,或者作为异常,就会导致连接不成功。甚至有的代理服务器会把客户端发到目标机器上的数据包放到TCP包的数据的最后(TCP数据包的前一部分是其他数据)。
所以说分析好各有代理服务器发送给目标级的请求,解析出真正的客户端发给目标机的数据,才是最重要的。呵呵