与Python和urllib2的源接口

如何设置Python和urllib2的源IP /接口?

不幸的是,正在使用的标准库模块堆栈(urllib2,httplib,套接字)在devise上有些糟糕 – 在操作的关键点, HTTPConnection.connect (在httplib中)委托给socket.create_connection ,你在创build套接字实例socksock.connect调用之间没有“挂钩”,因为你在sock.bind之前插入sock.connect ,这就是你需要设置的源IP(我广泛传播因为我们不是在这样一种气密,过度封装的方式下devise抽象 – 我将在本周四在OSCON上以“禅和抽象维护的艺术”的名义来谈论这个 – 但是在这里你的问题是如何处理这样devise的一堆抽象,感叹)。

当你遇到这样的问题时,你只有两个不太好的解决scheme:复制,粘贴和编辑错误devise的代码,在这些代码中你需要放置一个原来的devise者不需要的“钩子” 或者“猴子补丁”那个代码。 也不是好的,但都可以工作,所以至less让我们感谢,我们有这样的select(通过使用开源和dynamic语言)。 在这种情况下,我想我会去换猴子(这是坏的,但复制和粘贴编码更糟糕) – 代码片段,如:

 import socket true_socket = socket.socket def bound_socket(*a, **k): sock = true_socket(*a, **k) sock.bind((sourceIP, 0)) return sock socket.socket = bound_socket 

根据你的确切需求(你是否需要所有的套接字绑定到相同的源IP,或…?),你可以简单地在使用urllib2之前运行它,或者(当然更复杂的方式)对于那些需要以某种方式进行绑定的传出套接字(然后每次恢复socket.socket = true_socket以避免将来的套接字尚未创build)。 第二种select会增加自己的复杂性,以适当的协调,所以我在等待你澄清,你是否需要这样的复杂,才解释所有。

AKX的很好的答案是“复制/粘贴/编辑”选项的一个变种,所以我不需要做太多的扩展 – 但是请注意,它不能完全复制它的connect方法中的socket.create_connection ,请看这里的源代码(在页面的最后),并决定如果你决定走这条路线,你可能想在你的复制/粘贴/编辑版本中包含create_connection函数的其他function。

这似乎工作。

 import urllib2, httplib, socket class BindableHTTPConnection(httplib.HTTPConnection): def connect(self): """Connect to the host and port specified in __init__.""" self.sock = socket.socket() self.sock.bind((self.source_ip, 0)) if isinstance(self.timeout, float): self.sock.settimeout(self.timeout) self.sock.connect((self.host,self.port)) def BindableHTTPConnectionFactory(source_ip): def _get(host, port=None, strict=None, timeout=0): bhc=BindableHTTPConnection(host, port=port, strict=strict, timeout=timeout) bhc.source_ip=source_ip return bhc return _get class BindableHTTPHandler(urllib2.HTTPHandler): def http_open(self, req): return self.do_open(BindableHTTPConnectionFactory('127.0.0.1'), req) opener = urllib2.build_opener(BindableHTTPHandler) opener.open("http://google.com/").read() # Will fail, 127.0.0.1 can't reach google.com. 

不过,你需要弄清楚一些参数化“127.0.0.1”的方法。

下面是使用HTTPConnection的source_address参数 (在Python 2.7中引入)的进一步改进:

 import functools import httplib import urllib2 class BoundHTTPHandler(urllib2.HTTPHandler): def __init__(self, source_address=None, debuglevel=0): urllib2.HTTPHandler.__init__(self, debuglevel) self.http_class = functools.partial(httplib.HTTPConnection, source_address=source_address) def http_open(self, req): return self.do_open(self.http_class, req) 

这给了我们一个自定义的urllib2.HTTPHandler实现,它可以感知到source_address。 我们可以将它添加到一个新的urllib2.OpenerDirector中,并使用下面的代码作为默认的opener(用于将来的urlopen()调用)安装它:

 handler = BoundHTTPHandler(source_address=("192.168.1.10", 0)) opener = urllib2.build_opener(handler) urllib2.install_opener(opener) 

我想我会跟上一个稍微好一点的猴子补丁版本。 如果您需要能够在某些套接字上设置不同的端口选项,或者使用类似套接字的SSL之类的东西,则下面的代码会更好一些。

 _ip_address = None def bind_outgoing_sockets_to_ip(ip_address): """This binds all python sockets to the passed in ip address""" global _ip_address _ip_address = ip_address import socket from socket import socket as s class bound_socket(s): def connect(self, *args, **kwargs): if self.family == socket.AF_INET: if self.getsockname()[0] == "0.0.0.0" and _ip_address: self.bind((_ip_address, 0)) s.connect(self, *args, **kwargs) socket.socket = bound_socket 

如果您需要在需要绑定到不同的IP地址的相同进程中运行类似于Web服务器的东西,则必须仅在连接上绑定套接字。

推理我应该在可用的最高级别猴子补丁,这是一个替代的亚历克斯的答案补丁httplib而不是socket ,利用httplib.HTTPSConnection.__init__()httplib.HTTPSConnection.__init__()关键字参数(这是不公开的urllib2 ,AFAICT )。 经过testing并正在使用Python 2.7.2。

 import httplib HTTPSConnection_real = httplib.HTTPSConnection class HTTPSConnection_monkey(HTTPSConnection_real): def __init__(*a, **kw): HTTPSConnection_real.__init__(*a, source_address=(SOURCE_IP, 0), **kw) httplib.HTTPSConnection = HTTPSConnection_monkey 

从Python 2.7开始,httplib.HTTPConnection添加了source_address,允许您提供一个IP端口对来绑定。

请参阅: http : //docs.python.org/2/library/httplib.html#httplib.HTTPConnection