2017-06-13 114 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()'就可以了。 –