gohttpx 1.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
gohttpx-1.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Assistest
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
gohttpx-1.0.1/PKG-INFO ADDED
@@ -0,0 +1,417 @@
1
+ Metadata-Version: 2.4
2
+ Name: gohttpx
3
+ Version: 1.0.1
4
+ Summary: 通过本地 Go 服务扩展 HTTPX 请求能力
5
+ License: MIT
6
+ Project-URL: Homepage, https://github.com/Assistest/GoHTTPX
7
+ Classifier: License :: OSI Approved :: MIT License
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: httpx<0.29,>=0.28
14
+ Dynamic: license-file
15
+
16
+ # GoHTTPX Bridge v1
17
+
18
+ ## Python PyPI 安装
19
+
20
+ ```powershell
21
+ pip install gohttpx
22
+ ```
23
+
24
+ Go 服务端不属于 pip 包。请从 [GitHub Releases](https://github.com/Assistest/GoHTTPX/releases) 获取与 Python SDK 同标签的 Windows amd64 安装包,并校验随附 SHA-256 文件。Go 服务端不可达时会抛出 `GoServiceUnavailable`,消息会指向 Releases 地址。
25
+
26
+ ```python
27
+ from gohttpx import Client, GoServiceUnavailable
28
+
29
+ try:
30
+ with Client(go_token="replace-with-secret") as client:
31
+ client.get("https://example.test/")
32
+ except GoServiceUnavailable as error:
33
+ print(error)
34
+ ```
35
+
36
+ GoHTTPX 在本机常驻一个 Go 发包服务,让 Python 继续使用 HTTPX 0.28 的 `Client`、`AsyncClient`、请求编码、cookies、auth、redirect 和 `Response` 语义,同时把目标请求交给 req/v3、uTLS 或 HTTP/3 Transport 执行。
37
+
38
+ 当前发布版本为 `1.0.0`,控制协议为 `/api/v1`、`protocol_version=1`。依赖固定为 req/v3 `v3.59.0`、quic-go `v0.60.0`、uTLS `v1.8.2`;Python 最低版本为 3.10,要求 `httpx>=0.28,<0.29`。
39
+
40
+ 公开发布与手动部署请同时阅读 [更新记录](CHANGELOG.md) 和 [运维说明](RUNBOOK.md)。每个发布二进制的 SHA-256、大小和源码 revision 以对应 GitHub Release 为准。
41
+
42
+ ## 架构与状态边界
43
+
44
+ 一个由运维人员手动启动的 loopback Go 服务,可以由同一 Python 后端中的多个站点模块共享。同步 `Client` 在构造时立即创建 Go `req.Client` 会话;异步 `AsyncClient` 在第一次请求时懒创建。每个已经创建会话的 client 都对应一个独立 Go session,因此 TLS、代理、HTTP 版本、连接池和重试配置互不串用。
45
+
46
+ HTTPX 负责 params、headers、cookies、Basic/Digest auth、redirect、`json/data/files/content` 编码以及最终 `Response`;Go 会话只保存底层网络配置。业务 cookies、headers 和 auth 不会在 Go 中持久化,控制 API 的 bearer token 也不会转发给目标站点。Python 不会启动或停止 Go 服务。
47
+
48
+ ```text
49
+ 站点模块 -> httpx.Client / httpx.AsyncClient
50
+ -> GoTransport -> 127.0.0.1 /api/v1
51
+ -> 独立 req.Client 会话 -> 目标站点
52
+ ```
53
+
54
+ ## Windows 构建与启动
55
+
56
+ 在本目录执行:
57
+
58
+ ```powershell
59
+ go build -trimpath -ldflags="-s -w" -o gohttpx-server.exe .
60
+ .\gohttpx-server.exe --host 127.0.0.1 --port 9876 --token <secret>
61
+ ```
62
+
63
+ 也可以把 token 放入当前进程环境。显式 `--token` 的优先级高于 `GOHTTPX_TOKEN`:
64
+
65
+ ```powershell
66
+ $env:GOHTTPX_TOKEN = "replace-with-a-long-random-secret"
67
+ .\gohttpx-server.exe --host 127.0.0.1 --port 9876
68
+ ```
69
+
70
+ 正式模式 token 不能为空。只有明确传入 `--insecure-no-auth` 才会关闭鉴权,该模式仅用于本机开发。默认禁止监听非 loopback 地址;`--allow-non-loopback` 是显式危险开关,本机 bearer 设计不应被当作公网认证方案。
71
+
72
+ 常用 CLI:
73
+
74
+ | 参数 | 默认值 | 含义与边界 |
75
+ |---|---:|---|
76
+ | `--host` | `127.0.0.1` | 监听地址;非 `localhost`/loopback 必须同时传 `--allow-non-loopback`。 |
77
+ | `--port` | `9876` | `1..65535`。 |
78
+ | `--token` | `GOHTTPX_TOKEN` | bearer token;显式参数覆盖环境变量。 |
79
+ | `--insecure-no-auth` | `false` | 清空 token 并关闭鉴权,仅限开发。 |
80
+ | `--allow-non-loopback` | `false` | 允许非 loopback 监听。 |
81
+ | `--max-body-mib` | `48` | 单次目标请求正文和响应正文的内存上限,正整数 MiB。 |
82
+ | `--idle-ttl` | `24h` | 无活动 Go 会话的回收时间,必须大于 0。 |
83
+ | `--version` | `false` | 不需要 token、不会监听端口,打印 server、protocol、req/v3、uTLS 实际构建版本后退出 0。 |
84
+
85
+ 版本、健康和能力检查:
86
+
87
+ ```powershell
88
+ .\gohttpx-server.exe --version
89
+ Invoke-RestMethod http://127.0.0.1:9876/api/v1/health
90
+ Invoke-RestMethod http://127.0.0.1:9876/api/v1/capabilities -Headers @{Authorization="Bearer $env:GOHTTPX_TOKEN"}
91
+ ```
92
+
93
+ `health` 无需鉴权,固定返回 `status`、`protocol_version`、`server_version`。正常模式下 `capabilities` 需要 bearer;只有显式使用 `--insecure-no-auth` 的本机调试模式才免鉴权。v1 capabilities 精确返回四个字段:`protocol_version`、`server_version`、`max_body_bytes`、`tls_fingerprints`。
94
+
95
+ ## Python 单文件接入
96
+
97
+ 把 `python/gohttpx.py` 复制到项目的可导入目录;它不是已发布的 pip 包。项目需自行安装兼容的 HTTPX 0.28.x。同步客户端在构造时检查 capabilities 并创建 Go 会话;异步客户端在第一次请求时懒创建会话。
98
+
99
+ ### 同步示例
100
+
101
+ ```python
102
+ import httpx
103
+
104
+ from gohttpx import Client, RequestOptions
105
+
106
+ with Client(
107
+ go_endpoint="http://127.0.0.1:9876",
108
+ go_token="replace-with-secret",
109
+ headers={"User-Agent": "my-backend/1.0"},
110
+ cookies={"site": "one"},
111
+ follow_redirects=True,
112
+ ) as client:
113
+ query = client.get("https://example.test/search", params=[("q", "a"), ("q", "b")])
114
+ created = client.post("https://example.test/items", json={"name": "test"})
115
+ form = client.post("https://example.test/form", data={"name": "test"})
116
+ upload = client.post("https://example.test/upload", files={"file": ("a.bin", b"abc")})
117
+ raw = client.post("https://example.test/raw", content=b"\x00\xff")
118
+ traced = client.get(
119
+ "https://example.test/trace",
120
+ extensions={"go_req": RequestOptions(trace=True, dump=True, retry_count=1)},
121
+ )
122
+
123
+ created.raise_for_status()
124
+ print(query.url, form.status_code, upload.headers, raw.content)
125
+ print(traced.extensions.get("go_trace"))
126
+ print(traced.extensions.get("go_dump"))
127
+ ```
128
+
129
+ `json`、`data`、`files`、`content` 都是 HTTPX 原生参数;HTTPX 先编码最终 bytes,Go 原样发送。默认 headers、cookies、重复 query、redirect history 也由 HTTPX 处理。
130
+
131
+ Basic 与 Digest auth 使用 HTTPX 的真实 auth 对象:
132
+
133
+ ```python
134
+ import httpx
135
+
136
+ from gohttpx import Client
137
+
138
+ with Client(go_token="secret", auth=("user", "pass")) as client:
139
+ basic = client.get("https://example.test/basic")
140
+
141
+ with Client(go_token="secret", auth=httpx.DigestAuth("user", "pass")) as client:
142
+ digest = client.get("https://example.test/digest")
143
+ ```
144
+
145
+ ### 异步示例
146
+
147
+ ```python
148
+ import asyncio
149
+
150
+ from gohttpx import AsyncClient
151
+
152
+
153
+ async def main() -> None:
154
+ async with AsyncClient(
155
+ go_endpoint="http://127.0.0.1:9876",
156
+ go_token="replace-with-secret",
157
+ headers={"X-Site": "demo"},
158
+ cookies={"session": "one"},
159
+ follow_redirects=True,
160
+ ) as client:
161
+ response = await client.post(
162
+ "https://example.test/items",
163
+ params=[("source", "a"), ("source", "b")],
164
+ json={"name": "async"},
165
+ )
166
+ response.raise_for_status()
167
+ print(response.json(), response.history)
168
+
169
+
170
+ asyncio.run(main())
171
+ ```
172
+
173
+ `Client` 和 `AsyncClient` 的会话配置入口是 `client_options=ClientOptions(...)`;`tls_fingerprint`、`impersonate`、`verify`、`cert`、`proxy`、`http1`、`http2` 是当前签名支持的固定会话便利参数。单次 `RequestOptions` 不属于构造参数,只能放在 `extensions={"go_req": ...}`。公开签名还接受 HTTPX 的 `auth`、`params`、`headers`、`cookies`、`timeout`、`follow_redirects`、`max_redirects`、`event_hooks`、`base_url`、`default_encoding`;`transport` 与 `mounts` 明确禁止,避免绕过 Go Transport。
174
+
175
+ 当前签名正式不提供 `limits` 或 `trust_env` 便利参数。HTTPX 的全局/按 URL 环境代理规则无法无损映射到一个配置固定的 Go session,控制连接也固定不读取代理环境变量;连接池和固定代理分别使用 `ClientOptions.transport` 与 `ClientOptions.proxy_url` 显式配置。`proxy=` 便利参数同样只生成一个固定会话代理,不表示支持环境代理或 per-URL mounts。
176
+
177
+ `go_token=None` 时读取 `GOHTTPX_TOKEN`;显式字符串(包括空字符串)不再读取环境变量。
178
+
179
+ ## TLS、代理、证书和 HTTP 版本
180
+
181
+ ```python
182
+ import httpx
183
+
184
+ from gohttpx import Client, ClientOptions, TLSFingerprint
185
+
186
+ # TLS 指纹
187
+ with Client(go_token="secret", tls_fingerprint=TLSFingerprint.CHROME_120) as client:
188
+ response = client.get("https://example.test/")
189
+
190
+ # 固定代理;Proxy 的 auth 和 headers 子集会被序列化
191
+ proxy = httpx.Proxy("http://proxy.example:8080", auth=("user", "pass"), headers={"X-Proxy": "one"})
192
+ with Client(go_token="secret", proxy=proxy) as client:
193
+ response = client.get("https://example.test/")
194
+
195
+ # 自定义根 CA 与 mTLS
196
+ with Client(
197
+ go_token="secret",
198
+ verify=r"C:\certs\root-ca.pem",
199
+ cert=(r"C:\certs\client.pem", r"C:\certs\client-key.pem"),
200
+ ) as client:
201
+ response = client.get("https://example.test/")
202
+
203
+ # HTTP/1.1、HTTP/2、HTTP/3、H2C
204
+ http1 = Client(go_token="secret", client_options=ClientOptions(http_version="http1"))
205
+ http2 = Client(go_token="secret", client_options=ClientOptions(http_version="http2"))
206
+ http3 = Client(go_token="secret", client_options=ClientOptions(http_version="http3", tls_fingerprint=None))
207
+ h2c = Client(go_token="secret", client_options=ClientOptions(http_version="h2c"))
208
+ for client in (http1, http2, http3, h2c):
209
+ client.close()
210
+ ```
211
+
212
+ `verify` 只支持 `bool` 或 CA PEM 文件路径,不接受自定义 `ssl.SSLContext`。`cert` 支持一个同时含证书和私钥的 PEM 文件,或 `(证书路径, 私钥路径)`。`httpx.Proxy` 若含自定义 SSLContext 会被拒绝。`proxy_url` 支持 `http`、`https`、`socks5`、`socks5h`。
213
+
214
+ 组合限制:
215
+
216
+ - `impersonate` 与任何显式 `tls_fingerprint` 互斥;impersonate 可选 `none/chrome/firefox/safari`。
217
+ - proxy 不能与强制 `http2`、`http3`、`h2c` 组合;proxy 与 `auto/http1` 可用。
218
+ - HTTP/3 不接受显式 TLS fingerprint 或非 `none` impersonate,使用标准 QUIC TLS。
219
+ - 强制 HTTPS HTTP/2 不接受显式 TLS fingerprint 或非 `none` impersonate;省略两者时使用 req 的标准 TLS 以协商 `h2`,并支持 mTLS。
220
+ - HTTP/3 支持 verify、root CA、client cert/key、compression、GET body、retry、trace、dump、单次 timeout 和 `max_response_header_bytes`。
221
+ - HTTP/3 将 `tls_handshake_timeout_ms` 映射为 QUIC `HandshakeIdleTimeout`,将 `idle_conn_timeout_ms` 映射为 QUIC `MaxIdleTimeout`。直接发送 0 时采用 quic-go 的 5 秒/30 秒默认;Python `TransportOptions` 默认会显式发送 10000/90000 ms。
222
+ - HTTP/3 的 proxy、HTTP 阶段 timeout、TCP pool/buffer 选项和非默认 HTTP/2 嵌套选项会在创建会话时返回 `INVALID_REQUEST`。空 map/slice 和数值 0 视为默认。
223
+ - HTTP/3 请求的非空 `header_order`、`pseudo_header_order`,以及 `force_chunked=true`、`close_connection=true` 会返回 `INVALID_REQUEST`。
224
+ - `keep_alive=false` 会在每次 HTTP/3 响应完整读取后关闭空闲 QUIC 连接;`true` 允许复用。
225
+
226
+ ### TLSFingerprint 全部 49 个值
227
+
228
+ | 家族 | 值 |
229
+ |---|---|
230
+ | Go/随机 | `golang`, `randomized`, `randomized_alpn`, `randomized_no_alpn` |
231
+ | Android | `android_11_okhttp` |
232
+ | Chrome | `chrome_auto`, `chrome_58`, `chrome_62`, `chrome_70`, `chrome_72`, `chrome_83`, `chrome_87`, `chrome_96`, `chrome_100`, `chrome_102`, `chrome_106_shuffle`, `chrome_100_psk`, `chrome_112_psk_shuffle`, `chrome_114_padding_psk_shuffle`, `chrome_115_pq`, `chrome_115_pq_psk`, `chrome_120`, `chrome_120_pq`, `chrome_131`, `chrome_133` |
233
+ | Firefox | `firefox_auto`, `firefox_55`, `firefox_56`, `firefox_63`, `firefox_65`, `firefox_99`, `firefox_102`, `firefox_105`, `firefox_120` |
234
+ | iOS | `ios_auto`, `ios_11_1`, `ios_12_1`, `ios_13`, `ios_14` |
235
+ | Edge | `edge_auto`, `edge_85`, `edge_106` |
236
+ | Safari | `safari_auto`, `safari_16_0` |
237
+ | 360 | `360_auto`, `360_7_5`, `360_11_0` |
238
+ | QQ | `qq_auto`, `qq_11_1` |
239
+
240
+ `ClientOptions.tls_fingerprint` 的 Python 默认是 `None`;当 HTTP 版本为 `auto/http1/h2c` 且 impersonate 为 `none` 时,SDK 的有效默认是 `android_11_okhttp`。强制 HTTPS HTTP/2 与 HTTP/3 使用标准 TLS。
241
+
242
+ ## 完整 DTO 字段矩阵
243
+
244
+ 所有时间字段单位均为毫秒。配置 JSON 总大小上限为 4 MiB。表中的“0=默认”表示不调用对应 req 设置;HTTP/3 的例外单独列出。
245
+
246
+ ### ClientOptions
247
+
248
+ | 字段 | Python 类型 | 默认 | 含义、边界与 HTTP/3 规则 |
249
+ |---|---|---:|---|
250
+ | `tls_fingerprint` | `TLSFingerprint | str | None` | `None` | `auto/http1/h2c` 且无 impersonate 时有效默认 `android_11_okhttp`;强制 HTTPS HTTP/2、HTTP/3 必须省略。 |
251
+ | `impersonate` | `Impersonate | str` | `none` | `none/chrome/firefox/safari`;非 `none` 与显式 fingerprint 互斥,强制 HTTPS HTTP/2、HTTP/3 拒绝。 |
252
+ | `proxy_url` | `str | None` | `None` | 固定 `http/https/socks5/socks5h` URL;不能与强制 HTTP/2、HTTP/3、H2C 组合。 |
253
+ | `verify` | `bool` | `True` | 是否校验证书;HTTP/3 生效。 |
254
+ | `root_ca_pem` | `str | None` | `None` | 一个或多个纯 `CERTIFICATE` PEM,禁止夹杂其他字节/块;HTTP/3 生效。 |
255
+ | `client_cert_pem` | `str | None` | `None` | 客户端证书 PEM;必须和 key 同时提供;HTTP/3 生效。 |
256
+ | `client_key_pem` | `str | None` | `None` | 客户端私钥 PEM;必须和 cert 匹配;HTTP/3 生效。 |
257
+ | `http_version` | `str` | `auto` | `auto/http1/http2/http3/h2c`。 |
258
+ | `keep_alive` | `bool` | `True` | TCP/QUIC 连接复用;HTTP/3 false 时每次完整响应后关闭空闲连接。 |
259
+ | `compression` | `bool` | `False` | req/QUIC 原生压缩协商;默认关闭以保持 HTTPX 的 `Accept-Encoding` 与正文一致。 |
260
+ | `allow_get_body` | `bool` | `True` | 是否允许 GET 携带 body;HTTP/3 生效。 |
261
+ | `retry` | `RetryOptions` | `RetryOptions()` | 会话级重试,见下表;HTTP/3 生效。 |
262
+ | `transport` | `TransportOptions` | `TransportOptions()` | 连接与 Transport 配置,见下表。 |
263
+ | `http2` | `HTTP2Options` | `HTTP2Options()` | HTTP/2 帧和 timeout 配置;HTTP/3 只接受默认/零值。 |
264
+
265
+ ### RetryOptions
266
+
267
+ | 字段 | 类型 | 默认 | 边界与规则 |
268
+ |---|---|---:|---|
269
+ | `count` | `int` | `0` | `0..10`;0 时 mode 必须为 `none`。 |
270
+ | `mode` | `str` | `none` | `none/fixed/backoff`;count>0 时必须是 fixed 或 backoff。 |
271
+ | `fixed_interval_ms` | `int` | `0` | `0..600000`;fixed 模式必须大于 0,其他模式必须为 0。 |
272
+ | `backoff_min_ms` | `int` | `0` | `0..600000`;backoff 模式要求 `0 < min <= max`,其他模式为 0。 |
273
+ | `backoff_max_ms` | `int` | `0` | `0..600000`;规则同上。 |
274
+ | `status_codes` | `tuple[int, ...]` | `()` | 每项 `100..599` 且不得重复;none 模式必须为空。配置后这些状态码和网络错误触发 req 重试。 |
275
+
276
+ ### TransportOptions
277
+
278
+ | 字段 | 类型 | 默认 | 单位/边界 | HTTP/3 |
279
+ |---|---|---:|---|---|
280
+ | `tls_handshake_timeout_ms` | `int` | `10000` | ms,`0..600000` | 映射 QUIC 握手空闲 timeout。 |
281
+ | `response_header_timeout_ms` | `int` | `0` | ms,`0..600000` | 非 0 拒绝。 |
282
+ | `expect_continue_timeout_ms` | `int` | `1000` | ms,`0..600000` | 仅 0 可用;SDK 在默认 HTTP/3 配置中发送 0。 |
283
+ | `idle_conn_timeout_ms` | `int` | `90000` | ms,`0..600000` | 映射 QUIC 最大空闲 timeout。 |
284
+ | `max_idle_conns` | `int` | `100` | `0..100000` | 仅 0 可用;SDK 在默认 HTTP/3 配置中发送 0。 |
285
+ | `max_idle_conns_per_host` | `int` | `0` | `0..100000` | 非 0 拒绝。 |
286
+ | `max_conns_per_host` | `int` | `0` | `0..100000` | 非 0 拒绝。 |
287
+ | `max_response_header_bytes` | `int` | `0` | bytes,`0..16777216` | 生效。 |
288
+ | `read_buffer_size` | `int` | `0` | bytes,`0..16777216` | 非 0 拒绝。 |
289
+ | `write_buffer_size` | `int` | `0` | bytes,`0..16777216` | 非 0 拒绝。 |
290
+ | `proxy_connect_headers` | `Mapping[str, Sequence[str]]` | `{}` | CONNECT header 名必须是 HTTP token,值不得含控制字符 | 非空拒绝。 |
291
+
292
+ ### HTTP2Options 与嵌套 DTO
293
+
294
+ | 字段 | 类型 | 默认 | 边界/含义 | HTTP/3 |
295
+ |---|---|---:|---|---|
296
+ | `settings` | `tuple[HTTP2Setting, ...]` | `()` | SETTINGS 列表;ID 不得重复 | 非空拒绝。 |
297
+ | `connection_flow` | `int | None` | `None` | uint32;0=默认 | 非 0 拒绝。 |
298
+ | `header_priority` | `PriorityParam | None` | `None` | HEADERS priority | 任何非 None 值拒绝。 |
299
+ | `priority_frames` | `tuple[PriorityFrame, ...]` | `()` | 额外 PRIORITY frames | 非空拒绝。 |
300
+ | `max_header_list_size` | `int` | `0` | uint32;0=默认 | 非 0 拒绝。 |
301
+ | `strict_max_concurrent_streams` | `bool` | `False` | 严格服从对端并发流限制 | true 拒绝。 |
302
+ | `read_idle_timeout_ms` | `int` | `0` | ms,`0..600000` | 非 0 拒绝。 |
303
+ | `ping_timeout_ms` | `int` | `0` | ms,`0..600000` | 非 0 拒绝。 |
304
+ | `write_byte_timeout_ms` | `int` | `0` | ms,`0..600000` | 非 0 拒绝。 |
305
+
306
+ | 嵌套 DTO 字段 | 类型 | 默认/边界 |
307
+ |---|---|---|
308
+ | `HTTP2Setting.id` | `int` | 必填,`1..6`,同一 settings 中唯一。 |
309
+ | `HTTP2Setting.value` | `int` | 必填,uint32:`0..4294967295`。 |
310
+ | `PriorityParam.stream_dependency` | `int` | `0`,`0..2147483647`。 |
311
+ | `PriorityParam.exclusive` | `bool` | `False`。 |
312
+ | `PriorityParam.weight` | `int` | `0`,`0..255`。 |
313
+ | `PriorityFrame.stream_id` | `int` | 必填,`0..2147483647`。 |
314
+ | `PriorityFrame.priority` | `PriorityParam` | 必填。 |
315
+
316
+ ### RequestOptions
317
+
318
+ 单次请求放入 HTTPX `extensions={"go_req": ...}`;值可以是 `RequestOptions` 或字段严格匹配的 mapping。未知字段和错误类型在 Python 侧直接拒绝。
319
+
320
+ | 字段 | 类型 | 默认 | 边界/含义 | HTTP/3 |
321
+ |---|---|---:|---|---|
322
+ | `header_order` | `tuple[str, ...]` | `()` | 非空时覆盖 HTTP header 名称分组顺序;为空时按 HTTPX prepared headers 的首次出现顺序自动设置 | 非空拒绝。 |
323
+ | `pseudo_header_order` | `tuple[str, ...]` | `()` | HTTP/2 pseudo-header 顺序提示 | 非空拒绝。 |
324
+ | `force_chunked` | `bool` | `False` | 强制 chunked encoding | true 拒绝。 |
325
+ | `close_connection` | `bool` | `False` | 请求后关闭连接 | true 拒绝。 |
326
+ | `trace` | `bool` | `False` | 返回 req/QUIC trace | 生效。 |
327
+ | `dump` | `bool` | `False` | 返回内存诊断 dump,不接受路径或 writer | 生效,但格式不保证与 TCP dump 逐字节一致。 |
328
+ | `retry_count` | `int | None` | `None` | `0..10`,覆盖本次请求最大重试次数 | 生效。 |
329
+
330
+ ## trace、retry 与 dump
331
+
332
+ ```python
333
+ from gohttpx import Client, RequestOptions
334
+
335
+ with Client(go_token="secret") as client:
336
+ response = client.get(
337
+ "https://example.test/",
338
+ extensions={
339
+ "go_req": RequestOptions(
340
+ trace=True,
341
+ dump=True,
342
+ retry_count=2,
343
+ )
344
+ },
345
+ )
346
+ trace = response.extensions.get("go_trace")
347
+ dump = response.extensions.get("go_dump")
348
+ ```
349
+
350
+ `go_trace` 在启用时包含且仅包含:`dns_lookup_ms`、`connect_ms`、`tls_handshake_ms`、`first_byte_ms`、`response_ms`、`total_ms`、`connection_reused`、`remote_address`。`go_dump` 只在启用 dump 时存在,可能含目标 headers 和 body,调用方必须按敏感诊断数据保护;Go 服务不记录请求日志。
351
+
352
+ ## 控制协议与 header 契约
353
+
354
+ 两个控制 POST(创建会话、发起请求)必须使用 `application/json`;允许合法的参数(例如 `charset=UTF-8`),media type 大小写不敏感。缺失或错误类型返回 415 `UNSUPPORTED_MEDIA_TYPE`,语法畸形返回 400 `INVALID_REQUEST`,均为 JSON error envelope。无正文的 GET/DELETE 不要求 Content-Type。
355
+
356
+ v1 JSON 在任何层级都禁止 `null`,每个对象拒绝未知 key。创建请求的必需 key 是 `protocol_version`,其余预定义 `ClientOptions` key 可省略;目标请求的必需 key 是 `protocol_version`、`method`、`url`,预定义的 `headers`、`body_base64`、`timeout_ms`、`options` 可省略并采用空/零值。成功目标响应的必需 key 是 `protocol_version`、`request_id`、`status_code`、`reason_phrase`、`headers`、`body_base64`、`url`、`http_version`、`elapsed_ms`,可选 key 仅为 `trace`、`dump`,未启用时省略。错误对象必需 `code`、`message`、`retryable`,可选 `request_id` 只在已生成请求 ID 时出现;健康、能力和创建响应均使用各自文档列出的 exact keys。
357
+
358
+ 目标请求进入 req 最终 RoundTrip 前会从每请求 context 深拷贝恢复 HTTPX prepared headers,不会在线上补出调用方没有的业务 `User-Agent` 或 `Content-Type`。HTTP/1 对普通 header 保留首次出现的 key casing、同名重复值顺序和名称分组顺序;非空 `go_req.header_order` 覆盖自动顺序。`Host`、`User-Agent`、`Content-Length`、`Transfer-Encoding` 等由 req/Go 按协议特殊处理,不能视为任意原始 TCP 重放;HTTP/2 和 HTTP/3 的字段名按协议转为小写。响应侧 `net/http` 只能可靠保留值与重复值,不承诺原始 casing 或全局线序;当前 response envelope 按 canonical header 名排序。
359
+
360
+ ## 错误映射与会话重建
361
+
362
+ | 场景/Go code | Python 异常 | retryable 字段 |
363
+ |---|---|---|
364
+ | 本地 Go 服务无法连接、控制连接超时/断开 | `GoServiceUnavailable` | 不适用 |
365
+ | `UPSTREAM_TIMEOUT` | `httpx.TimeoutException` | `true` |
366
+ | `UPSTREAM_DNS_ERROR` | `httpx.ConnectError` | `true` |
367
+ | `UPSTREAM_CONNECT_ERROR` | `httpx.ConnectError` | `true` |
368
+ | `UPSTREAM_TLS_ERROR` | `httpx.ConnectError` | `false` |
369
+ | `UPSTREAM_PROTOCOL_ERROR` | `httpx.RemoteProtocolError` | `false` |
370
+ | `INVALID_REQUEST` | `GoProtocolError` | `false` |
371
+ | `UNSUPPORTED_MEDIA_TYPE` | `GoProtocolError` | `false` |
372
+ | `UNAUTHORIZED` | `GoProtocolError` | `false` |
373
+ | `PROTOCOL_MISMATCH` | `GoProtocolError` | `false` |
374
+ | `UNSUPPORTED_FEATURE` | `GoProtocolError` | `false` |
375
+ | `CLIENT_NOT_FOUND` 第二次仍失败 | `GoProtocolError` | `false` |
376
+ | `INTERNAL_ERROR` 或未知 code | `GoProtocolError` | 由 envelope 提供 |
377
+ | 目标站点 HTTP 4xx/5xx | 正常 `httpx.Response` | 不适用 |
378
+
379
+ 异常会保留原目标 `httpx.Request`;服务错误还暴露 `code` 和可用时的 `request_id`。
380
+
381
+ 只有收到完整、合法的 `CLIENT_NOT_FOUND` JSON 时,Transport 才使用原 ClientOptions 重建一次 Go 会话,并把完全相同的控制 envelope 重发一次。第二次 `CLIENT_NOT_FOUND` 直接抛错。控制连接中断、超时或响应不完整时绝不自动重发,因此不会在执行结果未知时偷偷重复 POST。
382
+
383
+ ## 限制与安全边界
384
+
385
+ - 请求和响应均完整缓冲在内存中;默认每方向 48 MiB,可用 `--max-body-mib` 调整。
386
+ - v1 不支持 streaming upload/download、WebSocket、SSE 或 parallel download。
387
+ - 控制配置 JSON 上限 4 MiB;目标 URL 最长 16384 bytes,method 最长 64 bytes。
388
+ - 每个目标请求最多 256 个 headers;单个名字最多 256 bytes,单个值最多 16384 bytes,总计最多 1 MiB。header 使用 Latin-1 无损映射。
389
+ - 任意 callback、middleware、hook、response transformer、自定义 dial/TLS handshake/proxy 函数、自定义 marshal、`io.Reader`/`io.Writer`、进度回调都不能跨进程。桥接内部为 uTLS+mTLS 使用的固定 handshake 不对调用方开放。
390
+ - Go 不持久化业务 cookies/headers/auth,不跟随 redirect,不使用 CookieJar,不自动字符集转换。
391
+ - `Host`、`User-Agent`、`Content-Length`、`Transfer-Encoding`、连接复用和 HTTP/2 帧仍受 req 与 Go Transport 控制;普通请求 header 的契约以上述“控制协议与 header 契约”为准,不扩展为任意原始 TCP 报文重放。
392
+ - 鉴权仅面向本机 loopback bearer;控制 token 不进入目标请求。
393
+ - Go 服务不输出启动日志或请求日志。控制面错误直接返回 JSON error envelope,Python 映射为带原始 request 的 HTTPX/`GoProtocolError` 异常,页面层可直接捕获并展示。
394
+
395
+ ## 运维与升级
396
+
397
+ Go 服务应由进程管理器或运维脚本手动常驻启动;Python 进程只连接它。按 Ctrl+C 会触发最多 10 秒的 graceful shutdown,并关闭已登记会话的空闲连接。孤儿会话默认空闲 24 小时后回收;正在执行的会话不会被空闲清理。
398
+
399
+ 两个同步 Client 构造完成后立即对应两个独立 Go session;两个异步 AsyncClient 则在各自第一次请求后才分别拥有独立 session。关闭一个已创建会话的 client 会幂等删除它自己的 session,不影响另一个 client。服务重启导致 session 丢失时,下一次请求按上述 `CLIENT_NOT_FOUND` 规则重建。
400
+
401
+ v1 发布后,`/api/v1` 的 required/optional key 集合、字段名、类型、默认值和语义保持稳定;optional key 可按约定省略,其他未知 key 一律拒绝。任何协议扩展必须使用新的 `protocol_version` 与 endpoint,或同步升级 Go 服务和 Python SDK 后再发布,不能让单边先接受新字段。Python 包版本与 protocol 版本独立,当前 server/Python 版本均为 `1.0.0`。
402
+
403
+ ## 测试与离线 E2E
404
+
405
+ 在 `gohttpx` 目录执行:
406
+
407
+ ```powershell
408
+ go vet ./...
409
+ go test ./...
410
+ go test -race ./...
411
+ python -B -m unittest discover -s python -p "test_*.py" -v
412
+ python -c "from pathlib import Path; [compile(p.read_text(encoding='utf-8'), str(p), 'exec') for p in Path('python').glob('*.py')]"
413
+ ```
414
+
415
+ Go 测试使用 `testing/httptest`,Python 使用 `unittest`。Python E2E 会在系统临时目录构建单个临时 EXE,启动本机 Go 服务和本机目标 HTTP 服务,覆盖正文编码、cookies、redirect、Basic/Digest auth、重复 query/header、错误状态、timeout、会话隔离与重建;测试不访问公网,结束后删除该临时 EXE。
416
+
417
+ 运行 Python 全套测试还需要 `cryptography`:`python -m pip install "httpx>=0.28,<0.29" "cryptography"`。