httpunk 0.1.0a1__tar.gz → 0.1.0a2__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.
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/Cargo.lock +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/Cargo.toml +2 -3
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/PKG-INFO +54 -15
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/README.md +51 -13
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/lib.rs +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/lib.rs +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/_backend/__init__.py +7 -2
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/_backend/asyncio.py +2 -2
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/asyncio.py +102 -10
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h2/client.py +1 -2
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/util/__init__.py +1 -3
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/util/pool.py +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/pyproject.toml +5 -7
- {httpunk-0.1.0a1/src/py → httpunk-0.1.0a2/src}/h1/codec.rs +1 -1
- {httpunk-0.1.0a1/src/py → httpunk-0.1.0a2/src}/h2/codec.rs +1 -1
- {httpunk-0.1.0a1/src/py → httpunk-0.1.0a2/src}/h2/streams.rs +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/src/lib.rs +10 -7
- httpunk-0.1.0a2/tests/test_asyncio_client_protocol.py +165 -0
- httpunk-0.1.0a1/src/py/mod.rs +0 -26
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/LICENSE +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/Cargo.toml +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/LICENSE +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/UPSTREAM_VERSION +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/codec/error.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/codec/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/ext.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/data.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/go_away.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/head.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/headers.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/ping.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/priority.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/reason.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/reset.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/settings.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/stream_id.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/util.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/frame/window_update.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/hpack/decoder.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/hpack/encoder.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/hpack/header.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/hpack/huffman/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/hpack/huffman/table.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/hpack/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/hpack/table.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/proto/error.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/proto/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/proto/streams/flow_control.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/proto/streams/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-h2/src/proto/streams/state.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/Cargo.toml +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/LICENSE +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/UPSTREAM_VERSION +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/body/length.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/body/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/cfg.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/common/date.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/common/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/error.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/ext/h1_reason_phrase.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/ext/informational.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/ext/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/headers.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/proto/h1/decode.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/proto/h1/encode.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/proto/h1/httpunk.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/proto/h1/io.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/proto/h1/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/proto/h1/role.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/proto/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper/src/trace.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper-util/Cargo.toml +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper-util/LICENSE +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper-util/UPSTREAM_VERSION +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper-util/src/client/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper-util/src/client/proxy/matcher.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper-util/src/client/proxy/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/crates/vendor-hyper-util/src/lib.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/__init__.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/_backend/tonio.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/_common.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/_httpunk.pyi +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/exceptions.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h1/__init__.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h1/client.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h1/connection.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h1/server.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h1/share.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h2/__init__.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h2/connection.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h2/server.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h2/settings.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h2/share.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h2/stream.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/h2/streams.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/http.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/py.typed +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/types.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/util/auto.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/util/client.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/util/graceful.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/httpunk/util/proxy.py +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.0a2/src}/errors.rs +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.0a2/src}/h1/mod.rs +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.0a2/src}/h2/mod.rs +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.0a2/src}/http/mod.rs +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.0a2/src}/proxy.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/_client.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/conftest.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_asyncio_backend.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_asyncio_e2e.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_asyncio_protocol.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_backend.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h1_codec.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h1_conn.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h1_decode.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h1_server.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_codec.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_conn.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_errors.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_flow.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_flow_control.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_ready.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_server.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_settings.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_h2_state.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_util_auto.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_util_connect.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_util_graceful.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_util_pool.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_util_proxy.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.0a2}/tests/test_util_tls.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "httpunk"
|
|
3
|
-
version = "0.1.0-
|
|
3
|
+
version = "0.1.0-alpha2"
|
|
4
4
|
description = "The Rust HTTP library for Python"
|
|
5
5
|
authors = [
|
|
6
6
|
"Giovanni Barillari <g@baro.dev>",
|
|
@@ -30,8 +30,7 @@ include = [
|
|
|
30
30
|
|
|
31
31
|
# The vendored third-party crates live in their own workspace members so each
|
|
32
32
|
# owns its crate root (upstream `crate::` paths resolve with no rewrite → truly
|
|
33
|
-
# byte-identical) and is excluded from the main crate's fmt/clippy.
|
|
34
|
-
# THIRD-PARTY.md and PLAN.md §4.
|
|
33
|
+
# byte-identical) and is excluded from the main crate's fmt/clippy.
|
|
35
34
|
[workspace]
|
|
36
35
|
members = ["crates/vendor-h2", "crates/vendor-hyper", "crates/vendor-hyper-util"]
|
|
37
36
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpunk
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.0a2
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: BSD License
|
|
@@ -19,7 +19,8 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
19
19
|
Classifier: Programming Language :: Python
|
|
20
20
|
Classifier: Programming Language :: Rust
|
|
21
21
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
22
|
-
Requires-Dist: tonio
|
|
22
|
+
Requires-Dist: tonio~=0.7.1 ; python_full_version >= '3.14' and extra == 'tonio'
|
|
23
|
+
Provides-Extra: tonio
|
|
23
24
|
License-File: LICENSE
|
|
24
25
|
Summary: The Rust HTTP library for Python
|
|
25
26
|
Keywords: http,h10,h11,h2
|
|
@@ -34,14 +35,16 @@ Project-URL: Source, https://github.com/gi0baro/httpunk
|
|
|
34
35
|
|
|
35
36
|
# httpunk
|
|
36
37
|
|
|
37
|
-
httpunk is a Rust-powered HTTP library for Python.
|
|
38
|
+
httpunk is a Rust-powered async HTTP library for Python.
|
|
38
39
|
It's powered by the [hyper](https://github.com/hyperium/hyper) stack and Rust crates like [http](https://github.com/hyperium/http).
|
|
39
40
|
|
|
40
41
|
httpunk is deliberately *low-level*: you bring your own connected transport, and
|
|
41
42
|
build requests and read responses directly. It's meant to be a solid, performant
|
|
42
43
|
base for building HTTP clients and servers on top of.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
httpunk's API mirrors hyper's wherever possible.
|
|
46
|
+
|
|
47
|
+
> **Note:** httpunk is in an early, alpha stage.
|
|
45
48
|
|
|
46
49
|
> **Note:** httpunk was built with substantial help from LLMs, under human supervision.
|
|
47
50
|
|
|
@@ -99,16 +102,19 @@ asyncio.run(main())
|
|
|
99
102
|
pip install httpunk
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
httpunk supports additional backends beyond `asyncio`, but they require extra dependencies.
|
|
106
|
+
Enable one via the relevant extra:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
pip install httpunk[tonio]
|
|
110
|
+
```
|
|
105
111
|
|
|
106
112
|
## Features
|
|
107
113
|
|
|
108
114
|
- **HTTP/1 and HTTP/2**, client and server implementations
|
|
109
115
|
- **Protocol-neutral structures** such as `Request`, `Response`, `HeaderMap`
|
|
110
116
|
- **Multiple backend support**: `asyncio` and `tonio` (with `trio` targeted for future releases)
|
|
111
|
-
- **AsyncIO ready-to-go protocols**: extensible `asyncio.Protocol`
|
|
117
|
+
- **AsyncIO ready-to-go protocols**: extensible `asyncio.Protocol` classes (H1, H2, Auto)
|
|
112
118
|
- **Batteries in the `util` module**: connect and ALPN negotiation, h1/h2 auto-detection, connection pooling, graceful shutdown, proxy-environment matching.
|
|
113
119
|
|
|
114
120
|
## Usage
|
|
@@ -305,7 +311,7 @@ except HTTPunkError:
|
|
|
305
311
|
### Utilities
|
|
306
312
|
|
|
307
313
|
`httpunk.util` collects the higher-level conveniences a real client/server host needs. Unlike
|
|
308
|
-
the core, these carry no wire-protocol fidelity constraint
|
|
314
|
+
the core, these carry no wire-protocol fidelity constraint.
|
|
309
315
|
|
|
310
316
|
#### connect
|
|
311
317
|
|
|
@@ -367,10 +373,9 @@ All pools expose `retain(predicate)` (evict connections a predicate rejects), `i
|
|
|
367
373
|
|
|
368
374
|
#### Graceful shutdown
|
|
369
375
|
|
|
370
|
-
`GracefulShutdown` coordinates a graceful shutdown across many connections
|
|
371
|
-
`
|
|
372
|
-
|
|
373
|
-
for them to drain.
|
|
376
|
+
`GracefulShutdown` coordinates a graceful shutdown across many connections.
|
|
377
|
+
`watch(server, serve)` registers a connection and returns the coroutine that drives it;
|
|
378
|
+
`shutdown()` signals every watched connection and waits for them to drain.
|
|
374
379
|
|
|
375
380
|
```python
|
|
376
381
|
from httpunk.util import GracefulShutdown
|
|
@@ -402,8 +407,10 @@ if intercept is not None:
|
|
|
402
407
|
|
|
403
408
|
### AsyncIO utilities
|
|
404
409
|
|
|
405
|
-
`httpunk.asyncio` provides reusable `asyncio.Protocol`
|
|
406
|
-
in any asyncio
|
|
410
|
+
`httpunk.asyncio` provides reusable `asyncio.Protocol` classes so you
|
|
411
|
+
can embed httpunk in any asyncio program.
|
|
412
|
+
|
|
413
|
+
**Server protocols** — subclass one and implement `handle(request)`:
|
|
407
414
|
|
|
408
415
|
- `H1ServerProtocol` / `H2ServerProtocol` — force the protocol.
|
|
409
416
|
- `AutoServerProtocol` — detect HTTP/1 vs HTTP/2 from the client's opening bytes.
|
|
@@ -442,6 +449,38 @@ server.close() # stop accepting new connections
|
|
|
442
449
|
await conns.shutdown(timeout=30) # drain in-flight, force-close stragglers
|
|
443
450
|
```
|
|
444
451
|
|
|
452
|
+
**Client protocols** — the mirror of the server ones, for `loop.create_connection`. Once the
|
|
453
|
+
connection is up, `await proto.ready()` returns the httpunk client connection to send requests
|
|
454
|
+
on. Configuration (`authority`/`scheme`) is passed via a factory closure, since
|
|
455
|
+
`create_connection` calls the factory with no arguments.
|
|
456
|
+
|
|
457
|
+
- `H1ClientProtocol` / `H2ClientProtocol` — force the protocol.
|
|
458
|
+
- `AutoClientProtocol` — pick HTTP/1 vs HTTP/2 from the TLS ALPN result (plain TCP → HTTP/1).
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
import asyncio
|
|
462
|
+
import ssl
|
|
463
|
+
|
|
464
|
+
import httpunk.asyncio
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
async def main():
|
|
468
|
+
loop = asyncio.get_running_loop()
|
|
469
|
+
ctx = ssl.create_default_context()
|
|
470
|
+
ctx.set_alpn_protocols(["h2", "http/1.1"])
|
|
471
|
+
transport, proto = await loop.create_connection(
|
|
472
|
+
lambda: httpunk.asyncio.H2ClientProtocol(authority="example.com:443", scheme="https"),
|
|
473
|
+
"example.com", 443, ssl=ctx, server_hostname="example.com",
|
|
474
|
+
)
|
|
475
|
+
conn = await proto.ready() # await handshake -> H2Connection
|
|
476
|
+
resp = await conn.request("GET", "/", headers={"host": "example.com"})
|
|
477
|
+
print(resp.status, await resp.read())
|
|
478
|
+
await proto.aclose()
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
asyncio.run(main())
|
|
482
|
+
```
|
|
483
|
+
|
|
445
484
|
## License
|
|
446
485
|
|
|
447
486
|
httpunk is released under the BSD 3-Clause License.
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# httpunk
|
|
2
2
|
|
|
3
|
-
httpunk is a Rust-powered HTTP library for Python.
|
|
3
|
+
httpunk is a Rust-powered async HTTP library for Python.
|
|
4
4
|
It's powered by the [hyper](https://github.com/hyperium/hyper) stack and Rust crates like [http](https://github.com/hyperium/http).
|
|
5
5
|
|
|
6
6
|
httpunk is deliberately *low-level*: you bring your own connected transport, and
|
|
7
7
|
build requests and read responses directly. It's meant to be a solid, performant
|
|
8
8
|
base for building HTTP clients and servers on top of.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
httpunk's API mirrors hyper's wherever possible.
|
|
11
|
+
|
|
12
|
+
> **Note:** httpunk is in an early, alpha stage.
|
|
11
13
|
|
|
12
14
|
> **Note:** httpunk was built with substantial help from LLMs, under human supervision.
|
|
13
15
|
|
|
@@ -65,16 +67,19 @@ asyncio.run(main())
|
|
|
65
67
|
pip install httpunk
|
|
66
68
|
```
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
httpunk supports additional backends beyond `asyncio`, but they require extra dependencies.
|
|
71
|
+
Enable one via the relevant extra:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
pip install httpunk[tonio]
|
|
75
|
+
```
|
|
71
76
|
|
|
72
77
|
## Features
|
|
73
78
|
|
|
74
79
|
- **HTTP/1 and HTTP/2**, client and server implementations
|
|
75
80
|
- **Protocol-neutral structures** such as `Request`, `Response`, `HeaderMap`
|
|
76
81
|
- **Multiple backend support**: `asyncio` and `tonio` (with `trio` targeted for future releases)
|
|
77
|
-
- **AsyncIO ready-to-go protocols**: extensible `asyncio.Protocol`
|
|
82
|
+
- **AsyncIO ready-to-go protocols**: extensible `asyncio.Protocol` classes (H1, H2, Auto)
|
|
78
83
|
- **Batteries in the `util` module**: connect and ALPN negotiation, h1/h2 auto-detection, connection pooling, graceful shutdown, proxy-environment matching.
|
|
79
84
|
|
|
80
85
|
## Usage
|
|
@@ -271,7 +276,7 @@ except HTTPunkError:
|
|
|
271
276
|
### Utilities
|
|
272
277
|
|
|
273
278
|
`httpunk.util` collects the higher-level conveniences a real client/server host needs. Unlike
|
|
274
|
-
the core, these carry no wire-protocol fidelity constraint
|
|
279
|
+
the core, these carry no wire-protocol fidelity constraint.
|
|
275
280
|
|
|
276
281
|
#### connect
|
|
277
282
|
|
|
@@ -333,10 +338,9 @@ All pools expose `retain(predicate)` (evict connections a predicate rejects), `i
|
|
|
333
338
|
|
|
334
339
|
#### Graceful shutdown
|
|
335
340
|
|
|
336
|
-
`GracefulShutdown` coordinates a graceful shutdown across many connections
|
|
337
|
-
`
|
|
338
|
-
|
|
339
|
-
for them to drain.
|
|
341
|
+
`GracefulShutdown` coordinates a graceful shutdown across many connections.
|
|
342
|
+
`watch(server, serve)` registers a connection and returns the coroutine that drives it;
|
|
343
|
+
`shutdown()` signals every watched connection and waits for them to drain.
|
|
340
344
|
|
|
341
345
|
```python
|
|
342
346
|
from httpunk.util import GracefulShutdown
|
|
@@ -368,8 +372,10 @@ if intercept is not None:
|
|
|
368
372
|
|
|
369
373
|
### AsyncIO utilities
|
|
370
374
|
|
|
371
|
-
`httpunk.asyncio` provides reusable `asyncio.Protocol`
|
|
372
|
-
in any asyncio
|
|
375
|
+
`httpunk.asyncio` provides reusable `asyncio.Protocol` classes so you
|
|
376
|
+
can embed httpunk in any asyncio program.
|
|
377
|
+
|
|
378
|
+
**Server protocols** — subclass one and implement `handle(request)`:
|
|
373
379
|
|
|
374
380
|
- `H1ServerProtocol` / `H2ServerProtocol` — force the protocol.
|
|
375
381
|
- `AutoServerProtocol` — detect HTTP/1 vs HTTP/2 from the client's opening bytes.
|
|
@@ -408,6 +414,38 @@ server.close() # stop accepting new connections
|
|
|
408
414
|
await conns.shutdown(timeout=30) # drain in-flight, force-close stragglers
|
|
409
415
|
```
|
|
410
416
|
|
|
417
|
+
**Client protocols** — the mirror of the server ones, for `loop.create_connection`. Once the
|
|
418
|
+
connection is up, `await proto.ready()` returns the httpunk client connection to send requests
|
|
419
|
+
on. Configuration (`authority`/`scheme`) is passed via a factory closure, since
|
|
420
|
+
`create_connection` calls the factory with no arguments.
|
|
421
|
+
|
|
422
|
+
- `H1ClientProtocol` / `H2ClientProtocol` — force the protocol.
|
|
423
|
+
- `AutoClientProtocol` — pick HTTP/1 vs HTTP/2 from the TLS ALPN result (plain TCP → HTTP/1).
|
|
424
|
+
|
|
425
|
+
```python
|
|
426
|
+
import asyncio
|
|
427
|
+
import ssl
|
|
428
|
+
|
|
429
|
+
import httpunk.asyncio
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
async def main():
|
|
433
|
+
loop = asyncio.get_running_loop()
|
|
434
|
+
ctx = ssl.create_default_context()
|
|
435
|
+
ctx.set_alpn_protocols(["h2", "http/1.1"])
|
|
436
|
+
transport, proto = await loop.create_connection(
|
|
437
|
+
lambda: httpunk.asyncio.H2ClientProtocol(authority="example.com:443", scheme="https"),
|
|
438
|
+
"example.com", 443, ssl=ctx, server_hostname="example.com",
|
|
439
|
+
)
|
|
440
|
+
conn = await proto.ready() # await handshake -> H2Connection
|
|
441
|
+
resp = await conn.request("GET", "/", headers={"host": "example.com"})
|
|
442
|
+
print(resp.status, await resp.read())
|
|
443
|
+
await proto.aclose()
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
asyncio.run(main())
|
|
447
|
+
```
|
|
448
|
+
|
|
411
449
|
## License
|
|
412
450
|
|
|
413
451
|
httpunk is released under the BSD 3-Clause License.
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//! frame parse/serialize) and `hpack` (HPACK header compression), plus the tiny
|
|
7
7
|
//! `ext::Protocol` type they depend on. h2's async `codec`, `proto`, `client`
|
|
8
8
|
//! and `server` are intentionally *not* vendored — that orchestration is
|
|
9
|
-
//! rewritten in Python
|
|
9
|
+
//! rewritten in Python.
|
|
10
10
|
//!
|
|
11
11
|
//! The `.rs` files under here are kept **byte-identical to upstream modulo a
|
|
12
12
|
//! uniform `crate::` -> `crate::` rewrite** the vendor script applies, so
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//! support types they depend on (`headers`, `ext`, `error`, `body::length`,
|
|
6
6
|
//! `proto::MessageHead`). hyper's async `conn`, `decode`, `io`, `dispatch`,
|
|
7
7
|
//! `client`, `server` and `upgrade` are intentionally *not* vendored — that
|
|
8
|
-
//! orchestration is rewritten in Python
|
|
8
|
+
//! orchestration is rewritten in Python.
|
|
9
9
|
//!
|
|
10
10
|
//! The `.rs` files under here are kept **byte-identical to upstream** so a
|
|
11
11
|
//! `git diff` between two vendored versions shows only genuine upstream changes.
|
|
@@ -25,8 +25,13 @@ class Backend(enum.Enum):
|
|
|
25
25
|
|
|
26
26
|
def create(self) -> Any:
|
|
27
27
|
if self is Backend.tonio:
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
try:
|
|
29
|
+
from .tonio import TonioBackend
|
|
30
|
+
except ImportError as exc:
|
|
31
|
+
raise ImportError(
|
|
32
|
+
"the tonio backend requires the 'tonio' package — install it with "
|
|
33
|
+
"`pip install httpunk[tonio]` (needs free-threaded CPython >= 3.14 on Unix)"
|
|
34
|
+
) from exc
|
|
30
35
|
return TonioBackend()
|
|
31
36
|
from .asyncio import AsyncioBackend
|
|
32
37
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""asyncio backend: the runtime primitives the drivers need, on `asyncio`.
|
|
2
2
|
|
|
3
3
|
The second backend (after tonio), validating that the drivers depend only on the
|
|
4
|
-
seam
|
|
4
|
+
seam. `import asyncio` below is the stdlib (absolute imports) — this
|
|
5
5
|
module is `httpunk._backend.asyncio`, not the top-level package.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
asyncio's TCP is transport/protocol-based and eagerly
|
|
8
8
|
drains the kernel socket into a userspace buffer, so — unlike tonio — a non-blocking
|
|
9
9
|
`receive_nowait` must peek *that* buffer. Rather than lean on `StreamReader`'s
|
|
10
10
|
private `_buffer`, we own a custom `asyncio.Protocol` (`_AsyncioStream`) that IS the
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
"""Reusable asyncio
|
|
2
|
-
|
|
1
|
+
"""Reusable asyncio protocols — embed httpunk in any asyncio program. They are
|
|
2
|
+
`asyncio.Protocol` subclasses of the asyncio backend's `_AsyncioStream` (the byte
|
|
3
|
+
transport the driver reads/writes); the host owns the loop and hands the protocol a
|
|
4
|
+
transport. Unlike uvicorn's h11/httptools protocols (HTTP/1 only), these also bring **HTTP/2**.
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
via `loop.create_server(factory)`; the protocol drives httpunk's server over *itself*
|
|
8
|
-
and calls your `handle` per request.
|
|
6
|
+
**Server** — subclass one and implement `handle(request)`, then hand it to
|
|
7
|
+
`loop.create_server(factory)`; the protocol drives httpunk's server over *itself* and calls
|
|
8
|
+
your `handle` per request:
|
|
9
9
|
|
|
10
10
|
class MyServer(httpunk.asyncio.AutoServerProtocol):
|
|
11
11
|
async def handle(self, request):
|
|
@@ -16,8 +16,19 @@ and calls your `handle` per request.
|
|
|
16
16
|
async with server:
|
|
17
17
|
await server.serve_forever()
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
the client
|
|
19
|
+
**Client** (the mirror) — hand one to `loop.create_connection(factory)`; once connected,
|
|
20
|
+
`await proto.ready()` returns the httpunk client connection to send requests on:
|
|
21
|
+
|
|
22
|
+
transport, proto = await loop.create_connection(
|
|
23
|
+
lambda: httpunk.asyncio.H2ClientProtocol(authority="example.com:443", scheme="https"),
|
|
24
|
+
"example.com", 443, ssl=ctx,
|
|
25
|
+
)
|
|
26
|
+
conn = await proto.ready()
|
|
27
|
+
resp = await conn.request("GET", "/", headers={"host": "example.com"})
|
|
28
|
+
await proto.aclose()
|
|
29
|
+
|
|
30
|
+
`H1*`/`H2*` force the protocol; `Auto*` picks h1-vs-h2 (server: from the client's opening
|
|
31
|
+
bytes; client: from the TLS ALPN result).
|
|
21
32
|
"""
|
|
22
33
|
|
|
23
34
|
from __future__ import annotations
|
|
@@ -27,12 +38,22 @@ import contextlib
|
|
|
27
38
|
from typing import Any, TypeVar
|
|
28
39
|
|
|
29
40
|
from ._backend.asyncio import AsyncioBackend, _AsyncioStream
|
|
41
|
+
from .h1.client import H1Connection
|
|
30
42
|
from .h1.server import H1Server
|
|
43
|
+
from .h2.client import H2Connection
|
|
31
44
|
from .h2.server import H2Server
|
|
32
45
|
from .util import auto
|
|
33
46
|
|
|
34
47
|
|
|
35
|
-
__all__ = [
|
|
48
|
+
__all__ = [
|
|
49
|
+
"AutoClientProtocol",
|
|
50
|
+
"AutoServerProtocol",
|
|
51
|
+
"H1ClientProtocol",
|
|
52
|
+
"H1ServerProtocol",
|
|
53
|
+
"H2ClientProtocol",
|
|
54
|
+
"H2ServerProtocol",
|
|
55
|
+
"ServerConnections",
|
|
56
|
+
]
|
|
36
57
|
|
|
37
58
|
_ProtocolT = TypeVar("_ProtocolT", bound="_ServerProtocol")
|
|
38
59
|
|
|
@@ -229,3 +250,74 @@ class ServerConnections:
|
|
|
229
250
|
if c._serve_task is not None:
|
|
230
251
|
c._serve_task.cancel() # hard-cancel a stuck handler / accept loop
|
|
231
252
|
await asyncio.wait(pending)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class _ClientProtocol(_AsyncioStream):
|
|
256
|
+
"""Base for the reusable client protocols — the client-side mirror of `_ServerProtocol`.
|
|
257
|
+
`_AsyncioStream` is the transport the driver reads/writes; on `connection_made` this builds
|
|
258
|
+
an httpunk client connection over `self` and spawns its handshake. `ready()` awaits the
|
|
259
|
+
handshake and returns the connection facade to send requests on. Subclasses provide
|
|
260
|
+
`_make_client`. There is no `handle` — the client is caller-driven, not push."""
|
|
261
|
+
|
|
262
|
+
def __init__(self, *, authority: str | None = None, scheme: str | None = None) -> None:
|
|
263
|
+
super().__init__()
|
|
264
|
+
self._backend = AsyncioBackend() # it's an asyncio protocol -> the asyncio backend
|
|
265
|
+
self._authority = authority
|
|
266
|
+
self._scheme = scheme
|
|
267
|
+
# Built in connection_made; None until then (so aclose() before a dial is a no-op).
|
|
268
|
+
self._client: H1Connection | H2Connection | None = None
|
|
269
|
+
self._connect_task: asyncio.Task[object] | None = None
|
|
270
|
+
|
|
271
|
+
def connection_made(self, transport):
|
|
272
|
+
super().connection_made(transport)
|
|
273
|
+
self._client = self._make_client()
|
|
274
|
+
# Run the HTTP handshake (the facade's __aenter__ -> conn.connect(): preface + SETTINGS
|
|
275
|
+
# for h2, a no-op for h1) concurrently; `ready()` awaits it. Retrieve the task's result
|
|
276
|
+
# so a handshake failure doesn't surface as a bare "Task exception was never retrieved".
|
|
277
|
+
task = self._loop.create_task(self._client.__aenter__())
|
|
278
|
+
self._connect_task = task
|
|
279
|
+
task.add_done_callback(lambda t: t.cancelled() or t.exception())
|
|
280
|
+
|
|
281
|
+
def _make_client(self) -> H1Connection | H2Connection:
|
|
282
|
+
raise NotImplementedError
|
|
283
|
+
|
|
284
|
+
async def ready(self) -> H1Connection | H2Connection:
|
|
285
|
+
"""Await the connection handshake, then return the ready client facade
|
|
286
|
+
(`H1Connection`/`H2Connection`) to send requests on."""
|
|
287
|
+
assert self._connect_task is not None and self._client is not None # set in connection_made
|
|
288
|
+
await self._connect_task
|
|
289
|
+
await self._client.ready()
|
|
290
|
+
return self._client
|
|
291
|
+
|
|
292
|
+
async def aclose(self) -> None:
|
|
293
|
+
"""Close the connection — drops the read/write scopes and the transport. A no-op if the
|
|
294
|
+
connection was never established (`connection_made` not yet called)."""
|
|
295
|
+
if self._client is not None:
|
|
296
|
+
await self._client.__aexit__(None, None, None)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class H1ClientProtocol(_ClientProtocol):
|
|
300
|
+
"""Speak HTTP/1 on this connection."""
|
|
301
|
+
|
|
302
|
+
def _make_client(self):
|
|
303
|
+
return H1Connection(self, authority=self._authority, backend=self._backend)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class H2ClientProtocol(_ClientProtocol):
|
|
307
|
+
"""Speak HTTP/2 on this connection."""
|
|
308
|
+
|
|
309
|
+
def _make_client(self):
|
|
310
|
+
return H2Connection(self, authority=self._authority, scheme=self._scheme or "http", backend=self._backend)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class AutoClientProtocol(_ClientProtocol):
|
|
314
|
+
"""Pick HTTP/1 or HTTP/2 from the TLS ALPN result — the client-side analogue of
|
|
315
|
+
`AutoServerProtocol`'s preface sniff. `h2` -> HTTP/2; anything else (including plain TCP
|
|
316
|
+
with no ALPN) -> HTTP/1, matching `httpunk.util.connect`."""
|
|
317
|
+
|
|
318
|
+
def _make_client(self):
|
|
319
|
+
ssl_object = self._transport.get_extra_info("ssl_object")
|
|
320
|
+
selected = ssl_object.selected_alpn_protocol() if ssl_object is not None else None
|
|
321
|
+
if selected == "h2":
|
|
322
|
+
return H2Connection(self, authority=self._authority, scheme=self._scheme or "https", backend=self._backend)
|
|
323
|
+
return H1Connection(self, authority=self._authority, backend=self._backend)
|
|
@@ -10,8 +10,7 @@ MAX_CONCURRENT_STREAMS), `Connection` (the client protocol driver over the share
|
|
|
10
10
|
collapses `http2::handshake` + the spawned `Connection` driver + `SendRequest`
|
|
11
11
|
into one async-context-managed object. The core method is `send_request(Request)
|
|
12
12
|
-> Response` (≈ `SendRequest::send_request`); `request` is a thin wrapper.
|
|
13
|
-
This layer is low-level by design — no pool, connector, or high-level client
|
|
14
|
-
(those live downstream; see PLAN.md §3.3).
|
|
13
|
+
This layer is low-level by design — no pool, connector, or high-level client.
|
|
15
14
|
|
|
16
15
|
Cross-reference: `h2 ...` comments cite hyperium/h2 v0.4.15.
|
|
17
16
|
"""
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""httpunk.util — composable HTTP utilities above the codec, mirroring the
|
|
2
|
-
**non-legacy** surface of `hyper-util
|
|
3
|
-
PLAN.md §11):
|
|
4
|
-
|
|
2
|
+
**non-legacy** surface of `hyper-util`:
|
|
5
3
|
- `connect(url, ...)` — client connect + ALPN negotiation (≈ `client::pool::negotiate`).
|
|
6
4
|
- `auto.serve(transport, ...)` — an auto h1-or-h2 server (≈ `server::conn::auto`).
|
|
7
5
|
- `GracefulShutdown` — a shutdown coordinator (≈ `server::graceful`).
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
Connection reuse is inherently runtime-bound (it manages live connections), so —
|
|
5
5
|
unlike the vendored proxy matcher — it lives in Python. hyper-util layers these as
|
|
6
6
|
tower `Service`s; we keep the same *concepts and names* as concrete helpers, with
|
|
7
|
-
**no** `Service`/`MakeService` abstraction
|
|
7
|
+
**no** `Service`/`MakeService` abstraction:
|
|
8
8
|
|
|
9
9
|
- `Singleton` — coalesce concurrent connects to **one shared** connection (the HTTP/2
|
|
10
10
|
case: one multiplexed connection for all callers).
|
|
@@ -33,13 +33,10 @@ dynamic = [
|
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
requires-python = ">=3.10"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
dependencies = [
|
|
41
|
-
"tonio>=0.7.1; python_version >= '3.14' and platform_system != 'Windows'",
|
|
42
|
-
]
|
|
36
|
+
dependencies = []
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
tonio = ["tonio~=0.7.1; python_version >= '3.14'"]
|
|
43
40
|
|
|
44
41
|
[dependency-groups]
|
|
45
42
|
build = [
|
|
@@ -52,6 +49,7 @@ test = [
|
|
|
52
49
|
'pytest~=8.3',
|
|
53
50
|
'pytest-asyncio~=0.24',
|
|
54
51
|
'trustme~=1.1',
|
|
52
|
+
"tonio; python_version >= '3.14' and platform_system != 'Windows'",
|
|
55
53
|
]
|
|
56
54
|
|
|
57
55
|
all = [
|
|
@@ -15,7 +15,7 @@ use pyo3::prelude::*;
|
|
|
15
15
|
use pyo3::types::PyBytes;
|
|
16
16
|
use std::sync::Mutex;
|
|
17
17
|
|
|
18
|
-
use crate::
|
|
18
|
+
use crate::http::HeaderMap;
|
|
19
19
|
use vendor_hyper::{
|
|
20
20
|
BodyDecode, BodyDecoder, BodyEncoder, encode_request, encode_response, parse_request,
|
|
21
21
|
parse_response,
|
|
@@ -16,7 +16,7 @@ use vendor_h2::frame::{Reason, Reset, StreamId};
|
|
|
16
16
|
use vendor_h2::proto::streams::{FlowControl, State};
|
|
17
17
|
use vendor_h2::proto::{Error, Initiator};
|
|
18
18
|
|
|
19
|
-
use crate::
|
|
19
|
+
use crate::errors::{ConnectionClosedError, HTTPunkError};
|
|
20
20
|
|
|
21
21
|
create_exception!(
|
|
22
22
|
_httpunk,
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
use pyo3::prelude::*;
|
|
2
2
|
use std::sync::OnceLock;
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
mod py;
|
|
4
|
+
mod errors;
|
|
5
|
+
mod h1;
|
|
6
|
+
mod h2;
|
|
7
|
+
mod http;
|
|
8
|
+
mod proxy;
|
|
10
9
|
|
|
11
10
|
pub fn get_lib_version() -> &'static str {
|
|
12
11
|
static LIB_VERSION: OnceLock<String> = OnceLock::new();
|
|
@@ -21,7 +20,11 @@ pub fn get_lib_version() -> &'static str {
|
|
|
21
20
|
fn _httpunk(_py: Python, module: &Bound<PyModule>) -> PyResult<()> {
|
|
22
21
|
module.add("__version__", get_lib_version())?;
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
errors::register(module)?;
|
|
24
|
+
http::register(module)?;
|
|
25
|
+
h2::register(module)?;
|
|
26
|
+
h1::register(module)?;
|
|
27
|
+
proxy::register(module)?;
|
|
25
28
|
|
|
26
29
|
Ok(())
|
|
27
30
|
}
|