高级用法#
自定义池行为#
这个 PoolManager
类自动处理创建 ConnectionPool
根据需要为每个主机创建实例。默认情况下,它将保留最多10 ConnectionPool
实例。如果您向多个不同的主机发出请求,则增加该数量可能会提高性能。
import urllib3
http = urllib3.PoolManager(num_pools=50)
但是,请记住,这确实会增加内存和套接字消耗。
类似地, ConnectionPool
类保留了一个个体池 HTTPConnection
实例。这些连接在单个请求期间使用,并在请求完成时返回池。默认情况下,将只保存一个连接以供重复使用。如果您同时向同一主机发出多个请求,则增加此数量可能会提高性能。
import urllib3
http = urllib3.PoolManager(maxsize=10)
# Alternatively
pool = urllib3.HTTPConnectionPool("google.com", maxsize=10)
共享资源的行为 ConnectionPool
不同于 PoolManager
。默认情况下,如果发出新请求,并且池中没有空闲连接,则将创建新连接。但是,如果超过以下值,则不会保存此连接 maxsize
联系是存在的。这意味着 maxsize
不确定可以打开到特定主机的最大连接数,而只确定要保留在池中的最大连接数。但是,如果您指定 block=True
那最多也就是 maxsize
打开到特定主机的连接。
http = urllib3.PoolManager(maxsize=10, block=True)
# Alternatively
pool = urllib3.HTTPConnectionPool("google.com", maxsize=10, block=True)
任何新请求都将被阻止,直到池中有可用的连接。这是防止多线程应用程序中连接过多的主机泛滥的好方法。
流媒体和I/O#
使用时 preload_content=True
(默认设置)响应正文将被立即读取到内存中,并且HTTP连接将被释放回池中,而无需手动干预。
但是,在处理大型响应时,通常更好的方法是使用 preload_content=False
。设置 preload_content
至 False
意味着urllib3将仅在请求数据时从套接字读取。
备注
使用时 preload_content=False
,您需要手动将HTTP连接释放回连接池,以便可以重复使用。为了确保在重新使用之前,HTTP连接处于有效状态,应该从线路上读取所有数据。
您可以拨打 drain_conn()
丢弃仍在网络上的未读数据。如果数据已经从响应中完全读取,则此调用不是必需的。
读取所有数据后,您可以调用 release_conn()
以将连接释放到池中。
您可以拨打 close()
关闭连接,但此调用不会将连接返回到池,而是丢弃线路上的未读数据,并使连接处于未定义的协议状态。如果您不喜欢从套接字读取数据而不是重新使用HTTP连接,则需要这样做。
stream()
使您可以遍历响应内容的块。
import urllib3
resp = urllib3.request(
"GET",
"https://httpbin.org/bytes/1024",
preload_content=False
)
for chunk in resp.stream(32):
print(chunk)
# b"\x9e\xa97'\x8e\x1eT ....
resp.release_conn()
但是,您也可以将 HTTPResponse
实例作为类似文件的对象。这允许您进行缓冲:
import urllib3
resp = urllib3.request(
"GET",
"https://httpbin.org/bytes/1024",
preload_content=False
)
print(resp.read(4))
# b"\x88\x1f\x8b\xe5"
呼叫 read()
将阻塞,直到有更多响应数据可用。
import io
import urllib3
resp = urllib3.request(
"GET",
"https://httpbin.org/bytes/1024",
preload_content=False
)
reader = io.BufferedReader(resp, 8)
print(reader.read(4))
# b"\xbf\x9c\xd6"
resp.release_conn()
您可以使用这个类似文件的对象来执行一些操作,如使用 codecs
:
import codecs
import json
import urllib3
reader = codecs.getreader("utf-8")
resp = urllib3.request(
"GET",
"https://httpbin.org/ip",
preload_content=False
)
print(json.load(reader(resp)))
# {"origin": "127.0.0.1"}
resp.release_conn()
代理人#
您可以使用 ProxyManager
要通过HTTP代理传输请求,请执行以下操作:
import urllib3
proxy = urllib3.ProxyManager("https://localhost:3128/")
proxy.request("GET", "https://google.com/")
用法 ProxyManager
是一样的 PoolManager
.
您可以使用HTTP、HTTPS或SOCKS连接到代理。urllib3的行为将根据您选择的代理类型和您正在联系的目标而有所不同。
HTTP和HTTPS代理#
HTTP/HTTPS代理都支持HTTP和HTTPS目标。它们之间的唯一区别是您是否需要首先建立到代理的TLS连接。您可以通过指定正确的代理方案来指定需要联系的代理。(即 http://
或 https://
)
urllib3的行为将根据您的代理和目标而有所不同:
- HTTP代理+HTTP目标
您的请求将与 absolute URI 。
- HTTP代理+HTTPS目标
将使用以下命令建立TCP隧道 HTTP CONNECT 。之后,将与目的地建立TLS连接,并发送您的请求。
- HTTPS代理+HTTP目标
将建立到代理的TLS连接,稍后您的请求将与 absolute URI 。
- HTTPS代理+HTTPS目标
将建立TLS-in-TLS隧道。将建立到代理的初始TLS连接,然后 HTTP CONNECT 将被发送以建立到目的地的TCP连接,最后将建立到目的地的第二个TLS连接。您可以自定义
ssl.SSLContext
用于代理TLS连接proxy_ssl_context
的论据ProxyManager
班级。
对于HTTPS代理,我们还支持将您的请求通过 absolute URI 如果 use_forwarding_for_https
参数设置为 True
。我们强烈推荐您 only use this option with trusted or corporate proxies 因为代理将全面了解您的请求。
您的代理似乎只使用HTTP,而不使用HTTPS#
如果您收到的是 ProxyError
它还提到您的代理只会说HTTP而不会说HTTPS以下是如何解决您的问题的:
如果您正在使用 urllib3
直接,确保您要传递的URL urllib3.ProxyManager
开头为 http://
而不是 https://
:
# Do this:
http = urllib3.ProxyManager("http://...")
# Not this:
http = urllib3.ProxyManager("https://...")
如果相反,您正在使用 urllib3
通过另一个库(如请求),可能会有多种方式错误配置您的代理。您需要找出配置不正确的地方,并在那里进行修复。需要查看的一些常见位置是环境变量,如 HTTP_PROXY
, HTTPS_PROXY
,以及 ALL_PROXY
。
确保所有这些环境变量的值都以 http://
而不是 https://
:
# Check your existing environment variables in bash
$ env | grep "_PROXY"
HTTP_PROXY=http://127.0.0.1:8888
HTTPS_PROXY=https://127.0.0.1:8888 # <--- This setting is the problem!
# Make the fix in your current session and test your script
$ export HTTPS_PROXY="http://127.0.0.1:8888"
$ python test-proxy.py # This should now pass.
# Persist your change in your shell 'profile' (~/.bashrc, ~/.profile, ~/.bash_profile, etc)
# You may need to logout and log back in to ensure this works across all programs.
$ vim ~/.bashrc
如果您使用的是Windows或MacOS,则可能会在系统级别设置您的代理。要检查这一点,首先确保没有设置上述环境变量,然后运行以下命令:
$ python -c 'import urllib.request; print(urllib.request.getproxies())'
如果上述命令的输出不是空的,如下所示:
{
"http": "http://127.0.0.1:8888",
"https": "https://127.0.0.1:8888" # <--- This setting is the problem!
}
搜索如何在您的操作系统上配置代理并更改 https://...
URL进入 http://
。进行更改后,将返回 urllib.request.getproxies()
应该是:
{ # Everything is good here! :)
"http": "http://127.0.0.1:8888",
"https": "http://127.0.0.1:8888"
}
如果在执行完所有这些步骤后,您仍然无法确定如何配置您的代理,请 join our community Discord 我们会尽力帮你解决你的问题。
SOCKS代理#
对于袜子,你可以使用 SOCKSProxyManager
以连接到SOCKS4或SOCKS5代理。要使用SOCKS代理,您需要安装 PySocks 或将urllib3与 socks
额外:
python -m pip install urllib3[socks]
一旦安装了PySock,您就可以使用 SOCKSProxyManager
:
from urllib3.contrib.socks import SOCKSProxyManager
proxy = SOCKSProxyManager("socks5h://localhost:8889/")
proxy.request("GET", "https://google.com/")
备注
建议使用 socks5h://
或 socks4a://
您的计划中的 proxy_url
以确保在连接到域名时从远程服务器(而不是客户端)进行DNS解析。
自定义TLS证书#
与其使用 certifi 您可以提供自己的证书颁发机构捆绑包。这在您已生成自己的证书或使用私有证书颁发机构的情况下非常有用。只需在创建时提供证书包的完整路径 PoolManager
:
import urllib3
http = urllib3.PoolManager(
cert_reqs="CERT_REQUIRED",
ca_certs="/path/to/your/certificate_bundle"
)
resp = http.request("GET", "https://example.com")
当您指定自己的证书包时,只有用该包验证的请求才会成功。建议使用单独的 PoolManager
向不需要自定义证书的URL发出请求。
自定义SNI主机名#
如果您想通过使用SNI的HTTPS创建到主机的连接,有两个位置需要主机名。它必须包含在发送的主机头中,以便服务器知道请求的主机是哪个。主机名还应与服务器提供的证书匹配,该证书由urllib3检查。
通常,当您通过名称连接到主机时,urllib3会为您设置和检查这些值。但是,有时设置连接的预期主机头和证书主机名(主题)很有用,尤其是在不使用名称解析进行连接时。例如,您可以使用HTTPS通过IP连接到服务器,如下所示:
import urllib3
pool = urllib3.HTTPSConnectionPool(
"104.154.89.105",
server_hostname="badssl.com"
)
pool.request(
"GET",
"/",
headers={"Host": "badssl.com"},
assert_same_host=False
)
请注意,以这种方式使用连接时,必须指定 assert_same_host=False
.
当的DNS解析 example.org
与您要使用的地址不匹配。IP可能用于专用接口,或者您可能希望在循环DNS下使用特定主机。
针对另一台主机验证TLS#
如果您要连接的服务器提供的证书与主机名或SNI主机名不同,则可以使用 assert_hostname
:
import urllib3
pool = urllib3.HTTPSConnectionPool(
"wrong.host.badssl.com",
assert_hostname="badssl.com",
)
pool.request("GET", "/")
客户端证书#
您还可以指定客户端证书。当服务器和客户端都需要验证对方的身份时,这很有用。通常,这些证书由同一颁发机构颁发。要使用客户端证书,请在创建 PoolManager
:
http = urllib3.PoolManager(
cert_file="/path/to/your/client_cert.pem",
cert_reqs="CERT_REQUIRED",
ca_certs="/path/to/your/certificate_bundle"
)
如果您有加密的客户端证书私钥,则可以使用 key_password
参数指定用于解密密钥的密码。
http = urllib3.PoolManager(
cert_file="/path/to/your/client_cert.pem",
cert_reqs="CERT_REQUIRED",
key_file="/path/to/your/client.key",
key_password="keyfile_password"
)
如果你的密钥没有加密 key_password
参数不是必需的。
TLS最低和最高版本#
当urllib3配置的TLS版本与服务器愿意使用的TLS版本不兼容时,您可能会看到如下错误:
SSLError(1, '[SSL: UNSUPPORTED_PROTOCOL] unsupported protocol (_ssl.c:1124)')
从v2.0开始,默认情况下,urllib3使用TLS 1.2和更高版本,因此只支持TLS 1.1或更早版本的服务器在默认情况下不能使用urllib3。
要解决该问题,您需要使用 ssl_minimum_version
选项和 TLSVersion enum 在标准库中 ssl
模块将urllib3配置为接受更广泛的TLS版本。
为获得最佳安全性,最好将此值设置为服务器正在使用的TLS版本。例如,如果服务器需要TLS 1.0,您可以这样配置urllib3:
import ssl
import urllib3
http = urllib3.PoolManager(
ssl_minimum_version=ssl.TLSVersion.TLSv1
)
# This request works!
resp = http.request("GET", "https://tls-v1-0.badssl.com:1010")
证书验证和macOS#
苹果提供的python和openssl库包含一个补丁,使它们能够自动检查系统密钥链的证书。如果指定自定义证书并看到请求意外成功,这可能会令人惊讶。例如,如果您指定自己的证书进行验证,而服务器提供的是不同的证书,则连接可能会失败。但是,如果该服务器提供系统密钥链中的证书,则连接将成功。
This article 有更深入的分析和解释。
TLS警告#
urllib3将根据证书验证支持的级别发出几个不同的警告。这些警告指示特定的情况,并且可以以不同的方式解决。
InsecureRequestWarning
当对未启用证书验证的https url发出请求时,会发生这种情况。跟随 certificate verification 解决此警告的指南。
发出未验证的https请求是 强烈地 然而,如果你理解这些风险并希望禁用这些警告,你可以使用。 disable_warnings()
:
import urllib3
urllib3.disable_warnings()
或者,您可以用标准捕获警告。 logging
模块:
logging.captureWarnings(True)
最后,可以通过设置 PYTHONWARNINGS
环境变量或使用 -W flag .
Brotli编码#
Brotli是一种由google创建的压缩算法,其压缩效果比gzip和deflate更好,并且受到urllib3的支持,如果 Brotli 包装或 brotlicffi 软件包已安装。您也可以通过 urllib3[brotli]
额外的:
$ python -m pip install urllib3[brotli]
下面是一个通过 Accept-Encoding
标题:
import urllib3
urllib3.request(
"GET",
"https://www.google.com/",
headers={"Accept-Encoding": "br"}
)
Z标准编码#
Zstandard 是Facebook创建的一种压缩算法,其压缩效果好于brotli、gzip和deflate(请参阅 benchmarks )并由urllib3支持,如果 zstandard package 已安装。您也可以通过以下方式请求安装程序包 urllib3[zstd]
额外:
$ python -m pip install urllib3[zstd]
备注
Urllib3中的Z标准支持要求使用v0.18.0或更高版本的 zstandard
包裹。如果安装的版本低于v0.18.0,则不会启用Z标准支持。
下面是一个使用zstd编码的示例 Accept-Encoding
标题:
import urllib3
urllib3.request(
"GET",
"https://www.facebook.com/",
headers={"Accept-Encoding": "zstd"}
)
用Wireshark解密捕获的TLS会话#
Python3.8及更高版本支持TLS预主密钥的日志记录。用这些秘密工具 Wireshark 可以解密捕获的网络流量。
要启用此功能,只需定义环境变量 SSLKEYLOGFILE :
export SSLKEYLOGFILE=/path/to/keylogfile.txt
然后在中配置密钥日志文件 Wireshark 见 Wireshark TLS Decryption 以获取指示。
自定义SSL上下文#
您可以通过为urllib3SSL配置提供 ssl.SSLContext
对象。出于兼容性的目的,我们建议您从以下地址获取 create_urllib3_context()
。
一旦你有了一个上下文对象,你就可以改变它来达到你想要的任何效果。例如,下面的代码加载默认的SSL证书,设置 ssl.OP_ENABLE_MIDDLEBOX_COMPAT
默认情况下未设置的标志,然后发出HTTPS请求:
import ssl
from urllib3 import PoolManager
from urllib3.util import create_urllib3_context
ctx = create_urllib3_context()
ctx.load_default_certs()
ctx.options |= ssl.OP_ENABLE_MIDDLEBOX_COMPAT
with PoolManager(ssl_context=ctx) as pool:
pool.request("GET", "https://www.google.com/")
注意,这不同于将 options
参数为 create_urllib3_context()
因为我们不会覆盖默认选项:我们只添加一个新选项。