最近公司网络改造,限制所有对公网的直接tcp连接,打开一个网页只能通过代理,于是,我以前写的一些网络程序,统统不能用鸟~~

直接的socket连接不能用,也没有办法了,但是有些走http协议的,倒是还可以利用下代理访问,便想改造一番。

python的urllib支持代理的方式访问,只要设置一个http_proxy的环境变量就行了。但是,httplib不知道基于什么古怪的原因,居然不支持。

决定手工改了,打开httplib的源代码,仔细读了一番,在HTTPConnection类的connect方法中,直接连到代理服务器。试了试,还是不行,抓了下包,原来浏览器访问的时候在http协议的GET和POST之类的请求中,将地址设成绝对路径,而不仅仅是设置Host的header。又在putrequest函数里改了。

还是不行,抓包,仔细看,很诡异的问题,直到后来无意中发现,http请求里的header里面,Host被加入了两次~~~改掉之后就行了。

真烦啊,耽误我们大量时间的,往往不是那些看起来很困难的东西,而是无足轻重的不起眼的小细节。

直接改httplib的代码有些暴力,其他使用这个库的应用程序,可能会产生一些不可预知的错误,后来改了改,实现了一个单独的代理类。

下面把主要的代码贴出来

[python]
class ProxyHttpConnection(httplib.HTTPConnection):
def __init__(self, host, port=None, strict=None):
httplib.HTTPConnection.__init__(self, host, port, strict)

def connect(self):
"""Connect to the host and port specified in __init__."""
msg = "getaddrinfo returns an empty list"
for res in socket.getaddrinfo(self.host, self.port, 0,
socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
if self._http_vsn == 11 and os.environ.has_key("http_proxy"):
proaddr = os.environ["http_proxy"];
if proaddr.startswith("http:"): proaddr = proaddr[7:];
prhost,prport = proaddr.split(":");
sa = prhost,int(prport);
try:
self.sock = socket.socket(af, socktype, proto)
if self.debuglevel > 0:
print "connect: (%s, %s)" % (self.host, self.port)
self.sock.connect(sa)
except socket.error, msg:
if self.debuglevel > 0:
print ‘connect fail:’, (self.host, self.port)
if self.sock:
self.sock.close()
self.sock = None
continue
break
if not self.sock:
raise socket.error, msg

def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
if self._HTTPConnection__response and self._HTTPConnection__response.isclosed():
self._HTTPConnection__response = None

if self._HTTPConnection__state == httplib._CS_IDLE:
self._HTTPConnection__state = httplib._CS_REQ_STARTED
else:
raise CannotSendRequest()

self._method = method
if not url:
url = ‘/’
if self._http_vsn == 11 and os.environ.has_key("http_proxy"):
if not url.startswith("http"):
tmport = "";
if self.port!=80: tmport = ":%d"%port;
url = "http://%s%s%s" % (self.host,tmport,url);
str = ‘%s %s %s’ % (method, url, self._http_vsn_str)

self._output(str)

if self._http_vsn == 11:
if not skip_accept_encoding:
self.putheader(‘Accept-Encoding’, ‘identity’)
self.putheader(‘Proxy-Connection’, ‘Close’);
else:
pass
[/python]

在自己的应用中,直接使用这个ProxyHttpConnection代替HttpConnection类就行了。

Proxy-Connection这个header在标准的http协议里是没有定义的,但是大部分浏览器实现了它,来告诉缓存服务器应该将连接直接关掉还是保持,主要是因为缓存服务器两边的连接有http 1.0和http 1.1两种可能,这样做反而导致了误解和歧义,因为http协议头本身就有一个connection来控制。现在的大部分浏览器会加入一个判断,如果连接的是一个代理服务器,就会使用Proxy-Connection,如果是真正的服务器,就采用标准的头。与之相关的一些讨论可以google到。


发表评论

邮箱地址不会被公开。 必填项已用*标注