Metadata-Version: 2.4
Name: dartio-tls
Version: 0.2.0
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Summary: Python HTTP client (sync + async) that speaks TLS like Flutter dart:io (BoringSSL), with a Rust core
Keywords: http-client,tls,boringssl,dart,flutter,rust
Author: you-just
License: MIT
Requires-Python: >=3.8
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/you-just/dartio-tls
Project-URL: Issues, https://github.com/you-just/dartio-tls/issues
Project-URL: Repository, https://github.com/you-just/dartio-tls

# dartio-tls

[English](#english) | [中文](#中文)

---

## English

A Python HTTP client that speaks TLS the way Flutter's `dart:io` does —
same embedded BoringSSL, same handshake shape (cipher suites and order,
extensions, signature algorithms, curves, ALPN) — with a Rust core and
both synchronous and asyncio interfaces.

`dart:io` (the networking library inside every Flutter app) produces a
TLS ClientHello that is distinct from browser or `requests`/`httpx`
stacks. This library uses the **same TLS library** (`BoringSSL`, via the
`boring` crate) configured the same way, so the bytes on the wire match
what a real Flutter `dart:io` client sends.

### Features

- **Flutter `dart:io` TLS shape** out of the box, per platform:
  `dartio_3_33_0_android` (ChaCha20-first, as on Android devices) and
  `dartio_3_33_0_desktop` (AES-GCM-first). The shape is fixed by the profile,
  not by the host CPU, so a Linux x86 server emits the Android shape exactly
- **Sync + Async clients**: blocking `Client` and asyncio-native
  `AsyncClient` (Rust tokio runtime underneath)
- **Ordered headers**: request headers are serialized in the exact order
  you provide (pass `list[tuple]`; plain `dict` also accepted)
- **HTTP/1.1** with strict framing: content-length / chunked / close
  response bodies, HEAD/204/304 handling
- **Keep-alive connection pool** like `dart:io` `HttpClient` (idle timeout
  15 s by default), plus transparent gzip decoding
- **Proxy support** per client or per request (`http://user:pass@host:port`,
  CONNECT tunneling for https targets)
- **Cookie jar**, automatic redirects (301/302/303 → GET, 307/308 keep
  method), per-request timeout
- **Embedded CA store** (Mozilla bundle) — no dependence on the system
  certificate store
- **Typed**: ships a `.pyi` stub; every public method has a docstring
- Prebuilt wheels for macOS (Apple Silicon), Linux (x86_64 and aarch64,
  manylinux_2_28), and Windows (x86_64); one wheel per platform covers
  Python ≥ 3.8 (abi3)

### Install

```bash
pip install dartio-tls
```

### Usage

Synchronous:

```python
import dartio_tls

with dartio_tls.Client(impersonate="dartio_3_33_0_android", timeout=20.0) as client:
    resp = client.post(
        "https://example.com/api",
        headers=[("user-agent", "Dart/3.9"), ("content-type", "application/json")],
        json={"q": "x"},
    )
    print(resp.status_code, resp.headers.get("content-type"), resp.json())
```

Asynchronous:

```python
import asyncio
import dartio_tls

async def main():
    async with dartio_tls.AsyncClient(impersonate="dartio_3_33_0_android") as client:
        r1, r2 = await asyncio.gather(
            client.get("https://example.com/a"),
            client.post("https://example.com/b", json={"k": 1}),
        )

asyncio.run(main())
```

Per-request proxy override:

```python
resp = client.get(url, proxy="http://user:pass@host:port")
```

### API

- Constructors (`Client` / `AsyncClient`):
  `impersonate`, `proxy`, `cookie_store`, `timeout`,
  `follow_redirects`, `max_redirects`, `verify`,
  `pool` (keep-alive, default on), `idle_timeout` (seconds, default 15),
  `auto_uncompress` (gzip, default on)
- Methods: `get / post / put / delete / patch / head / options /
  request(method, ...)`, each accepting `headers`, `content` (bytes),
  `json` (auto-serialized, sets content-type), `proxy`;
  `close()` and `with` / `async with` (`AsyncClient` also has `aclose()`)
- Response: `status_code`, `headers`, `raw_headers` (ordered list of
  tuples), `content` (bytes), `text`, `json()`, `url` (final URL after redirects)
- `Headers`: case-insensitive `get(name, default)`, `headers[name]`,
  `name in headers`, `get_all(name)`, `keys()` / `values()` / `items()`,
  `dict(headers)`. Iterating, integer indexing and `len()` keep the 0.1.x
  list-of-tuples behaviour
- Exceptions, all subclasses of `DartioError(RuntimeError)`:
  `BuilderError`, `ConnectError`, `ProxyError`, `TLSError`,
  `TimeoutException`, `NetworkError`, `DecodeError`, `ClientClosedError`

Profiles: `dartio_3_33_0_android`, `dartio_3_33_0_desktop`
(`dartio_3_33_0` is an alias of the desktop profile and remains the default).
`dartio_tls.profiles()` lists them.

### License

MIT

---

## 中文

以 Flutter `dart:io` 的 TLS 形态发包的 Python HTTP 客户端——同一个内嵌
BoringSSL、同样的握手形态（cipher 套件与顺序、扩展、签名算法、曲线、
ALPN）——Rust 内核，同步 + asyncio 双面。

`dart:io`（每个 Flutter App 内嵌的网络库）产生的 TLS ClientHello 与浏览器
或 `requests`/`httpx` 栈都不同。本库与 `dart:io` 使用**同一个 TLS 库**
（BoringSSL，经 `boring` crate）按同样方式配置，线上字节与真实 Flutter
`dart:io` 客户端一致。

### 特性

- **Flutter `dart:io` TLS 形态**开箱即用，按平台分档：
  `dartio_3_33_0_android`（ChaCha20 优先，同安卓真机）与
  `dartio_3_33_0_desktop`（AES-GCM 优先）。形态由档决定、不看宿主 CPU，
  Linux x86 服务器上也能精确发出安卓形
- **同步 + 异步双面**：阻塞式 `Client` 与 asyncio 原生 `AsyncClient`
  （底层为 Rust tokio runtime）
- **请求头保序**：头按你给的顺序原样上线（传 `list[tuple]`；普通
  `dict` 也收）
- **HTTP/1.1** 严格帧形：content-length / chunked / close 三种响应体、
  HEAD/204/304 处理
- **keep-alive 连接池**，对齐 `dart:io` `HttpClient`（空闲超时缺省 15 秒）；
  gzip 响应自动解压
- **代理支持**：per-client 或 per-request（`http://user:pass@host:port`，
  https 目标走 CONNECT 隧道）
- **cookie jar**、自动重定向（301/302/303 转 GET，307/308 保方法）、
  请求级超时
- **内嵌 CA store**（Mozilla bundle），不依赖系统证书库
- **带类型**：随包提供 `.pyi` 存根，公开方法都有 docstring
- 预编译 wheel：macOS（Apple Silicon）、Linux（x86_64 与 aarch64，
  manylinux_2_28）、Windows（x86_64）；每平台一个 wheel 通吃 Python ≥ 3.8（abi3）

### 安装

```bash
pip install dartio-tls
```

### 用法

同步：

```python
import dartio_tls

with dartio_tls.Client(impersonate="dartio_3_33_0_android", timeout=20.0) as client:
    resp = client.post(
        "https://example.com/api",
        headers=[("user-agent", "Dart/3.9"), ("content-type", "application/json")],
        json={"q": "x"},
    )
    print(resp.status_code, resp.headers.get("content-type"), resp.json())
```

异步：

```python
import asyncio
import dartio_tls

async def main():
    async with dartio_tls.AsyncClient(impersonate="dartio_3_33_0_android") as client:
        r1, r2 = await asyncio.gather(
            client.get("https://example.com/a"),
            client.post("https://example.com/b", json={"k": 1}),
        )

asyncio.run(main())
```

per-request 代理覆盖：

```python
resp = client.get(url, proxy="http://user:pass@host:port")
```

### API

- 构造（`Client` / `AsyncClient`）：`impersonate`、`proxy`、
  `cookie_store`、`timeout`、`follow_redirects`、`max_redirects`、`verify`、
  `pool`（连接复用，缺省开）、`idle_timeout`（秒，缺省 15）、
  `auto_uncompress`（gzip 解压，缺省开）
- 方法：`get / post / put / delete / patch / head / options /
  request(任意方法)`，均收 `headers`、`content`（bytes）、`json`
  （自动序列化并补 content-type）、`proxy`；`close()` 与 `with` /
  `async with`（`AsyncClient` 另有 `aclose()`）
- Response：`status_code`、`headers`、`raw_headers`（保序 tuple 列表）、
  `content`（bytes）、`text`、`json()`、`url`（跳转后最终 URL）
- `Headers`：大小写不敏感的 `get(name, default)`、`headers[name]`、
  `name in headers`、`get_all(name)`、`keys()` / `values()` / `items()`、
  `dict(headers)`；迭代、整数下标和 `len()` 保持 0.1.x 的 tuple 列表语义
- 异常全部继承 `DartioError(RuntimeError)`：`BuilderError`、`ConnectError`、
  `ProxyError`、`TLSError`、`TimeoutException`、`NetworkError`、
  `DecodeError`、`ClientClosedError`

可用档：`dartio_3_33_0_android`、`dartio_3_33_0_desktop`（`dartio_3_33_0`
是桌面档别名，仍为缺省）。`dartio_tls.profiles()` 可列出。

### 许可证

MIT

