使用Python和urllib2的源接口

33 投票
6 回答
22269 浏览
提问于 2025-04-15 13:00

我该如何用Python和urllib2设置源IP或网络接口?

6 个回答

12

这里有一个进一步的改进,利用了 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)
24

这看起来是有效的。

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”这个地址变成可以参数化的形式。

48

很遗憾,当前使用的标准库模块(比如 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_connectionconnect 方法,具体可以查看源代码 这里(在页面的最底部),如果你决定走这条路,可以考虑在你复制/粘贴/编辑的版本中加入 create_connection 函数的其他功能。

撰写回答