使用Python和urllib2的源接口
我该如何用Python和urllib2设置源IP或网络接口?
6 个回答
这里有一个进一步的改进,利用了 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 中,并用下面的代码将其设置为默认的打开器(这样以后调用 urlopen() 时就会使用这个打开器):
handler = BoundHTTPHandler(source_address=("192.168.1.10", 0))
opener = urllib2.build_opener(handler)
urllib2.install_opener(opener)
这看起来是有效的。
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”这个地址变成可以参数化的形式。
很遗憾,当前使用的标准库模块(比如 urllib2、httplib 和 socket)在设计上有些问题。关键的操作点在于 HTTPConnection.connect
(在 httplib 中),它调用了 socket.create_connection
,而这个函数在创建 socket 实例 sock
和调用 sock.connect
之间没有提供任何“钩子”,让你可以在 sock.connect
之前插入 sock.bind
,这样才能设置源 IP。我一直在提倡不要把抽象设计得如此封闭和严密——我将在本周四的 OSCON 上以“抽象维护的禅与艺术”为题进行演讲——但在这里,你的问题是如何处理这样一堆设计得不太好的抽象,唉。
当你遇到这样的麻烦时,通常只有两种不太好的解决方案:要么复制、粘贴并编辑那些设计不当的代码,加入原设计者没有考虑到的“钩子”;要么就是对那段代码进行“猴子补丁”。这两种方法都不好,但都能奏效,所以至少我们可以感激有这样的选择(因为我们使用的是开源和动态语言)。在这种情况下,我觉得我会选择猴子补丁(虽然这不好,但复制粘贴代码更糟)——比如这样的一段代码:
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
根据你的具体需求(你是否需要所有的 socket 都绑定到同一个源 IP,还是...?),你可以在正常使用 urllib2
之前简单地运行这段代码,或者(当然更复杂的方式)只在需要绑定特定方式的那些出站 socket 时运行它(然后每次都恢复 socket.socket = true_socket
,以便为未来要创建的 socket 腾出空间)。第二种选择会增加一些复杂性,所以我在等你确认是否真的需要这些复杂性,然后再详细解释。
AKX 的好答案是“复制/粘贴/编辑”方案的一种变体,所以我不需要多说什么——不过请注意,它并没有完全复现 socket.create_connection
的 connect
方法,具体可以查看源代码 这里(在页面的最底部),如果你决定走这条路,可以考虑在你复制/粘贴/编辑的版本中加入 create_connection
函数的其他功能。