[英]Force requests to use IPv4 / IPv6
如何强制requests
库对获取请求使用特定的 Internet 协议版本? 或者这可以用 Python 中的另一种方法更好地实现吗? 我可以,但我不想使用curl
...
阐明目的的示例:
import requests
r = requests.get('https://my-dyn-dns-service.domain/?hostname=my.domain',
auth = ('myUserName', 'my-password'))
我找到了一个简约的解决方案来强制 urrlib3 使用 ipv4 或 ipv6。 urrlib3 使用此方法为 Http 和 Https 创建新连接。 您可以在其中指定要使用的任何 AF_FAMILY。
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
def allowed_gai_family():
"""
https://github.com/shazow/urllib3/blob/master/urllib3/util/connection.py
"""
family = socket.AF_INET
if urllib3_cn.HAS_IPV6:
family = socket.AF_INET6 # force ipv6 only if it is available
return family
urllib3_cn.allowed_gai_family = allowed_gai_family
这是一个 hack,但您可以使用猴子补丁 getaddrinfo 过滤到仅 IPv4 地址:
# Monkey patch to force IPv4, since FB seems to hang on IPv6
import socket
old_getaddrinfo = socket.getaddrinfo
def new_getaddrinfo(*args, **kwargs):
responses = old_getaddrinfo(*args, **kwargs)
return [response
for response in responses
if response[0] == socket.AF_INET]
socket.getaddrinfo = new_getaddrinfo
我已经为requests
+ urllib3
+ socket
编写了一个运行时补丁,它允许可选地并在每个请求的基础上传递所需的地址系列。
与其他解决方案不同的是,它不涉及猴子补丁,而是用补丁文件替换requests
的导入,它提供了一个与request
兼容的接口,所有暴露的类都被子类化和补丁,所有“简单 API”功能都重新实现。 唯一明显的区别应该是有一个额外的family
参数公开,您可以使用它来将名称解析期间使用的地址族限制为socket.AF_INET
或socket.AF_INET6
。 然后使用一系列复杂的(但主要是 LoC 密集型)策略方法覆盖将这个值一直传递到urllib3
的底层,在那里它将用于socket.create_connection
函数调用的替代实现。
TL;DR用法如下所示:
import socket
from . import requests_wrapper as requests # Use this load the patch
# This will work (if IPv6 connectivity is available) …
requests.get("http://ip6only.me/", family=socket.AF_INET6)
# … but this won't
requests.get("http://ip6only.me/", family=socket.AF_INET)
# This one will fail as well
requests.get("http://127.0.0.1/", family=socket.AF_INET6)
# This one will work if you have IPv4 available
requests.get("http://ip6.me/", family=socket.AF_INET)
# This one will work on both IPv4 and IPv6 (the default)
requests.get("http://ip6.me/", family=socket.AF_UNSPEC)
补丁库的完整链接(~350 LoC): https ://gitlab.com/snippets/1900824
我对https://stackoverflow.com/a/33046939/5059062采取了类似的方法,但是修补了socket
中发出 DNS 请求的部分,因此它只针对每个请求执行 IPv6 或 IPv4,这意味着可以使用在urllib
中与在requests
中一样有效。
如果您的程序还使用 unix 管道和其他类似的东西,这可能会很糟糕,所以我敦促谨慎使用猴子补丁。
import requests
import socket
from unittest.mock import patch
import re
orig_getaddrinfo = socket.getaddrinfo
def getaddrinfoIPv6(host, port, family=0, type=0, proto=0, flags=0):
return orig_getaddrinfo(host=host, port=port, family=socket.AF_INET6, type=type, proto=proto, flags=flags)
def getaddrinfoIPv4(host, port, family=0, type=0, proto=0, flags=0):
return orig_getaddrinfo(host=host, port=port, family=socket.AF_INET, type=type, proto=proto, flags=flags)
with patch('socket.getaddrinfo', side_effect=getaddrinfoIPv6):
r = requests.get('http://ip6.me')
print('ipv6: '+re.search(r'\+3>(.*?)</',r.content.decode('utf-8')).group(1))
with patch('socket.getaddrinfo', side_effect=getaddrinfoIPv4):
r = requests.get('http://ip6.me')
print('ipv4: '+re.search(r'\+3>(.*?)</',r.content.decode('utf-8')).group(1))
并且没有requests
:
import urllib.request
import socket
from unittest.mock import patch
import re
orig_getaddrinfo = socket.getaddrinfo
def getaddrinfoIPv6(host, port, family=0, type=0, proto=0, flags=0):
return orig_getaddrinfo(host=host, port=port, family=socket.AF_INET6, type=type, proto=proto, flags=flags)
def getaddrinfoIPv4(host, port, family=0, type=0, proto=0, flags=0):
return orig_getaddrinfo(host=host, port=port, family=socket.AF_INET, type=type, proto=proto, flags=flags)
with patch('socket.getaddrinfo', side_effect=getaddrinfoIPv6):
r = urllib.request.urlopen('http://ip6.me')
print('ipv6: '+re.search(r'\+3>(.*?)</',r.read().decode('utf-8')).group(1))
with patch('socket.getaddrinfo', side_effect=getaddrinfoIPv4):
r = urllib.request.urlopen('http://ip6.me')
print('ipv4: '+re.search(r'\+3>(.*?)</',r.read().decode('utf-8')).group(1))
在 3.5.2 中测试
您可以使用此 hack 来强制requests
使用 IPv4:
requests.packages.urllib3.util.connection.HAS_IPV6 = False
(这是 Nulano 作为评论提交的,我认为它应该是一个正确的答案)。
这完全未经测试,可能需要一些调整,但是结合使用 Python“请求”的答案与现有的套接字连接以及如何强制 python httplib 库只使用 A 请求,看起来你应该能够创建一个仅 IPv6 的套接字然后让请求将其用于其连接池,例如:
try:
from http.client import HTTPConnection
except ImportError:
from httplib import HTTPConnection
class MyHTTPConnection(HTTPConnection):
def connect(self):
print("This actually called called")
self.sock = socket.socket(socket.AF_INET6)
self.sock.connect((self.host, self.port,0,0))
if self._tunnel_host:
self._tunnel()
requests.packages.urllib3.connectionpool.HTTPConnection = MyHTTPConnection
在阅读了上一个答案后,我不得不修改代码以强制使用 IPv4 而不是 IPv6。 请注意,我使用了 socket.AF_INET 而不是 socket.AF_INET6,并且 self.sock.connect() 有 2 项元组参数。
我还需要覆盖与 HTTPConnection 大不相同的 HTTP S连接,因为requests
包装了 httplib.HTTPSConnection 以验证证书是否可用ssl
模块。
import socket
import ssl
try:
from http.client import HTTPConnection
except ImportError:
from httplib import HTTPConnection
from requests.packages.urllib3.connection import VerifiedHTTPSConnection
# HTTP
class MyHTTPConnection(HTTPConnection):
def connect(self):
self.sock = socket.socket(socket.AF_INET)
self.sock.connect((self.host, self.port))
if self._tunnel_host:
self._tunnel()
requests.packages.urllib3.connectionpool.HTTPConnection = MyHTTPConnection
requests.packages.urllib3.connectionpool.HTTPConnectionPool.ConnectionCls = MyHTTPConnection
# HTTPS
class MyHTTPSConnection(VerifiedHTTPSConnection):
def connect(self):
self.sock = socket.socket(socket.AF_INET)
self.sock.connect((self.host, self.port))
if self._tunnel_host:
self._tunnel()
self.sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file)
requests.packages.urllib3.connectionpool.HTTPSConnection = MyHTTPSConnection
requests.packages.urllib3.connectionpool.VerifiedHTTPSConnection = MyHTTPSConnection
requests.packages.urllib3.connectionpool.HTTPSConnectionPool.ConnectionCls = MyHTTPSConnection
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.