在我的项目中,我使用python requests
library处理所有HTTP请求。
现在,我需要使用特定的DNS查询http服务器 - 有两个环境,每个环境都使用自己的DNS,并且可以独立进行更改。
因此,当代码运行时,它应该使用特定于环境的DNS,而不是我的互联网连接中指定的DNS。
有没有人尝试使用python-requests?我只找到了urllib2的解决方案:
https://stackoverflow.com/questions/4623090/python-set-custom-dns-server-for-urllib-requests
答案 0 :(得分:19)
requests
使用urllib3
,最终也使用httplib.HTTPConnection
,因此来自 https://stackoverflow.com/questions/4623090/python-set-custom-dns-server-for-urllib-requests 的技术(现已删除,仅链接到Tell urllib2 to use custom DNS)在某种程度上仍然适用。
同名下的urllib3.connection
模块子类httplib.HTTPConnection
已将.connect()
方法替换为调用self._new_conn
的方法。反过来,这个代表urllib3.util.connection.create_connection()
。修补 函数可能最简单:
from urllib3.util import connection
_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.
host, port = address
hostname = your_dns_resolver(host)
return _orig_create_connection((hostname, port), *args, **kwargs)
connection.create_connection = patched_create_connection
并且您需要提供自己的代码来将地址的host
部分解析为IP地址,而不是依赖connection.create_connection()
调用(包裹socket.create_connection()
)来解析你的主机名。
与所有monkeypatching一样,请注意代码在以后的版本中没有显着改变;这里的补丁是针对urllib3
版本1.21.1创建的。但应该适用于早在1.9版本的版本。
请注意,此答案已重新编写,可用于较新的urllib3
版本,这些版本添加了更方便的修补位置。请参阅旧方法的编辑历史记录,适用于版本< 1.9,作为销售urllib3
版本的补丁而不是独立安装。
答案 1 :(得分:18)
您应该查看TransportAdapters,包括源代码。关于它们的文档并不是很好,但它们可以对RFC 2818和RFC 6125中描述的许多功能进行低级访问。特别是,这些文档鼓励(要求?)客户端代码,以支持特定于应用程序的DNS,以便检查证书' CommonName和SubjectAltName。这些调用中需要的关键字参数是" assert_hostname"。以下是如何使用请求库设置它:
from requests import Session, HTTPError
from requests.adapters import HTTPAdapter, DEFAULT_POOLSIZE, DEFAULT_RETRIES, DEFAULT_POOLBLOCK
class DNSResolverHTTPSAdapter(HTTPAdapter):
def __init__(self, common_name, host, pool_connections=DEFAULT_POOLSIZE, pool_maxsize=DEFAULT_POOLSIZE,
max_retries=DEFAULT_RETRIES, pool_block=DEFAULT_POOLBLOCK):
self.__common_name = common_name
self.__host = host
super(DNSResolverHTTPSAdapter, self).__init__(pool_connections=pool_connections, pool_maxsize=pool_maxsize,
max_retries=max_retries, pool_block=pool_block)
def get_connection(self, url, proxies=None):
redirected_url = url.replace(self.__common_name, self.__host)
return super(DNSResolverHTTPSAdapter, self).get_connection(redirected_url, proxies=proxies)
def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs):
pool_kwargs['assert_hostname'] = self.__common_name
super(DNSResolverHTTPSAdapter, self).init_poolmanager(connections, maxsize, block=block, **pool_kwargs)
common_name = 'SuperSecretSarahServer'
host = '192.168.33.51'
port = 666
base_url = 'https://{}:{}/api/'.format(common_name, port)
my_session = Session()
my_session.mount(self.base_url.lower(), DNSResolverHTTPSAdapter(common_name, host))
user_name = 'sarah'
url = '{}users/{}'.format(self.base_url, user_name)
default_response_kwargs = {
'auth': (NAME, PASSWORD),
'headers': {'Content-Type': 'application/json'},
'verify': SSL_OPTIONS['ca_certs'],
'cert': (SSL_OPTIONS['certfile'], SSL_OPTIONS['keyfile'])
}
response = my_session.get(url, **default_response_kwargs)
我使用common_name
作为证书上预期的名称以及您的代码将如何引用所需的计算机。我使用host
作为外部世界识别的名称 - FQDN,IP,DNS条目......当然,SSL_OPTIONS字典(在我的示例中)必须在您的计算机上列出相应的证书/密钥文件名。 (另外,NAME和PASSWORD应该解决纠正字符串的问题。)
答案 2 :(得分:2)
定制的HTTPAdapter将解决问题。
不要忘记将server_hostname
设置为启用SNI。
import requests
class HostHeaderSSLAdapter(requests.adapters.HTTPAdapter):
def resolve(self, hostname):
# a dummy DNS resolver
import random
ips = [
'104.16.89.20', # CloudFlare
'151.101.2.109', # Fastly
]
resolutions = {
'cdn.jsdelivr.net': random.choice(ips),
}
return resolutions.get(hostname)
def send(self, request, **kwargs):
from urllib.parse import urlparse
connection_pool_kwargs = self.poolmanager.connection_pool_kw
result = urlparse(request.url)
resolved_ip = self.resolve(result.hostname)
if result.scheme == 'https' and resolved_ip:
request.url = request.url.replace(
'https://' + result.hostname,
'https://' + resolved_ip,
)
connection_pool_kwargs['server_hostname'] = result.hostname # SNI
connection_pool_kwargs['assert_hostname'] = result.hostname
# overwrite the host header
request.headers['Host'] = result.hostname
else:
# theses headers from a previous request may have been left
connection_pool_kwargs.pop('server_hostname', None)
connection_pool_kwargs.pop('assert_hostname', None)
return super(HostHeaderSSLAdapter, self).send(request, **kwargs)
url = 'https://cdn.jsdelivr.net/npm/bootstrap/LICENSE'
session = requests.Session()
session.mount('https://', HostHeaderSSLAdapter())
r = session.get(url)
print(r.headers)
r = session.get(url)
print(r.headers)
答案 3 :(得分:0)
我知道这是一个旧线程,但这是我使用tldextract和dnspython的python3兼容解决方案。我留下了一些注释掉的代码,以说明如何调试和设置其他会话参数。
g++
这是控制台输出:
which gcc
希望这会有所帮助。
答案 4 :(得分:0)
或者仅使用带有curl的子进程并添加--dns-servers