Подтвердить что ты не робот

Исходный интерфейс с Python и urllib2

Как установить исходный IP/интерфейс с помощью Python и urllib2?

4b9b3361

Ответ 1

К сожалению, стек стандартных используемых библиотечных модулей (urllib2, httplib, socket) несколько плохо разработан для этой цели - в ключевом пункте операции HTTPConnection.connect (в httplib) делегирует socket.create_connection, что в свою очередь, не дает никакого "крючка" между созданием экземпляра сокета sock и вызова sock.connect, чтобы вы вставляли sock.bind непосредственно перед sock.connect, что вам нужно, чтобы установить IP-адрес источника ( Я евангелизируюсь широко, чтобы НЕ разрабатывать абстракции в таком воздухонепроницаемом, чрезмерно инкапсулированном виде - я буду говорить об этом в OSCON в этот четверг под названием "Zen и Art of Abstraction Maintenance" - но здесь ваша проблема как бороться со стеком абстракций, которые были созданы таким образом, вздох).

Когда вы сталкиваетесь с такими проблемами, у вас есть только два не очень хороших решения: скопируйте, вставьте и отредактируйте неверно оформленный код, в который вам нужно поместить "крючок", который не использовал оригинальный дизайнер; или, "обезьяна-патч", этот код. Также не ХОРОШО, но оба могут работать, поэтому, по крайней мере, будем благодарны за то, что у нас есть такие возможности (с использованием языка с открытым исходным кодом и динамического языка). В этом случае, я думаю, что я поеду для обезглавливания (что плохо, но копирование и вставка кодирования еще хуже) - фрагмент кода, например:

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_connection в своем методе connect, см. источник здесь (в самом конце страницы) и решите, какие еще функции функции create_connection вы можете захотеть воплотить в ваша скопированная/вставленная/отредактированная версия, если вы решите пойти по этому маршруту.

Ответ 2

Кажется, что это работает.

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".

Ответ 3

Здесь приведено дополнительное уточнение, в котором используется 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 реализацию, которая является исходной_дачей. Мы можем добавить его в новый urllib2.OpenerDirector и установить его как открывающий по умолчанию (для будущего urlopen()) со следующим кодом:

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

Ответ 4

Я думал, что буду следить за чуть лучшей версией патча обезьяны. Если вам нужно установить разные параметры порта в некоторых сокетах или использовать что-то вроде 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-адресу.

Ответ 5

Предполагая, что я должен использовать обезьяну-патч на самом высоком уровне, здесь альтернатива Alex отвечает, что исправляет 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