2017-06-13 155 views
0

HTTP請求的問題Python的 - 使用請求模塊

我需要檢查,如果從URL域沒有指向私有IP請求之前,並返回用於HTTP連接的IP獲得IP地址。

這是我的測試腳本:

import ipaddress 
import requests 
import socket 
import sys 

from urllib.parse import urlparse 


def get_ip(url): 
    hostname = socket.gethostbyname(urlparse(url).hostname) 
    print('IP: {}'.format(hostname)) 
    if hostname: 
     return ipaddress.IPv4Address(hostname).is_private 

def get_req(url): 
    private_ip = get_ip(url) 
    if not private_ip: 
     try: 
      with requests.Session() as s: 
       s.max_redirects = 5 
       r = s.get(url, timeout=5, stream=True) 
      return {'url': url, 'staus_code': r.status_code} 
     except requests.exceptions.RequestException: 
      return 'ERROR' 
    return 'Private IP' 

if __name__ == '__main__': 
    print(get_req(sys.argv[1])) 

如果域解析爲IP地址繁殖,如果網站託管背後的CloudFlare這將無法正常工作,例如:

# python test.py http://example.com 
IP: 104.31.65.106 
{'staus_code': 200, 'url': 'http://exmaple.com'} 

從tcpdump的一個片段:

22:21:51.833221 IP 1.2.3.4.54786 > 104.31.64.106.80: Flags [S], seq 902413592, win 29200, options [mss 1460,sackOK,TS val 252001723 ecr 0,nop,wscale 7], length 0 
22:21:51.835313 IP 104.31.64.106.80 > 1.2.3.4.54786: Flags [S.], seq 2314392251, ack 902413593, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 10], length 0 
22:21:51.835373 IP 1.2.3.4.54786 > 104.31.64.106.80: Flags [.], ack 1, win 229, length 0 

該腳本在104.31.65.106上進行了測試,但HTTP連接爲作出104.31.64.106

我看到this線程,但我不會消耗響應主體,所以the connection won't be released和實際上我的版本的請求模塊沒有這些屬性。

有沒有一種方法來達到這與requests模塊或我必須使用另一個庫,如urlliburliib3

澄清:如果嘗試連接到專用網絡地址,我只需要阻止該請求。如果有多個選項並且選擇了公共地址,那很好。

+0

爲什麼沒有'rsp = requests.get(...,stream = True); rsp.raw._connection.sock.getpeername()'爲你工作? – Flurin

+0

好的,所以我只是測試它,我想我可以關閉try/except塊中的連接,但它看起來像流只有在服務器啓用了keep-alive的情況下才起作用,否則連接立即關閉,並且我得到'AttributeError:'NoneType'對象沒有屬性「getpeername」。我希望在請求發出前檢查IP。 – HTF

+0

爲什麼所有的shenanigans都帶'request.sys()作爲s'然後s = requests.Session()'?那只是替換你的配置會話,放下's = ...'行。 –

回答

0

urllib3將自動跳過給定DNS名稱的不可路由地址。這不是需要預防的事情。

什麼創建連接時內部發生是這樣的:

  • DNS信息的請求;如果您的系統支持IPv6(綁定到::1成功),那麼包括IPv6地址。
  • 在爲了使地址列,它們由一個
    • 嘗試一個用於每個地址的合適插座被構造和
    • 插座被告知要連接到的IP地址
    • 如果連接失敗,則嘗試下一個IP地址,否則返回連接的套接字。

urllib3.util.connection.create_connection() function。專用網絡通常不可路由,因此自動跳過

但是,如果您是您自己的私人網絡,那麼有可能試圖連接到該IP地址,這可能需要一些時間來解決。

解決方案是adapt a previous answer of mine,它允許您在創建套接字連接的位置解析主機名;這應該讓你跳過私人使用地址。在socket.getaddrinfo()創建自己的循環,在這一點上拋出一個異常,如果一個私有網絡地址將嘗試:

import socket 
from ipaddress import ip_address 
from urllib3.util import connection 


class PrivateNetworkException(Exception): 
    pass 


_orig_create_connection = connection.create_connection 

def patched_create_connection(address, *args, **kwargs): 
    """Wrap urllib3's create_connection to resolve the name elsewhere""" 
    # resolve hostname to an ip address; use your own 
    # resolver here, as otherwise the system resolver will be used. 
    family = connection.allowed_gai_family() 

    host, port = address 
    err = None 
    for *_, sa in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM): 
     ip, port = sa 
     if ip_address(ip).is_private: 
      # Private network address, raise an exception to prevent 
      # connecting 
      raise PrivateNetworkException(ip) 
     try: 
      # try to create connection for this one address 
      return _orig_create_connection((ip, port), *args, **kwargs) 
     except socket.error as err: 
      last_err = err 
      continue 

     if last_err is not None: 
      raise last_err 

connection.create_connection = patched_create_connection 

所以這段代碼中查找IP地址的主機的早期,然後引發自定義異常。抓住這個例外:

with requests.Session(max_redirects=5) as s: 
    try: 
     r = s.get(url, timeout=5, stream=True) 
     return {'url': url, 'staus_code': r.status_code} 
    except PrivateNetworkException: 
     return 'Private IP' 
    except requests.exceptions.RequestException: 
     return 'ERROR' 
+0

謝謝,任何建議,我可以通過IP連接實際上是'requests.raw._original_response'? – HTF

+1

@HTF:我打算假設您使用的是Python 3,因此您在SO上找到的其他答案適用於Python 2不再適用。這是因爲套接字文件現在更復雜一些。 'requests.raw._original_response'是一個'http.client.HTTPResponse'實例,'.fp'是套接字文件,其中包含一個緩衝區,它將'SocketIO'對象與'_sock'屬性中的實際套接字包裝在一起。所以原始的套接字可以作爲'requests.raw._original_response.fp.raw._sock'使用。調用'.getpeername()'就可以了。 –