繁体   English   中英

使用Python和urllib2的源接口

[英]Source interface with Python and urllib2

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

不幸的是,正在使用的标准库模块堆栈(urllib2,httplib,socket)在某种程度上设计得很糟糕 - 在操作的关键点, HTTPConnection.connect (在httplib中)委托给socket.create_connection ,而后者又给出了在创建套接字实例socksock.connect调用之间没有任何“挂钩”,你可以在sock.bind之前插入sock.connect ,这是你需要设置源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

根据您的具体需求(您是否需要将所有套接字绑定到相同的源IP,或者......?),您可以在使用urllib2之前简单地运行它,或者(当然是以更复杂的方式)在需要时运行它对于那些你需要以某种方式绑定的传出套接字(然后每次恢复socket.socket = true_socket以避免未来的套接字被创建)。 第二种选择增加了它自己的复杂性,以便正确地进行协调,所以我在等你解释它们是否确实需要这些并发症。

AKX的好答案是“复制/粘贴/编辑”替代方案的变体,因此我不需要对其进行大量扩展 - 请注意,它在其connect方法中并未完全重现socket.create_connection ,请参阅此处的源代码(在页面的最后)如果您决定走这条路线,请确定您可能希望在复制/粘贴/编辑的版本中体现create_connection功能的其他功能。

这似乎有效。

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)

这为我们提供了一个知道source_address的自定义urllib2.HTTPHandler实现。 我们可以将它添加到新的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服务器的东西,则必须仅在连接上绑定套接字。

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

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

理由是我应该猴子补丁在可用的最高级别,这里是亚历克斯的答案替代哪些补httplib ,而不是socket ,趁着httplib.HTTPSConnection.__init__()source_address关键字参数(其不被暴露urllib2 ,AFAICT )。 测试并使用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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM