33

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

4

6 回答 6

48

不幸的是,正在使用的标准库模块堆栈(urllib2、httplib、socket)为此目的设计得有些糟糕——在操作的关键点,HTTPConnection.connect(在 httplib 中)委托给socket.create_connection,这反过来又没有给你任何“钩子”在创建套接字实例socksock.connect调用之间,让您插入sock.bind之前sock.connect需要设置源 IP 的内容(我正在广泛宣传不要以这种密封、过度封装的方式设计抽象——我将在本周四的 OSCON 上以“Zen 和抽象维护的艺术”为题讨论这个问题——但这里的问题是如何处理以这种方式设计的一堆抽象,叹息)。

当您遇到此类问题时,您只有两个不太好的解决方案:复制、粘贴和编辑错误设计的代码,您需要在其中放置原始设计者不满足的“钩子”;或者,“猴子补丁”该代码。两者都不好,但两者都可以工作,所以至少让我们感谢我们有这样的选择(通过使用开源和动态语言)。在这种情况下,我想我会去打猴子补丁(这很糟糕,但复制和粘贴编码更糟糕)——一个代码片段,例如:

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以避开尚未创建的未来套接字)。第二种选择添加了它自己的复杂性来正确编排,所以我在等你澄清你是否确实需要这些复杂性,然后再解释它们。

AKX 的好答案是“复制/粘贴/编辑”替代方案的变体,因此我不需要对此进行太多扩展 - 但请注意,它并不能完全复制socket.create_connectionconnect方法,请参阅此处的源代码(非常页面末尾)并决定create_connection如果您决定采用该路线,您可能希望在复制/粘贴/编辑的版本中体现该功能的其他功能。

于 2009-07-19T17:43:41.353 回答
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”。

于 2009-07-19T17:36:44.550 回答
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)
于 2013-02-03T03:16:40.040 回答
2

我想我会跟进一个稍微好一点的猴子补丁版本。如果您需要能够在某些套接字上设置不同的端口选项,或者正在使用 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 地址的同一进程中运行类似网络服务器的东西,则只需在连接时绑定套接字。

于 2010-07-23T13:54:02.150 回答
2

推理我应该在可用的最高级别进行猴子补丁,这是 Alex 的答案的替代方案,它利用'关键字参数( AFAICT未公开)来修补httplib而不是。在 Python 2.7.2 上测试和工作。sockethttplib.HTTPSConnection.__init__()source_addressurllib2

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
于 2012-04-02T17:08:15.050 回答
1

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

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

于 2013-06-18T13:35:17.373 回答