2009-07-19 10 views
31

मैं पाइथन और urllib2 के साथ स्रोत आईपी/इंटरफ़ेस कैसे सेट करूं?पाइथन और urllib2 के साथ स्रोत इंटरफेस

+0

यह "अनुरोध" पुस्तकालय या pycurl उपयोग करने के लिए उचित है। यदि आप इसे गैर-तुच्छ कार्यों के लिए उपयोग करते हैं, तो आप हमेशा urllib2 के खराब डिज़ाइन पर ठोकर खा जाते हैं। – HighCat

उत्तर

45

दुर्भाग्य से उपयोग में मानक पुस्तकालय मॉड्यूल के ढेर (urllib2, httplib, सॉकेट) कुछ हद तक बुरी तरह से इस प्रयोजन के लिए डिज़ाइन किया गया है आप कोई "हुक" जो भी सॉकेट उदाहरण sock के सृजन और sock.connect कॉल के बीच, आप सम्मिलित करना के लिए sock.bind बस से पहले sock.connect है कि आप स्रोत आईपी (मैं नहीं इस तरह में कपोल-कल्पना डिजाइन करने के लिए व्यापक रूप से प्रचार कर रहा हूँ निर्धारित करने की आवश्यकता क्या एक वायुरोधी, अत्यधिक-encapsulated तरीका - मैं इसके बारे में ओएसकॉन में "जेन और आर्ट ऑफ एब्स्ट्रक्शन रखरखाव" शीर्षक के तहत इस बारे में बात करूँगा - लेकिन यहां आपकी समस्या यह है कि कैसे डिजाइन किया गया है अबाउट के ढेर से निपटने के लिए इस तरह, श्वास)।

जब आपको ऐसी समस्याएं आ रही हैं तो आपके पास केवल दो अच्छे समाधान नहीं हैं: या तो गलत डिज़ाइन किए गए कोड को कॉपी, पेस्ट और संपादित करें जिसमें आपको "हुक" रखने की आवश्यकता है जिसे मूल डिजाइनर ने पूरा नहीं किया ; या, कोड "बंदर-पैच"। न तो अच्छा है, लेकिन दोनों काम कर सकते हैं, इसलिए कम से कम आभारी रहें कि हमारे पास ऐसे विकल्प हैं (ओपन-सोर्स और गतिशील भाषा का उपयोग करके)। इस मामले में, मुझे लगता है कि मैं बंदर पैचिंग के लिए जाना चाहते हैं (जो बुरा है, लेकिन कॉपी और पेस्ट कोडिंग और भी बदतर है) - इस तरह के रूप में एक कोड टुकड़ा:

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 

अपने सटीक आवश्यकताओं के आधार पर (कर आपको सभी स्रोतों को एक ही स्रोत आईपी से बंधने की आवश्यकता है, या ...?) आप सामान्य रूप से urllib2 का उपयोग करने से पहले इसे चला सकते हैं, या (कोर्स के अधिक जटिल तरीकों से) इसे केवल उन आउटगोइंग सॉकेट के लिए जरूरी है जिन्हें आपको चाहिए एक निश्चित तरीके से बांधने के लिए (फिर प्रत्येक बार socket.socket = true_socket को पुनर्स्थापित करने के लिए भविष्य में सॉकेट बनाने के तरीके से बाहर निकलने के लिए)। दूसरा विकल्प ठीक से ऑर्केस्ट्रेट करने के लिए अपनी जटिलताओं को जोड़ता है, इसलिए मैं आपको यह बताने के लिए इंतजार कर रहा हूं कि आपको उन सभी को समझाने से पहले ऐसी जटिलताओं की आवश्यकता है या नहीं। फिर भी नोट है कि यह वास्तव में अपने connect विधि में socket.create_connection पुन: पेश नहीं करता है, देखते हैं -

AKX के अच्छे जवाब "कॉपी/पेस्ट/संपादित करें" विकल्प पर एक प्रकार तो मैं विस्तार करने के लिए ज्यादा उस पर की आवश्यकता नहीं है स्रोत here (पृष्ठ के बहुत अंत में) और create_connection फ़ंक्शन की अन्य कार्यक्षमता का निर्णय लें, यदि आप उस मार्ग पर जाने का निर्णय लेते हैं तो आप अपने कॉपी/पेस्ट/संपादित संस्करण में शामिल होना चाहेंगे।

+5

न केवल एक पूर्ण उत्तर है, लेकिन, शायद, बंदर पैचिंग के अच्छे उपयोग का पहला उदाहरण मैंने कभी देखा है –

+1

टीएक्स @ रॉबर्टो - यह "कम से कम बुराइयों" से अधिक है, लेकिन, हाँ, जब एक अमूर्तता का सामना करना पड़ता है यह आपकी जरूरतों के मुकाबले सील कर दिया गया है (आवश्यक हुक/"लीक" गायब हो रहा है), बंदरगाह "अच्छा" हो सकता है (उसी अर्थ में कि मरम्मत से परे बीमार है जो दांत खींच रहा है "अच्छा" ;-)। –

+0

@ एलेक्स मार्टेलि: धन्यवाद। बंदर पैच बस ठीक काम किया! :) – jonasl

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" को पैरामीटर करने के लिए कुछ तरीका पता लगाना होगा। प्रमुख मुद्दा के इस ऑपरेशन में, HTTPConnection.connect (httplib में) socket.create_connection के प्रतिनिधियों, बारी में देता है जो -

+0

यह पूरी तरह से काम करता है! बहुत धन्यवाद। –

+0

@ डेवराक्स: आपको किस प्रणाली में सफलता मिली? मैं विंडोज 7 –

+0

में नेटवर्क इंटरफेस को बांधने में असमर्थ हूं, लिनक्स और ओएसएक्स पर कोड मेरे लिए काम करता है। मैंने विंडोज़ पर सॉकेट कोड कभी नहीं लिखा है, लेकिन मुझे संदेह है कि उपयोगकर्ता की जगह में कच्चे सॉकेट की खिड़कियों की कमी से समस्याएं पैदा हो सकती हैं। –

2

मैंने सोचा कि मैं बंदर पैच के थोड़ा बेहतर संस्करण के साथ अनुवर्ती हूं। यदि आपको कुछ सॉकेट पर अलग-अलग बंदरगाह विकल्पों को सेट करने में सक्षम होना चाहिए या सॉकेट की तरह कुछ ऐसा उपयोग कर रहे हैं जो सॉकेट को उप-वर्गीकृत करता है, तो निम्न कोड थोड़ा बेहतर काम करता है।

_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 

यदि आप एक ही प्रक्रिया एक अलग आईपी पते के लिए बाध्य करने की जरूरत है कि में एक वेबसर्वर की तरह कुछ चलाने की आवश्यकता केवल कनेक्ट होने पर सॉकेट बाध्य करने के लिए है।

1

तर्क है कि मैं उच्चतम स्तर पर बंदर-पैच उपलब्ध होना चाहिए, यहाँ जो socket के बजाय httplib पैच, httplib.HTTPSConnection.__init__() के source_address कीवर्ड तर्क (जो urllib2, AFAICT द्वारा उजागर नहीं किया गया है) का लाभ लेने एलेक्स के जवाब के लिए एक विकल्प है। परीक्षण और पायथन 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 
10

यहाँ कि (अजगर 2.7 से प्रारंभ) HTTPConnection's source_address argument का उपयोग करता है एक और शोधन है:

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) 
+2

+1। मुझे लगता है कि पाइथन 2.7 के अद्यतन 'httplib' मॉड्यूल के साथ अनुशंसित विधि होनी चाहिए। वैसे, आईपीवी 4 एड्रेस स्ट्रिंग को कोट्स में रखा जाना चाहिए। मैंने आपकी पोस्ट को संपादित करने का प्रयास किया लेकिन इस तरह का छोटा बदलाव स्टैक ओवरफ्लो के तुच्छता फ़िल्टर को पास नहीं कर सका। –

संबंधित मुद्दे