web-dev-qa-db-ja.com

Pythonおよびurllib2のソースインターフェイス

Pythonおよびurllib2でソースIP /インターフェースを設定するにはどうすればよいですか?

31
jonasl

残念ながら、使用中の標準ライブラリモジュールのスタック(urllib2、httplib、socket)は、この目的のためにやや不適切に設計されています-操作の重要なポイントで、_HTTPConnection.connect_(httplib内)は_socket.create_connection_に委任します。これにより、ソケットインスタンスsockの作成と_sock.connect_呼び出しの間に「フック」がまったくなくなり、_sock.bind_の直前に_sock.connect_を挿入する必要があります。ソースIPを設定します(このような気密で過度にカプセル化された方法で抽象化を設計しないことで広く伝道しています-今週木曜日にOSCONで「Zenand the Art ofAbstractionMaintenance」というタイトルで話します-しかし、ここでの問題は、このように設計された抽象化のスタックをどのように処理するかです、ため息をつきます)。

このような問題に直面した場合、2つのあまり良くない解決策しかありません。元の設計者が対応しなかった「フック」を配置する必要がある誤った設計のコードをコピー、貼り付け、編集します。または、そのコードを「モンキーパッチ」します。どちらも良いことではありませんが、どちらも機能するので、少なくとも(オープンソースで動的な言語を使用して)そのようなオプションがあることに感謝しましょう。この場合、私はモンキーパッチを適用すると思います(これは悪いですが、コピーアンドペーストのコーディングはさらに悪いです)-次のようなコードフラグメント:

_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_を復元するたびに、将来のソケットがまだ作成されないようにします)。 2番目の選択肢は、適切にオーケストレーションするために独自の複雑さを追加するため、すべてを説明する前に、そのような複雑さが必要かどうかを明確にするのを待っています。

AKXの良い答えは、「コピー/貼り付け/編集」の代替案の変形であるため、それについてあまり拡張する必要はありません。ただし、connectメソッドで_socket.create_connection_を正確に再現しないことに注意してください。ソース ここ (ページの最後にあります)そして、コピー/貼り付け/編集されたバージョンで具体化したい_create_connection_関数の他の機能を決定します。ルート。

46
Alex Martelli

これはうまくいくようです。

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」をパラメーター化する方法を理解する必要があります。

24
AKX

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対応のカスタム rllib2.HTTPHandler 実装が得られます。これを新しい rllib2.OpenerDirector に追加し、次のコードを使用してデフォルトのオープナーとしてインストールできます(将来的には rlopen() 呼び出し)。

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

モンキーパッチの少し良いバージョンでフォローアップすると思いました。一部のソケットで異なるポートオプションを設定できるようにする必要がある場合、またはソケットをサブクラス化する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サーバーのようなものを実行する必要がある場合にのみ、接続時にソケットをバインドする必要があります。

2
Marshall Weir

利用可能な最高レベルでモンキーパッチを適用する必要があるという理由で、httplib.HTTPSConnection.__init__()source_addressキーワード引数(urllib2、AFAICTによって公開されていない)を利用して、httplibの代わりにsocketにパッチを適用するAlexの回答の代替案を次に示します。テストされ、作業中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
1
Reid

Python 2.7 httplib.HTTPConnectionにはsource_addressが追加されており、バインドするIPポートペアを提供できます。

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

1
Andrew