httpunk 0.1.0a1__tar.gz → 0.1.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.
- {httpunk-0.1.0a1 → httpunk-0.1.1}/Cargo.lock +11 -11
- {httpunk-0.1.0a1 → httpunk-0.1.1}/Cargo.toml +2 -3
- {httpunk-0.1.0a1 → httpunk-0.1.1}/PKG-INFO +54 -15
- {httpunk-0.1.0a1 → httpunk-0.1.1}/README.md +51 -13
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/lib.rs +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/lib.rs +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/_backend/__init__.py +7 -2
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/_backend/asyncio.py +23 -2
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/_backend/tonio.py +7 -2
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/asyncio.py +102 -10
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h1/server.py +49 -15
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h2/client.py +1 -2
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/util/__init__.py +1 -3
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/util/pool.py +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.1}/pyproject.toml +5 -7
- {httpunk-0.1.0a1/src/py → httpunk-0.1.1/src}/h1/codec.rs +1 -1
- {httpunk-0.1.0a1/src/py → httpunk-0.1.1/src}/h2/codec.rs +1 -1
- {httpunk-0.1.0a1/src/py → httpunk-0.1.1/src}/h2/streams.rs +1 -1
- {httpunk-0.1.0a1 → httpunk-0.1.1}/src/lib.rs +10 -7
- httpunk-0.1.1/tests/test_asyncio_client_protocol.py +165 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h1_server.py +29 -0
- httpunk-0.1.0a1/src/py/mod.rs +0 -26
- {httpunk-0.1.0a1 → httpunk-0.1.1}/LICENSE +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/Cargo.toml +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/LICENSE +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/UPSTREAM_VERSION +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/codec/error.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/codec/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/ext.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/data.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/go_away.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/head.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/headers.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/ping.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/priority.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/reason.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/reset.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/settings.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/stream_id.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/util.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/frame/window_update.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/hpack/decoder.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/hpack/encoder.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/hpack/header.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/hpack/huffman/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/hpack/huffman/table.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/hpack/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/hpack/table.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/proto/error.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/proto/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/proto/streams/flow_control.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/proto/streams/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-h2/src/proto/streams/state.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/Cargo.toml +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/LICENSE +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/UPSTREAM_VERSION +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/body/length.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/body/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/cfg.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/common/date.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/common/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/error.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/ext/h1_reason_phrase.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/ext/informational.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/ext/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/headers.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/proto/h1/decode.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/proto/h1/encode.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/proto/h1/httpunk.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/proto/h1/io.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/proto/h1/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/proto/h1/role.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/proto/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper/src/trace.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper-util/Cargo.toml +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper-util/LICENSE +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper-util/UPSTREAM_VERSION +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper-util/src/client/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper-util/src/client/proxy/matcher.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper-util/src/client/proxy/mod.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/crates/vendor-hyper-util/src/lib.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/__init__.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/_common.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/_httpunk.pyi +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/exceptions.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h1/__init__.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h1/client.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h1/connection.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h1/share.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h2/__init__.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h2/connection.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h2/server.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h2/settings.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h2/share.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h2/stream.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/h2/streams.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/http.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/py.typed +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/types.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/util/auto.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/util/client.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/util/graceful.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/httpunk/util/proxy.py +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.1/src}/errors.rs +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.1/src}/h1/mod.rs +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.1/src}/h2/mod.rs +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.1/src}/http/mod.rs +0 -0
- {httpunk-0.1.0a1/src/py → httpunk-0.1.1/src}/proxy.rs +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/_client.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/conftest.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_asyncio_backend.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_asyncio_e2e.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_asyncio_protocol.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_backend.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h1_codec.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h1_conn.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h1_decode.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_codec.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_conn.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_errors.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_flow.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_flow_control.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_ready.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_server.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_settings.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_h2_state.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_util_auto.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_util_connect.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_util_graceful.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_util_pool.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_util_proxy.py +0 -0
- {httpunk-0.1.0a1 → httpunk-0.1.1}/tests/test_util_tls.py +0 -0
|
@@ -16,9 +16,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
|
|
16
16
|
|
|
17
17
|
[[package]]
|
|
18
18
|
name = "bytes"
|
|
19
|
-
version = "1.
|
|
19
|
+
version = "1.12.1"
|
|
20
20
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
21
|
-
checksum = "
|
|
21
|
+
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
|
22
22
|
|
|
23
23
|
[[package]]
|
|
24
24
|
name = "equivalent"
|
|
@@ -86,9 +86,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
|
86
86
|
|
|
87
87
|
[[package]]
|
|
88
88
|
name = "http"
|
|
89
|
-
version = "1.4.
|
|
89
|
+
version = "1.4.2"
|
|
90
90
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
91
|
-
checksum = "
|
|
91
|
+
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
|
|
92
92
|
dependencies = [
|
|
93
93
|
"bytes",
|
|
94
94
|
"itoa",
|
|
@@ -118,7 +118,7 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|
|
118
118
|
|
|
119
119
|
[[package]]
|
|
120
120
|
name = "httpunk"
|
|
121
|
-
version = "0.1.
|
|
121
|
+
version = "0.1.1"
|
|
122
122
|
dependencies = [
|
|
123
123
|
"bytes",
|
|
124
124
|
"h2",
|
|
@@ -175,9 +175,9 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
|
175
175
|
|
|
176
176
|
[[package]]
|
|
177
177
|
name = "libc"
|
|
178
|
-
version = "0.2.
|
|
178
|
+
version = "0.2.186"
|
|
179
179
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
180
|
-
checksum = "
|
|
180
|
+
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
|
181
181
|
|
|
182
182
|
[[package]]
|
|
183
183
|
name = "once_cell"
|
|
@@ -271,9 +271,9 @@ dependencies = [
|
|
|
271
271
|
|
|
272
272
|
[[package]]
|
|
273
273
|
name = "quote"
|
|
274
|
-
version = "1.0.
|
|
274
|
+
version = "1.0.46"
|
|
275
275
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
276
|
-
checksum = "
|
|
276
|
+
checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
|
|
277
277
|
dependencies = [
|
|
278
278
|
"proc-macro2",
|
|
279
279
|
]
|
|
@@ -292,9 +292,9 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
|
|
292
292
|
|
|
293
293
|
[[package]]
|
|
294
294
|
name = "syn"
|
|
295
|
-
version = "2.0.
|
|
295
|
+
version = "2.0.118"
|
|
296
296
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
297
|
-
checksum = "
|
|
297
|
+
checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
|
|
298
298
|
dependencies = [
|
|
299
299
|
"proc-macro2",
|
|
300
300
|
"quote",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "httpunk"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.1"
|
|
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.1
|
|
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.8.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
|
|
@@ -162,6 +162,12 @@ class _AsyncioStream(asyncio.Protocol):
|
|
|
162
162
|
if waiter is not None and not waiter.done():
|
|
163
163
|
waiter.set_result(None)
|
|
164
164
|
|
|
165
|
+
def interrupt_read(self):
|
|
166
|
+
"""Wake a parked `receive_some` so it returns b"" — lets a graceful shutdown release
|
|
167
|
+
an idle keep-alive read WITHOUT racing a coroutine (asyncio can wake a parked read from
|
|
168
|
+
another task; tonio can't, which is why it uses a select-based race instead)."""
|
|
169
|
+
self._wake_reader()
|
|
170
|
+
|
|
165
171
|
|
|
166
172
|
class _AsyncioScope:
|
|
167
173
|
"""A nursery over `asyncio` tasks, matching tonio's scope surface: `spawn`,
|
|
@@ -297,3 +303,18 @@ class AsyncioBackend:
|
|
|
297
303
|
scope = _AsyncioScope
|
|
298
304
|
monotonic = staticmethod(_time.monotonic)
|
|
299
305
|
sleep = staticmethod(asyncio.sleep) # async sleep(seconds), for deadline races (`select`)
|
|
306
|
+
|
|
307
|
+
# Every backend implements `timeout` (above), so the h1 server always uses it for the
|
|
308
|
+
# head-read deadline. This extra capability is asyncio-only: `_AsyncioStream.interrupt_read`
|
|
309
|
+
# can wake a parked idle read from another task, so the h1 server can skip the per-read
|
|
310
|
+
# shutdown `select`. tonio can't (no such method) → it keeps the select there.
|
|
311
|
+
native_read_interrupt = True
|
|
312
|
+
|
|
313
|
+
async def timeout(self, coro, seconds):
|
|
314
|
+
"""Run `coro` bounded by `seconds`; return `(result, True)` on completion, or
|
|
315
|
+
`(None, False)` on timeout (the coro is cancelled). One task + one timer, far cheaper
|
|
316
|
+
than racing a `sleep()` coroutine via `select`."""
|
|
317
|
+
try:
|
|
318
|
+
return await asyncio.wait_for(coro, seconds), True
|
|
319
|
+
except (TimeoutError, asyncio.TimeoutError):
|
|
320
|
+
return None, False
|
|
@@ -12,7 +12,7 @@ from tonio.colored.net import open_tcp_stream as _open_tcp_stream
|
|
|
12
12
|
from tonio.colored.net.tls import open_tls_over_tcp_stream as _open_tls_over_tcp_stream
|
|
13
13
|
from tonio.colored.sync import Lock as _Lock, Semaphore as _Semaphore
|
|
14
14
|
from tonio.colored.sync.channel import unbounded as _unbounded
|
|
15
|
-
from tonio.colored.time import time as _now
|
|
15
|
+
from tonio.colored.time import time as _now, timeout as _timeout
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class _TonioSemaphore:
|
|
@@ -107,4 +107,9 @@ class TonioBackend:
|
|
|
107
107
|
semaphore = _TonioSemaphore
|
|
108
108
|
queue = staticmethod(_unbounded)
|
|
109
109
|
monotonic = staticmethod(_now)
|
|
110
|
-
sleep = staticmethod(_colored.sleep) # async sleep(seconds)
|
|
110
|
+
sleep = staticmethod(_colored.sleep) # async sleep(seconds)
|
|
111
|
+
# `timeout(coro, seconds) -> (result, completed)` — tonio's native deadline; the h1 server
|
|
112
|
+
# uses it to bound the request-head read (measured ~12-16% faster than racing sleep() via
|
|
113
|
+
# select). Same contract as AsyncioBackend.timeout. tonio still can't wake a parked recv from
|
|
114
|
+
# another task, so it does NOT advertise `native_read_interrupt` (idle shutdown stays select).
|
|
115
|
+
timeout = staticmethod(_timeout)
|
|
@@ -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)
|