Metadata-Version: 2.4
Name: rex-tls
Version: 0.1.1
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Rust
Classifier: Topic :: Internet :: WWW/HTTP
Requires-Dist: certifi>=2024
Requires-Dist: maturin>=1.9,<2 ; extra == 'dev'
Requires-Dist: pytest>=8,<10 ; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24,<2 ; extra == 'dev'
Requires-Dist: trustme>=1.2.1,<2 ; extra == 'dev'
Requires-Dist: websocket-client>=1.8,<2 ; extra == 'dev'
Requires-Dist: h2>=4.3,<4.4 ; python_full_version < '3.10' and extra == 'dev'
Requires-Dist: h2>=4.4,<5 ; python_full_version >= '3.10' and extra == 'dev'
Provides-Extra: dev
License-File: LICENSE
Summary: Python HTTP client with native Android Chrome and OkHttp TLS/HTTP2 profiles
Keywords: tls,http2,android,chrome,okhttp,boringssl
Author: rex-tls contributors
License: MIT
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM

# 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。当前版本已经发布到 PyPI：
[rex-tls 0.1.1](https://pypi.org/project/rex-tls/0.1.1/)。

> 适用范围：协议兼容测试、移动客户端互操作、经过授权的网络研究和普通
> 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 |

三套 profile 均完成真实设备 L4 行为验证。OkHttp 4.12 不声明 HTTP/3 能力。

## 安装

```bash
python -m pip install rex-tls==0.1.1
```

要求 Python 3.9 或更高版本。0.1.1 提供以下预编译 wheel：

- Windows x86-64
- Linux manylinux 2.28 x86-64
- macOS x86-64
- macOS ARM64

首选导入名是 `rex_tls`；`mobile_tls` 仅作为兼容入口保留。

## 快速开始

```python
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：

```python
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 会话，连续请求时应优先使用：

```python
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)
```

## 请求参数与请求体

```python
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`。

## 请求头顺序

底层会自动按选定 profile 的设备规则排列请求头：

- 覆盖默认头时，头部保留在真实设备对应的槽位；
- 新增的非默认头在 profile 默认头之后，按调用者传入顺序排列；
- 需要重复同名头时，可传入元组列表；
- Session 级请求头会先与单次请求头进行大小写不敏感合并；
- `Host`、HTTP/2/HTTP/3 伪头及 `Content-Length` 由底层安全生成。

```python
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"),
        ],
    )
```

不需要手工复制全部 Chrome/OkHttp 默认请求头。只传入需要覆盖或追加的头即可。

## HTTP 代理

代理映射与 `requests` 风格相近：

```python
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/")
```

也可在单次请求覆盖代理：

```python
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`：

```python
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/3

HTTP/3 只适用于两个显式 Chrome profile，并且只支持 HTTPS URL：

```python
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` 不支持 H3。

## 异步接口

```python
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` 个请求；超过上限的请求会
排队。当前并发来自多条连接，不表示在单条 HTTP/2 或 HTTP/3 连接内多路复用。

```python
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:` 固定租用一个成员。

```python
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")
```

同步代码可以在线程中共享一个池：

```python
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 对象

常用属性和方法：

```python
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 包并验证证书与主机名：

```python
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)
```

## 异常处理

```python
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}")
```

## 查询运行时能力

```python
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 字典顺序。

完整中文手册见 [中文使用指南](docs/33-usage-zh-CN.md)。

## 开发与验证

```bash
python -m pip install -e ".[dev]"
python -m pytest -m "not network" -q
```

架构和证据说明位于 [`docs/`](docs/)；发布工作流见
[`docs/32-github-ci-and-publishing.md`](docs/32-github-ci-and-publishing.md)，本机四库性能
基准见 [`docs/34-performance.md`](docs/34-performance.md)。

## License

MIT

