rex-tls
rex-tls 是一个面向 Python 的原生移动端 TLS/HTTP 客户端。它提供接近
requests 的调用方式,并在 Rust 原生扩展中实现 TLS、HTTP/1.1、HTTP/2
以及 Chrome Android 的 HTTP/3 网络路径。
项目使用 vendored BoringSSL,不依赖 wreq、reqwest、curl 或系统
OpenSSL。当前发布版是
rex-tls 2.1.0;仅提供预编译二进制 wheel,不发布 sdist。
适用范围:协议兼容测试、移动客户端互操作、经过授权的网络研究和普通 HTTP 客户端开发。本项目不包含验证码求解、WAF 绕过或站点专用规避逻辑。
支持的 profile
| profile | 协议 | 设备验证基线 |
|---|---|---|
chrome_android_149 |
TLS + HTTP/1.1 + HTTP/2 + HTTP/3 | Pixel 4 / Android 11 / Chrome 149.0.7827.200 |
chrome_android_150 |
TLS + HTTP/1.1 + HTTP/2 + HTTP/3 | Pixel 4 / Android 11 / Chrome 150.0.7871.63 |
okhttp_4.12 |
TLS + HTTP/1.1 + HTTP/2 | Pixel 4 / Android 11 / OkHttp 4.12.0 |
okhttp_5.4 |
TLS + HTTP/1.1 + HTTP/2 | Pixel 4 / Android 11 / OkHttp Android 5.4.0 |
四套 profile 均已完成真实设备行为采集,并已在同一个本机候选原生二进制上完成严格验收。 Chrome 149/150 覆盖 TLS、HTTP/2、HTTP/3 wire 与会话行为;OkHttp 4.12/5.4 覆盖冷连接、 TLS 恢复、顺序/并发 H2、POST 和大请求头。两个 OkHttp profile 均不声明 HTTP/3 能力。
安装
python -m pip install rex-tls==2.1.0
要求 Python 3.9 或更高版本。当前稳定版提供以下预编译 wheel:
- Windows x86-64
- Linux manylinux 2.28 x86-64
2.1.0 暂不发布 macOS wheel,以控制 CI 成本;macOS 上 pip 不会回退到源码构建。
首选导入名是 rex_tls;mobile_tls 仅作为兼容入口保留。
快速开始
import rex_tls
response = rex_tls.get(
"https://example.com/",
profile="chrome_android_149",
params={"page": 1},
timeout=30,
)
response.raise_for_status()
print(response.status_code) # 200
print(response.http_version) # HTTP/1.1、HTTP/2 或 HTTP/3
print(response.text)
发送 JSON:
import rex_tls
response = rex_tls.post(
"https://api.example.com/items",
profile="okhttp_4.12",
json={"name": "测试", "enabled": True},
)
print(response.json())
推荐:复用 Session
Session 会复用连接、Cookie 和 TLS 会话,连续请求时应优先使用:
from rex_tls import Session
with Session(
"chrome_android_150",
timeout=20,
follow_redirects=True,
max_redirects=10,
) as session:
session.headers.update({"x-client-id": "demo"})
first = session.get("https://example.com/start")
second = session.get("https://example.com/next")
print(first.connection_reused)
print(second.connection_reused)
请求参数与请求体
from rex_tls import Session
with Session("okhttp_4.12") as session:
# 查询参数
r1 = session.get(
"https://api.example.com/search",
params={"q": "中文", "page": 2},
)
# application/x-www-form-urlencoded
r2 = session.post(
"https://api.example.com/form",
data={"username": "demo", "remember": "1"},
)
# application/json; charset=utf-8
r3 = session.post(
"https://api.example.com/json",
json={"message": "你好"},
)
# 原始内容
r4 = session.put(
"https://api.example.com/raw",
content=b"raw bytes",
headers={"content-type": "application/octet-stream"},
)
content、data 和 json 三者互斥。支持 get、options、head、
post、put、patch、delete 以及通用 request。
请求头顺序
底层在 ALPN 确定最终协议后,才选择 H1、H2 或 H3 的请求头策略:
- 合并后的用户请求头为空时,使用该 profile、协议和请求上下文的完整默认头;
- 合并后的用户请求头非空时,只排序用户提供的字段,不注入 profile 默认头;
- Chrome 已知字段进入实机分类槽位;未知或自定义字段保持彼此之间的调用者顺序;
- OkHttp 的应用/拦截器决定普通字段顺序,因此保留调用者顺序;
- 需要重复同名头时,可传入元组列表;
- Session 级请求头会先与单次请求头进行大小写不敏感合并;
Host/:authority、H2/H3 伪头及请求体所需的Content-Length属于协议元数据, 由底层生成,不属于默认头注入。
from rex_tls import Session
with Session("chrome_android_149") as session:
session.headers.update({"accept-language": "zh-CN,zh;q=0.9"})
response = session.get(
"https://example.com/",
headers=[
("user-agent", "My-Mobile-Client/1.0"),
("accept", "application/json"),
("x-trace-a", "1"),
("x-trace-b", "2"),
],
)
如果需要 profile 的整套默认头,请不要传 headers,也不要修改 Session.headers。一旦传入
任意用户头,该集合就被视为完整的用户普通头集合;例如只传 Accept 时不会再自动增加
User-Agent 或 Accept-Language。
该判定早于 CookieJar 和 json/data 自动生成的 Content-Type:只传 cookies= 或请求体
仍会保留完整 profile 默认头,再把这些派生字段放入对应协议的排序位置。
H1 字段名会使用实机观察到的大小写(例如 Host、Accept-Encoding,而 sec-ch-ua
保持小写)。HTTP 字段名在语义上大小写不敏感;H2/H3 则按协议要求只能发送小写字段名。
H2/H3 中 Host 会转换为 :authority,Connection、Keep-Alive 等逐跳头是非法字段,
库会明确报错而不会静默删除。排序表覆盖内置头和常见标准请求字段;任意 X-* 名称空间
无限,无法由有限静态表枚举,因此未知字段采用稳定回退规则。
Chrome 的文档导航上下文可显式控制。下例对应“跨站、无用户激活”的主导航,请求头中的
sec-fetch-site 会是 cross-site,并省略 sec-fetch-user:
with Session("chrome_android_149") as session:
response = session.get(
"https://example.com/",
navigation_site="cross-site",
user_activation=False,
)
navigation_site 可取 none、same-origin、same-site、cross-site;这两个参数只适用于
Chrome profile,并会沿同一次原生重定向链传播。库复现显式请求本身,不额外创建浏览器进程
可能产生的预连接或 favicon 请求。
HTTP 代理
代理映射与 requests 风格相近:
from rex_tls import Session
proxies = {
"http": "http://user:password@proxy.example:8080",
"https": "http://user:password@proxy.example:8080",
"no_proxy": ".internal.example,127.0.0.1",
}
with Session("okhttp_4.12", proxies=proxies) as session:
response = session.get("https://example.com/")
也可在单次请求覆盖代理:
response = session.get(
"https://example.com/",
proxies={"https": "http://other-proxy.example:3128"},
)
# 显式为这一次请求禁用 HTTPS 代理
direct = session.get("https://example.com/", proxies={"https": None})
从环境变量读取 HTTP_PROXY、HTTPS_PROXY、ALL_PROXY、NO_PROXY:
with Session("chrome_android_149", trust_env=True) as session:
response = session.get("https://example.com/")
当前代理能力边界:
- 支持
http://forward proxy 和 HTTPS 目标的 HTTP CONNECT; - 支持代理 URL 中的 Basic 用户名/密码;
- 不支持
https://proxy、SOCKS、PAC、NTLM/Digest 或 MASQUE; - 代理失败不会静默改成直连;
- 不要把
Proxy-Authorization放入源站请求头,凭据应写在代理 URL 中; - 传统 HTTP CONNECT 不能承载 QUIC,因此代理场景不使用 HTTP/3。
强制 HTTP/2
需要服务端实际协商 H2 时使用 http2=True:
from rex_tls import Session
with Session("chrome_android_149", http2=True) as session:
response = session.get("https://example.com/")
assert response.http_version == "HTTP/2"
该选项保留 profile 已采集的 h2,http/1.1 ALPN ClientHello 向量,在握手后验证服务端确实
选择 h2;这避免为了“强制”而改变 TLS 指纹。若服务端选择 H1、URL 是明文 HTTP(当前
不支持 h2c),请求会明确失败。http2=True 与 http3="auto"/"only" 互斥。强制 H2
时不要提供 Connection 等 H1 专用头。
顶层 rex_tls.get(..., http2=True)、AsyncSession、SessionPool 和
AsyncSessionPool 同样支持这个构造选项。
HTTP/3
HTTP/3 只适用于两个显式 Chrome profile,并且只支持 HTTPS URL:
from rex_tls import Session
# 优先尝试 HTTP/3,并允许按协议策略回退到 H2/H1
with Session("chrome_android_149", http3="auto") as session:
response = session.get("https://example.com/")
# 只允许 HTTP/3;无法建立 H3 时直接报错
with Session("chrome_android_150", http3="only") as session:
response = session.get("https://example.com/")
http3 可取 "off"、"auto"、"only",默认是 "off"。使用 H3
时必须写完整的 chrome_android_149 或 chrome_android_150,不要使用别名。
okhttp_4.12 与 okhttp_5.4 均不支持 H3。okhttp 和 okhttp_latest 当前解析为
okhttp_5.4;需要长期复现时应固定完整 profile 名称。
异步接口
import asyncio
from rex_tls import AsyncSession
async def main() -> None:
async with AsyncSession("okhttp_4.12") as session:
response = await session.get("https://example.com/", timeout=15)
response.raise_for_status()
print(response.status_code, response.http_version)
asyncio.run(main())
取消 asyncio Task 时,客户端会尝试取消对应的原生请求。
内置并发池
SessionPool 面向多线程,AsyncSessionPool 面向 asyncio。每个池成员拥有独立的
TLS/HTTP 连接状态,因此最多可以同时运行 max_connections 个请求;超过上限的请求会
排队。普通 Session 本身可由多线程并发调用,AsyncSession(max_concurrency=8) 默认用
有界 worker 执行并发请求;同 route 的 HTTP/2 请求可复用一条 TCP/TLS 连接上的多个
stream。HTTP/1.1 和当前 HTTP/3 驱动仍按单连接串行。
import asyncio
from rex_tls import AsyncSessionPool
async def main() -> None:
async with AsyncSessionPool(
"chrome_android_150",
max_connections=16,
session_mode="shared",
proxy="http://user:password@proxy.example:8080",
) as pool:
pool.headers["x-client"] = "pool-demo"
responses = await asyncio.gather(
*(pool.get(f"https://example.com/?id={index}") for index in range(100))
)
print([response.status_code for response in responses])
asyncio.run(main())
池的可变会话状态有两种模式:
session_mode="shared"(默认):所有连接共享默认 Header、代理配置和线程安全 CookieJar,整个池表现为一个登录会话;session_mode="isolated":每个连接的 Header、代理配置和 Cookie 均完全独立,适合 多账号或任务隔离。需要连续使用同一身份时,用async with pool.acquire() as session:固定租用一个成员。
async with AsyncSessionPool(
"okhttp_4.12", max_connections=8, session_mode="isolated"
) as pool:
async with pool.acquire() as session:
session.headers["authorization"] = "Bearer account-one"
session.proxies["https"] = "http://account-one.proxy:8080"
session.cookies.set("account", "one")
first = await session.get("https://example.com/first")
second = await session.get("https://example.com/second")
同步代码可以在线程中共享一个池:
from concurrent.futures import ThreadPoolExecutor
from rex_tls import SessionPool
with SessionPool("okhttp_4.12", max_connections=8) as pool:
with ThreadPoolExecutor(max_workers=32) as executor:
responses = list(executor.map(pool.get, urls))
pool_timeout= 只控制等待空闲池成员的时间;timeout= 仍控制网络请求。池级
headers、proxies 和 trust_env 会应用到所有成员。
Response 对象
常用属性和方法:
response.status_code # int
response.reason # str
response.url # 最终 URL
response.http_version # HTTP/1.1、HTTP/2 或 HTTP/3
response.headers # 大小写不敏感的响应头映射
response.headers.raw # 保留顺序及重复项的 tuple
response.headers.get_all("set-cookie")
response.content # bytes
response.text # 按 charset 解码后的 str
response.json() # JSON 解码
response.ok # status_code < 400
response.elapsed_seconds
response.local_address
response.remote_address
response.connection_reused
response.tls_session_reused
response.raise_for_status()
TLS 证书验证
默认使用 certifi CA 包并验证证书与主机名:
from rex_tls import Session
# 默认验证
session = Session("chrome_android_149", verify=True)
# 使用自定义 CA bundle
session = Session("chrome_android_149", verify="/path/to/ca-bundle.pem")
# 仅用于受控测试环境
session = Session("chrome_android_149", verify=False)
异常处理
import rex_tls
try:
response = rex_tls.get(
"https://example.com/",
profile="chrome_android_149",
timeout=10,
)
response.raise_for_status()
except rex_tls.RequestError as exc:
print(f"请求失败: {exc}")
except rex_tls.MobileTLSError as exc:
print(f"原生客户端错误: {exc}")
查询运行时能力
import rex_tls
print(rex_tls.profiles())
print(rex_tls.profile_info("chrome_android_149"))
print(dict(rex_tls.native_versions()))
与 requests 的差异
rex-tls 对齐的是常用调用方式,不是 requests 的完全兼容替代:
- 当前没有公开的
auth、hooks、adapters和mount; Session.cookies提供常用的 RequestsCookieJar 风格set/get/get_dict/update/clear接口,并支持 domain、path、Secure 和 expires;单次请求也支持cookies=, 显式Cookie请求头优先;Session.headers与Session.proxies是可修改字典;- Cookie、重定向、连接池与 TLS 会话由原生 Session 管理;
verify、代理类型和 HTTP/3 有更严格的安全约束;- 请求头最终顺序由 profile 决定,而不是简单照搬 Python 字典顺序。
完整中文手册见 中文使用指南。
2.1.0 发布内容
- 提供
chrome_android_149、chrome_android_150、okhttp_4.12、okhttp_5.4四个固定版本 profile; - 提供 requests 风格的同步/异步 Session、CookieJar、单次
cookies=、代理配置和 shared/isolated 并发池; - Chrome profile 支持 HTTP/1.1、HTTP/2 与 HTTP/3,OkHttp profile 支持 HTTP/1.1 与 HTTP/2;
- 请求头合并与最终 wire 顺序由底层 profile 规则处理;
- 发布 Windows x86-64 与 manylinux 2.28 x86-64 两个 CPython 3.9+ abi3 wheel, 不上传 macOS wheel 或源码包。
2.1.0 的协议核心继承已完成真实 Pixel 4 / Android 11 数据包与会话验收的 0.1.2 候选;
本次版本变更不修改 src、vendored BoringSSL 或 profile 实现。CI 会逐平台构建并直接测试
最终上传的同一份 wheel,同时检查协议实现相对已验收基线没有漂移。
0.1.2 稳定性改进(2.1.0 已包含)
- 接受服务端合法的 HTTP/2
SETTINGS_ENABLE_PUSH=0,继续拒绝非法值1; - TLS 对端未发送
close_notify时,由 HTTP framing 状态机判断完整性:完整响应可返回, 不完整的 Content-Length、chunked 或 HTTP/2 响应仍会失败,连接不会复用; - 空闲 H1/H2 连接默认最多 32 条、90 秒过期;TLS 与 H3 ticket 默认最多 64 个、 10 分钟过期;H3 Alt-Svc 路由最多 64 条并按 LRU 淘汰;
- 阻塞式系统 DNS 解析使用进程级有界 worker/队列,超时或取消不会无限创建后台线程;
Session.close()会同时释放空闲连接和缓存的会话票据。
这些是资源边界与协议互操作修复,不改变四个 profile 的 ClientHello、HTTP/2 客户端 SETTINGS、帧顺序或请求头 wire 顺序。Flutter/Dart profile 归入 OkHttp 5.x 之后的规划; Flutter 默认 Dart IO、Android Cronet 与 iOS URLSession 会分别建模。
开发与验证
python -m pip install -e ".[dev]"
python -m pytest -m "not network" -q
架构和证据说明位于 docs/;发布工作流见
docs/32-github-ci-and-publishing.md,本机四库性能
基准见 docs/34-performance.md。
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rex_tls-2.1.0-cp39-abi3-win_amd64.whl.
File metadata
- Download URL: rex_tls-2.1.0-cp39-abi3-win_amd64.whl
- Upload date:
- Size: 1.8 MB
- Tags: CPython 3.9+, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ea96312bfec236a3158ca552d8d62f7d598bc387f580b836bb5df1c8a6af920
|
|
| MD5 |
7529dce82cd7ce10d3f903ec2834a321
|
|
| BLAKE2b-256 |
ef0ffa54531eaae064ef56d97a6dbab9e725016674727b36eca824202f6a8ef9
|
File details
Details for the file rex_tls-2.1.0-cp39-abi3-manylinux_2_28_x86_64.whl.
File metadata
- Download URL: rex_tls-2.1.0-cp39-abi3-manylinux_2_28_x86_64.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.9+, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0cef9b26c78aa185b301a15b756b2009ee0239a516740832fc2e7e8a446bf278
|
|
| MD5 |
14119b6dec2d354428a356dc4d71bce2
|
|
| BLAKE2b-256 |
3d2076c7464bb199c487a4818e3718145331f46f0ed1fe2f9427220f7ea24b0e
|