天道酬勤,学无止境

How do I specify URL resolution in python's requests library in a similar fashion to curl's --resolve flag?

问题

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

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

回答1

在做了一些挖掘之后,我(不出所料)发现 Requests 通过要求 Python 来解析主机名(这是要求你的操作系统来做)。 首先,我找到了一些示例代码来劫持 DNS 解析(告诉 urllib2 使用自定义 DNS),然后我在套接字文档中找到了有关 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 上按原样工作。
回答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 更好的解决方案。

回答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)
...

资料来源:

  • 强制 Python 请求连接到特定的 IP 地址
  • Github 仓库:Roadmaster/forcediphttpsadapter
回答4

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

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

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐