【问题标题】:How do I specify URL resolution in python's requests library in a similar fashion to curl's --resolve flag?如何以类似于 curl 的 --resolve 标志的方式在 python 的请求库中指定 URL 解析?
【发布时间】:2017-06-05 17:16:13
【问题描述】:

我正在编写一些 python 客户端代码,由于一些环境限制,我想指定一个 URL 并控制它的解析方式。我可以通过 curl 使用 --resolve 标志来完成此操作。有没有办法用 Python 的 requests 库做类似的事情?

理想情况下,这可以在 Python 2.7 中使用,但我也可以使用 3.x 解决方案。

【问题讨论】:

    标签: python python-requests


    【解决方案1】:

    经过一番挖掘后,我(不出所料)发现 Requests 通过要求 Python 来解析主机名(即要求你的操作系统来做)。首先,我找到了一些示例代码来劫持 DNS 解析 (Tell urllib2 to use custom DNS),然后我在socket documentation 中找到了有关 Python 如何解析主机名的更多细节。然后只需将所有内容连接在一起即可:

    import socket
    import requests
    
    def is_ipv4(s):
        # Feel free to improve this: https://stackoverflow.com/questions/11827961/checking-for-ip-addresses
        return ':' not in s
    
    dns_cache = {}
    
    def add_custom_dns(domain, port, ip):
        key = (domain, port)
        # Strange parameters explained at:
        # https://docs.python.org/2/library/socket.html#socket.getaddrinfo
        # Values were taken from the output of `socket.getaddrinfo(...)`
        if is_ipv4(ip):
            value = (socket.AddressFamily.AF_INET, 0, 0, '', (ip, port))
        else: # ipv6
            value = (socket.AddressFamily.AF_INET6, 0, 0, '', (ip, port, 0, 0))
        dns_cache[key] = [value]
    
    # Inspired by: https://stackoverflow.com/a/15065711/868533
    prv_getaddrinfo = socket.getaddrinfo
    def new_getaddrinfo(*args):
        # Uncomment to see what calls to `getaddrinfo` look like.
        # print(args)
        try:
            return dns_cache[args[:2]] # hostname and port
        except KeyError:
            return prv_getaddrinfo(*args)
    
    socket.getaddrinfo = new_getaddrinfo
    
    # Redirect example.com to the IP of test.domain.com (completely unrelated).
    add_custom_dns('example.com', 80, '66.96.162.92')
    res = requests.get('http://example.com')
    print(res.text) # Prints out the HTML of test.domain.com.
    

    我在写这篇文章时遇到了一些警告:

    • 这对https 效果不佳。该代码工作正常(只需使用https://443 而不是http://80)。但是,SSL 证书与域名相关联,Requests 将尝试将证书上的名称验证到您尝试连接到的原始域。
    • getaddrinfo 返回的 IPv4 和 IPv6 地址信息略有不同。我对is_ipv4 的实现对我来说感觉很老套,如果您在实际应用程序中使用它,我强烈建议您使用更好的版本。
    • 代码已经在 Python 3 上测试过了,但我看不出它为什么不能在 Python 2 上按原样工作。

    【讨论】:

    • 哦,该死。那是铁杆。我假设有一种半简单的方法可以做到这一点,而我只是没有看到。我在下面提到的方法似乎解决了这个问题,但这很好作为后备。谢谢。
    • 在我的情况下,我必须用 value = (socket.AddressFamily.AF_INET, socket.SocketKind.SOCK_STREAM, 6, '', (ip, port)) 替换该行以使其工作
    【解决方案2】:

    我一直在尝试找出解决方案一段时间,最后偶然发现了这篇文章。 @supersam654 提供的解决方案并没有立即为我工作(使用 https 和 python 3.8),但是几天的睡眠让我得到了这个解决方案,无论版本如何(没有测试太多版本,但天真地希望就是这样)。

    它也应该适用于 ipv6 - 虽然我也没有测试过。

    解决方案的关键是对所有调用使用默认的 getaddrinfo()(对其输出不做任何假设)——只需将主机名替换为 IP 地址即可覆盖它!因此,我对它的运作情况发表了宏大的声明;-)

    import socket
    
    dns_cache = {}
    # Capture a dict of hostname and their IPs to override with
    def override_dns(domain, ip):
        dns_cache[domain] = ip
    
    
    prv_getaddrinfo = socket.getaddrinfo
    # Override default socket.getaddrinfo() and pass ip instead of host
    # if override is detected
    def new_getaddrinfo(*args):
        if args[0] in dns_cache:
            print("Forcing FQDN: {} to IP: {}".format(args[0], dns_cache[args[0]]))
            return prv_getaddrinfo(dns_cache[args[0]], *args[1:])
        else:
            return prv_getaddrinfo(*args)
    
    
    socket.getaddrinfo = new_getaddrinfo
    

    要使用上述逻辑 - 只需在发出请求之前像这样调用函数(您可以使用 IP 地址或其他 FQDN 覆盖!):

    override_dns('www.example.com', '192.168.1.100')
    

    我相信这比我之前使用的ForcedIPHTTPSAdapter 更好。

    【讨论】:

    • 感谢@supersam654 的启发。意识到我迟到了这个线程 - 发布代码以防其他人发现它有用。
    • 非常感谢@dhul.takker!非常干净的解决方案,并且与 python 的新闪亮 httpx 库一起工作就像一个魅力
    • 谢谢@dhul.takker !!!绝对专业,正是我需要的!
    【解决方案3】:

    迟到的答案,但有一个名为 forcediphttpsadapter 的模块正是这样做的:

    安装:

    pip3 install forcediphttpsadapter
    

    用法:

    import requests
    from forcediphttpsadapter.adapters import ForcedIPHTTPSAdapter
    
    url = 'https://domain.tld/path'
    session = requests.Session()
    session.mount(url, ForcedIPHTTPSAdapter(dest_ip='x.x.x.x')) # type the desired ip
    r = session.get(url, verify=False)
    print(r.text)
    ...
    

    来源:

    【讨论】:

      【解决方案4】:

      看起来最简单的方法是使用这个包:https://github.com/requests/requests-kerberos

      使用可路由名称并将 hostname_override 值设置为 Kerberos 期望的名称。

      【讨论】:

        猜你喜欢
        • 2017-03-30
        • 2019-06-04
        • 1970-01-01
        • 1970-01-01
        • 2020-11-27
        • 2017-10-08
        • 2012-12-28
        • 2015-12-15
        • 1970-01-01
        相关资源
        最近更新 更多