httpunk 0.2.2__tar.gz → 0.4.0__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.2.2 → httpunk-0.4.0}/Cargo.lock +3 -3
- {httpunk-0.2.2 → httpunk-0.4.0}/Cargo.toml +2 -2
- {httpunk-0.2.2 → httpunk-0.4.0}/PKG-INFO +95 -22
- {httpunk-0.2.2 → httpunk-0.4.0}/README.md +93 -20
- httpunk-0.4.0/crates/vendor-h2/src/error.rs +211 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/lib.rs +12 -4
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/proto/mod.rs +13 -2
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/proto/streams/mod.rs +2 -1
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/proto/streams/state.rs +22 -1
- httpunk-0.4.0/crates/vendor-hyper/UPSTREAM_VERSION +1 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/body/length.rs +3 -3
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/common/date.rs +0 -8
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/error.rs +1 -1
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/headers.rs +63 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/lib.rs +6 -3
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/proto/h1/decode.rs +11 -11
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/proto/h1/encode.rs +26 -26
- httpunk-0.4.0/crates/vendor-hyper/src/proto/h1/httpunk.rs +763 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/proto/h1/role.rs +47 -10
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/__init__.py +7 -1
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/_backend/asyncio.py +163 -33
- httpunk-0.4.0/httpunk/_backend/tonio.py +298 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/_common.py +28 -6
- httpunk-0.4.0/httpunk/_httpunk.pyi +1133 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/asyncio.py +34 -20
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/exceptions.py +42 -15
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/h1/__init__.py +1 -1
- httpunk-0.4.0/httpunk/h1/client.py +607 -0
- httpunk-0.4.0/httpunk/h1/connection.py +94 -0
- httpunk-0.4.0/httpunk/h1/server.py +1060 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/h1/share.py +67 -29
- httpunk-0.4.0/httpunk/h2/__init__.py +2 -0
- httpunk-0.4.0/httpunk/h2/client.py +283 -0
- httpunk-0.4.0/httpunk/h2/connection.py +554 -0
- httpunk-0.4.0/httpunk/h2/server.py +405 -0
- httpunk-0.4.0/httpunk/h2/share.py +43 -0
- httpunk-0.4.0/httpunk/h2/stream.py +45 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/types.py +17 -1
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/util/__init__.py +1 -1
- httpunk-0.4.0/httpunk/util/auto.py +311 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/util/client.py +12 -10
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/util/graceful.py +14 -7
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/util/pool.py +85 -41
- {httpunk-0.2.2 → httpunk-0.4.0}/pyproject.toml +1 -1
- {httpunk-0.2.2 → httpunk-0.4.0}/src/errors.rs +9 -5
- httpunk-0.4.0/src/h1/codec.rs +739 -0
- httpunk-0.4.0/src/h1/conn.rs +1470 -0
- httpunk-0.4.0/src/h1/errors.rs +154 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/src/h1/mod.rs +12 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/src/h2/codec.rs +618 -210
- httpunk-0.4.0/src/h2/conn.rs +2385 -0
- httpunk-0.4.0/src/h2/errors.rs +122 -0
- httpunk-0.4.0/src/h2/mod.rs +90 -0
- httpunk-0.4.0/src/h2/reason.rs +192 -0
- httpunk-0.4.0/src/h2/settings.rs +104 -0
- httpunk-0.4.0/src/h2/streams.rs +530 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/src/http/mod.rs +62 -1
- {httpunk-0.2.2 → httpunk-0.4.0}/src/lib.rs +1 -0
- httpunk-0.4.0/src/py.rs +105 -0
- httpunk-0.4.0/tests/_transport.py +24 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_asyncio_backend.py +121 -5
- httpunk-0.4.0/tests/test_asyncio_e2e.py +596 -0
- httpunk-0.4.0/tests/test_backend.py +248 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_error_retention.py +3 -0
- httpunk-0.4.0/tests/test_h1_codec.py +380 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_h1_conn.py +190 -8
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_h1_decode.py +57 -2
- httpunk-0.4.0/tests/test_h1_server.py +2022 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_h2_codec.py +33 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_h2_conn.py +2 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_h2_errors.py +6 -4
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_h2_flow.py +107 -91
- httpunk-0.4.0/tests/test_h2_flow_control.py +174 -0
- httpunk-0.4.0/tests/test_h2_ready.py +91 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_h2_server.py +452 -22
- httpunk-0.4.0/tests/test_h2_settings.py +115 -0
- httpunk-0.4.0/tests/test_h2_write_batch.py +115 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_headermap.py +10 -0
- httpunk-0.4.0/tests/test_util_auto.py +307 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_util_connect.py +14 -1
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_util_graceful.py +157 -39
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_util_pool.py +97 -2
- httpunk-0.4.0/tests/test_util_tls.py +397 -0
- httpunk-0.2.2/crates/vendor-hyper/UPSTREAM_VERSION +0 -1
- httpunk-0.2.2/crates/vendor-hyper/src/proto/h1/httpunk.rs +0 -449
- httpunk-0.2.2/httpunk/_backend/tonio.py +0 -142
- httpunk-0.2.2/httpunk/_httpunk.pyi +0 -461
- httpunk-0.2.2/httpunk/h1/client.py +0 -654
- httpunk-0.2.2/httpunk/h1/connection.py +0 -140
- httpunk-0.2.2/httpunk/h1/server.py +0 -576
- httpunk-0.2.2/httpunk/h2/__init__.py +0 -2
- httpunk-0.2.2/httpunk/h2/client.py +0 -463
- httpunk-0.2.2/httpunk/h2/connection.py +0 -522
- httpunk-0.2.2/httpunk/h2/server.py +0 -474
- httpunk-0.2.2/httpunk/h2/settings.py +0 -167
- httpunk-0.2.2/httpunk/h2/share.py +0 -88
- httpunk-0.2.2/httpunk/h2/stream.py +0 -91
- httpunk-0.2.2/httpunk/h2/streams.py +0 -1058
- httpunk-0.2.2/httpunk/util/auto.py +0 -123
- httpunk-0.2.2/src/h1/codec.rs +0 -414
- httpunk-0.2.2/src/h2/mod.rs +0 -74
- httpunk-0.2.2/src/h2/streams.rs +0 -357
- httpunk-0.2.2/tests/test_asyncio_e2e.py +0 -292
- httpunk-0.2.2/tests/test_backend.py +0 -119
- httpunk-0.2.2/tests/test_h1_codec.py +0 -108
- httpunk-0.2.2/tests/test_h1_server.py +0 -727
- httpunk-0.2.2/tests/test_h2_flow_control.py +0 -74
- httpunk-0.2.2/tests/test_h2_ready.py +0 -85
- httpunk-0.2.2/tests/test_h2_settings.py +0 -115
- httpunk-0.2.2/tests/test_util_auto.py +0 -161
- httpunk-0.2.2/tests/test_util_tls.py +0 -111
- {httpunk-0.2.2 → httpunk-0.4.0}/LICENSE +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/Cargo.toml +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/LICENSE +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/UPSTREAM_VERSION +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/codec/error.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/codec/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/ext.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/data.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/go_away.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/head.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/headers.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/ping.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/priority.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/reason.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/reset.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/settings.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/stream_id.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/util.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/frame/window_update.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/hpack/decoder.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/hpack/encoder.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/hpack/header.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/hpack/huffman/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/hpack/huffman/table.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/hpack/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/hpack/table.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/proto/error.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-h2/src/proto/streams/flow_control.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/Cargo.toml +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/LICENSE +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/body/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/cfg.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/common/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/ext/h1_reason_phrase.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/ext/informational.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/ext/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/proto/h1/io.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/proto/h1/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/proto/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper/src/trace.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper-util/Cargo.toml +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper-util/LICENSE +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper-util/UPSTREAM_VERSION +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper-util/src/client/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper-util/src/client/proxy/matcher.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper-util/src/client/proxy/mod.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/crates/vendor-hyper-util/src/lib.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/_backend/__init__.py +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/http.py +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/py.typed +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/httpunk/util/proxy.py +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/src/proxy.rs +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/_client.py +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/conftest.py +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_asyncio_client_protocol.py +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_asyncio_protocol.py +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_h2_state.py +0 -0
- {httpunk-0.2.2 → httpunk-0.4.0}/tests/test_util_proxy.py +0 -0
|
@@ -118,7 +118,7 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|
|
118
118
|
|
|
119
119
|
[[package]]
|
|
120
120
|
name = "httpunk"
|
|
121
|
-
version = "0.
|
|
121
|
+
version = "0.4.0"
|
|
122
122
|
dependencies = [
|
|
123
123
|
"bytes",
|
|
124
124
|
"h2",
|
|
@@ -132,9 +132,9 @@ dependencies = [
|
|
|
132
132
|
|
|
133
133
|
[[package]]
|
|
134
134
|
name = "hyper"
|
|
135
|
-
version = "1.11.
|
|
135
|
+
version = "1.11.1"
|
|
136
136
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
137
|
-
checksum = "
|
|
137
|
+
checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
|
|
138
138
|
dependencies = [
|
|
139
139
|
"atomic-waker",
|
|
140
140
|
"bytes",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "httpunk"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.4.0"
|
|
4
4
|
description = "The Rust HTTP library for Python"
|
|
5
5
|
authors = [
|
|
6
6
|
"Giovanni Barillari <g@baro.dev>",
|
|
@@ -61,7 +61,7 @@ vendor-hyper-util = { path = "crates/vendor-hyper-util" }
|
|
|
61
61
|
# the pinned versions land in Cargo.lock for `cargo audit` / Dependabot advisory
|
|
62
62
|
# coverage without pulling them (and tokio) into the build.
|
|
63
63
|
h2 = { version = "=0.4.19", optional = true }
|
|
64
|
-
hyper = { version = "=1.11.
|
|
64
|
+
hyper = { version = "=1.11.1", optional = true, default-features = false, features = ["http1", "client", "server"] }
|
|
65
65
|
|
|
66
66
|
[profile.release]
|
|
67
67
|
codegen-units = 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpunk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: BSD License
|
|
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
20
20
|
Classifier: Programming Language :: Python
|
|
21
21
|
Classifier: Programming Language :: Rust
|
|
22
22
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
-
Requires-Dist: tonio~=0.9.
|
|
23
|
+
Requires-Dist: tonio~=0.9.15 ; python_full_version >= '3.14' and extra == 'tonio'
|
|
24
24
|
Provides-Extra: tonio
|
|
25
25
|
License-File: LICENSE
|
|
26
26
|
Summary: The Rust HTTP library for Python
|
|
@@ -183,6 +183,7 @@ async for chunk in resp.aiter_bytes():
|
|
|
183
183
|
...
|
|
184
184
|
|
|
185
185
|
resp.trailers # a HeaderMap of trailing headers, or None
|
|
186
|
+
resp.version # httpunk.Version.HTTP_2 / HTTP_11 / HTTP_10 (≈ http::Version)
|
|
186
187
|
```
|
|
187
188
|
|
|
188
189
|
A response can be used as an async context manager to guarantee release (cancelling the body
|
|
@@ -198,7 +199,9 @@ async with await conn.request("GET", "/big") as resp:
|
|
|
198
199
|
|
|
199
200
|
`body` may be `bytes`, or a sync/async iterable of `bytes` (streamed as it is produced).
|
|
200
201
|
`trailers` are header fields sent after the body — chunked trailers on HTTP/1, a trailing
|
|
201
|
-
`HEADERS` frame on HTTP/2
|
|
202
|
+
`HEADERS` frame on HTTP/2. As in hyper, HTTP/1 trailers ride only on a chunked body (a
|
|
203
|
+
streamed one; a `bytes` body is `Content-Length`-framed and drops them) and only the fields
|
|
204
|
+
the request's own `Trailer` header declares are sent — undeclared ones are dropped:
|
|
202
205
|
|
|
203
206
|
```python
|
|
204
207
|
async def chunks():
|
|
@@ -207,7 +210,7 @@ async def chunks():
|
|
|
207
210
|
|
|
208
211
|
resp = await conn.request(
|
|
209
212
|
"POST", "/upload",
|
|
210
|
-
headers={"host": "example.com", "content-type": "application/octet-stream"},
|
|
213
|
+
headers={"host": "example.com", "content-type": "application/octet-stream", "trailer": "x-checksum"},
|
|
211
214
|
body=chunks(),
|
|
212
215
|
trailers={"x-checksum": "..."},
|
|
213
216
|
)
|
|
@@ -233,11 +236,51 @@ async with H1Server(transport, backend=Backend.asyncio) as server:
|
|
|
233
236
|
await request.respond(200, headers={"content-type": "text/plain"}, body=body)
|
|
234
237
|
```
|
|
235
238
|
|
|
236
|
-
Each `request` carries `method`, `target`/`path`, `headers`, and a streamable body
|
|
239
|
+
Each `request` carries `method`, `target`/`path`, `headers`, `version`, and a streamable body
|
|
237
240
|
(`request.read()` / `request.aiter_bytes()`). Answer it with `request.respond(status, *,
|
|
238
|
-
headers=None, body=None)
|
|
239
|
-
|
|
240
|
-
|
|
241
|
+
headers=None, body=None, trailers=None)` — `trailers` are sent after the body (HTTP/1.1
|
|
242
|
+
chunked trailers; an HTTP/2 trailing HEADERS frame), like the client's `Request.trailers` and
|
|
243
|
+
under hyper's rules: on HTTP/1.1 they need a chunked (streamed) body and a `Trailer` header
|
|
244
|
+
declaring the fields, and go out only if the request declared `TE: trailers`; otherwise they
|
|
245
|
+
are dropped and the body ends normally, as hyper's server does.
|
|
246
|
+
On HTTP/2 you can also abort a single stream with `request.reset()` instead of responding
|
|
247
|
+
(e.g. when a handler fails) — the connection and its other streams keep running — and
|
|
248
|
+
`await request.reset_received()` resolves with the reason
|
|
249
|
+
when the *client* abandons the request (`RST_STREAM`, even after it finished sending), the
|
|
250
|
+
signal a long-running or streaming handler races against its own completion to stop early.
|
|
251
|
+
A reset also fails an in-flight `respond()` / `send_data` with `StreamResetError` carrying the
|
|
252
|
+
client's reason, including while the body is waiting on the app's next chunk. The HTTP/1 twin
|
|
253
|
+
is `await request.peer_closed()`: once the request body is complete, a client that closes the
|
|
254
|
+
connection before the response is done is detected (hyper's `mid_message_detect_eof`), the
|
|
255
|
+
in-flight `respond()` / `send_data` fail with `H1IncompleteMessageError`, and the accept loop
|
|
256
|
+
ends. The await resolves either way and never hangs: `True` when the client closed
|
|
257
|
+
mid-request, `False` once the exchange completed (or failed) first, at once if it already had.
|
|
258
|
+
`H1Server(half_close=True)` turns the detection off, as hyper's `half_close` does. A request
|
|
259
|
+
announcing an upgrade (`Upgrade: h2c`, `Upgrade: websocket`, CONNECT) is watched only from its
|
|
260
|
+
response head on, when it can no longer be detached or switched.
|
|
261
|
+
|
|
262
|
+
When `respond()` fails this way while streaming an async body, the failure is raised at once
|
|
263
|
+
even if the body is parked waiting for its next chunk: the producer is cancelled at that
|
|
264
|
+
`await` (as hyper drops the body future), its `finally` blocks run, and only then does
|
|
265
|
+
`respond()` raise. Cancelling the task that awaits `respond()` cancels the producer the same way.
|
|
266
|
+
|
|
267
|
+
For push-style producers (an ASGI `send()` loop, a server-sent-events endpoint) use
|
|
268
|
+
`request.send_response(status, *, headers=None, end_stream=False)`: it writes the head now and
|
|
269
|
+
returns a `SendStream` — h2's `SendResponse`/`SendStream` shape, identical on both protocols:
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
stream = await request.send_response(200, headers={"content-type": "text/event-stream"})
|
|
273
|
+
await stream.send_data(b"data: 1\n\n") # each write awaits backpressure
|
|
274
|
+
await stream.send_data(b"data: 2\n\n", end_stream=True)
|
|
275
|
+
# stream.send_trailers({...}) ends the body with trailers; stream.send_reset() aborts it
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`end_stream=True` on `send_response` is a bodyless response. Framing follows hyper: a
|
|
279
|
+
`content-length` you set is honoured, otherwise the body is chunked (HTTP/1.1) or
|
|
280
|
+
close-delimited (HTTP/1.0); on HEAD/204/304 body chunks are discarded, as hyper never polls
|
|
281
|
+
that body. `send_reset` is `RST_STREAM(CANCEL)` on HTTP/2; HTTP/1 has no per-request reset, so
|
|
282
|
+
there it closes the connection (what hyper does when a response body errors). `respond()` is
|
|
283
|
+
the pull convenience built on the same path.
|
|
241
284
|
|
|
242
285
|
HTTP/1 serves one request/response at a time (the loop won't yield the next until the current
|
|
243
286
|
one is answered); HTTP/2 multiplexes, so for concurrent handling you would spawn a task per
|
|
@@ -275,24 +318,32 @@ h.raw_items() # [(b'content-type', b'text/plain'), (b'set-cookie'
|
|
|
275
318
|
|
|
276
319
|
### Errors
|
|
277
320
|
|
|
278
|
-
httpunk's exceptions all derive from a common `HTTPunkError` root
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
the
|
|
321
|
+
httpunk's exceptions all derive from a common `HTTPunkError` root, with one class per
|
|
322
|
+
public error kind of the upstream crate, so a caller can match on what hyper or h2 would
|
|
323
|
+
have reported. `ConnectionClosedError` is **protocol-neutral** — hyper's `Io` and h2's
|
|
324
|
+
`Io`: the transport failed with work in flight — so it sits directly under the root. The
|
|
325
|
+
**HTTP/1** errors mirror hyper's `Error` kinds under `H1Error`; the **HTTP/2** errors
|
|
326
|
+
mirror the h2 crate's under `H2Error`:
|
|
282
327
|
|
|
283
328
|
```
|
|
284
329
|
HTTPunkError
|
|
285
|
-
├── ConnectionClosedError
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
330
|
+
├── ConnectionClosedError transport closed / IO error with work in flight (hyper Io, h2 Io)
|
|
331
|
+
├── H1Error base for HTTP/1 errors (hyper `Error` kinds)
|
|
332
|
+
│ ├── H1ParseError malformed message head (hyper Parse) — args = (kind, message)
|
|
333
|
+
│ ├── H1BodyError malformed or truncated body (hyper Body) — args = (io_kind, message)
|
|
334
|
+
│ ├── H1IncompleteMessageError EOF while a message was still expected (hyper IncompleteMessage)
|
|
335
|
+
│ ├── H1UnexpectedMessageError bytes on an idle client connection (hyper UnexpectedMessage)
|
|
336
|
+
│ └── H1UserError local misuse hyper reports on the wire path (hyper User) — args = (kind, message)
|
|
337
|
+
└── H2Error base for HTTP/2 protocol errors
|
|
338
|
+
├── H2ProtocolError connection-level protocol violation (-> GOAWAY)
|
|
339
|
+
├── H2StreamError stream-level protocol violation (-> RST_STREAM)
|
|
340
|
+
├── H2UserError local API misuse
|
|
341
|
+
├── H2FlowControlError flow-control window over/underflow
|
|
342
|
+
├── GoAwayError the peer sent GOAWAY
|
|
343
|
+
└── StreamResetError the peer sent RST_STREAM for a stream
|
|
293
344
|
```
|
|
294
345
|
|
|
295
|
-
Catch `H2Error` for
|
|
346
|
+
Catch `H1Error` / `H2Error` for protocol failures, `ConnectionClosedError` for a dropped
|
|
296
347
|
transport, or `HTTPunkError` for anything httpunk raises.
|
|
297
348
|
|
|
298
349
|
`GoAwayError` carries `last_stream_id`, `error_code` and `debug_data`; `StreamResetError`
|
|
@@ -300,7 +351,7 @@ carries `stream_id` and `error_code`. Error codes are `H2Reason` members (an `In
|
|
|
300
351
|
they compare equal to plain ints) for known codes, or a raw int otherwise.
|
|
301
352
|
|
|
302
353
|
```python
|
|
303
|
-
from httpunk import ConnectionClosedError, GoAwayError, HTTPunkError, StreamResetError
|
|
354
|
+
from httpunk import ConnectionClosedError, GoAwayError, H1BodyError, HTTPunkError, StreamResetError
|
|
304
355
|
|
|
305
356
|
try:
|
|
306
357
|
resp = await conn.request("GET", "/")
|
|
@@ -310,6 +361,9 @@ except StreamResetError as exc:
|
|
|
310
361
|
except GoAwayError as exc:
|
|
311
362
|
# streams above last_stream_id were not processed and are safe to retry
|
|
312
363
|
print("server going away:", exc.last_stream_id)
|
|
364
|
+
except H1BodyError as exc:
|
|
365
|
+
io_kind, message = exc.args
|
|
366
|
+
print("truncated" if io_kind == "unexpected_eof" else "malformed body")
|
|
313
367
|
except ConnectionClosedError:
|
|
314
368
|
print("transport dropped")
|
|
315
369
|
except HTTPunkError:
|
|
@@ -355,6 +409,25 @@ async with server:
|
|
|
355
409
|
await request.respond(200, body=b"ok")
|
|
356
410
|
```
|
|
357
411
|
|
|
412
|
+
To configure per-protocol options, use `auto.Builder` (hyper-util's `auto::Builder`): `http1()` /
|
|
413
|
+
`http2()` return sub-builders whose setters chain, `http1_only()` / `http2_only()` force a protocol,
|
|
414
|
+
and `serve_connection(transport, cancel=None)` sniffs and builds. `serve()` is the default-options
|
|
415
|
+
shortcut over it.
|
|
416
|
+
|
|
417
|
+
```python
|
|
418
|
+
builder = auto.Builder(backend=Backend.asyncio)
|
|
419
|
+
builder.http1().header_read_timeout(5.0).http2().max_concurrent_streams(200)
|
|
420
|
+
server = await builder.serve_connection(transport)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
The option set mirrors hyper's server builders, with hyper's defaults. HTTP/1
|
|
424
|
+
(`http1::Builder`): `header_read_timeout`, `keep_alive`, `max_headers`, `max_buf_size`,
|
|
425
|
+
`auto_date_header`, `title_case_headers`, `ignore_invalid_headers`. HTTP/2 (`http2::Builder`):
|
|
426
|
+
`max_concurrent_streams`, `initial_stream_window_size`, `initial_connection_window_size`,
|
|
427
|
+
`max_frame_size`, `max_header_list_size`, `max_pending_accept_reset_streams`,
|
|
428
|
+
`max_local_error_reset_streams`, `auto_date_header`, `max_send_buf_size`, plus h2's
|
|
429
|
+
`data_frame_budget`. The same keywords are accepted by `H1Server(...)` / `H2Server(...)` directly.
|
|
430
|
+
|
|
358
431
|
#### Connection pools
|
|
359
432
|
|
|
360
433
|
`httpunk.util.pool` provides three composable pools. A *connector* is an async callable
|
|
@@ -147,6 +147,7 @@ async for chunk in resp.aiter_bytes():
|
|
|
147
147
|
...
|
|
148
148
|
|
|
149
149
|
resp.trailers # a HeaderMap of trailing headers, or None
|
|
150
|
+
resp.version # httpunk.Version.HTTP_2 / HTTP_11 / HTTP_10 (≈ http::Version)
|
|
150
151
|
```
|
|
151
152
|
|
|
152
153
|
A response can be used as an async context manager to guarantee release (cancelling the body
|
|
@@ -162,7 +163,9 @@ async with await conn.request("GET", "/big") as resp:
|
|
|
162
163
|
|
|
163
164
|
`body` may be `bytes`, or a sync/async iterable of `bytes` (streamed as it is produced).
|
|
164
165
|
`trailers` are header fields sent after the body — chunked trailers on HTTP/1, a trailing
|
|
165
|
-
`HEADERS` frame on HTTP/2
|
|
166
|
+
`HEADERS` frame on HTTP/2. As in hyper, HTTP/1 trailers ride only on a chunked body (a
|
|
167
|
+
streamed one; a `bytes` body is `Content-Length`-framed and drops them) and only the fields
|
|
168
|
+
the request's own `Trailer` header declares are sent — undeclared ones are dropped:
|
|
166
169
|
|
|
167
170
|
```python
|
|
168
171
|
async def chunks():
|
|
@@ -171,7 +174,7 @@ async def chunks():
|
|
|
171
174
|
|
|
172
175
|
resp = await conn.request(
|
|
173
176
|
"POST", "/upload",
|
|
174
|
-
headers={"host": "example.com", "content-type": "application/octet-stream"},
|
|
177
|
+
headers={"host": "example.com", "content-type": "application/octet-stream", "trailer": "x-checksum"},
|
|
175
178
|
body=chunks(),
|
|
176
179
|
trailers={"x-checksum": "..."},
|
|
177
180
|
)
|
|
@@ -197,11 +200,51 @@ async with H1Server(transport, backend=Backend.asyncio) as server:
|
|
|
197
200
|
await request.respond(200, headers={"content-type": "text/plain"}, body=body)
|
|
198
201
|
```
|
|
199
202
|
|
|
200
|
-
Each `request` carries `method`, `target`/`path`, `headers`, and a streamable body
|
|
203
|
+
Each `request` carries `method`, `target`/`path`, `headers`, `version`, and a streamable body
|
|
201
204
|
(`request.read()` / `request.aiter_bytes()`). Answer it with `request.respond(status, *,
|
|
202
|
-
headers=None, body=None)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
+
headers=None, body=None, trailers=None)` — `trailers` are sent after the body (HTTP/1.1
|
|
206
|
+
chunked trailers; an HTTP/2 trailing HEADERS frame), like the client's `Request.trailers` and
|
|
207
|
+
under hyper's rules: on HTTP/1.1 they need a chunked (streamed) body and a `Trailer` header
|
|
208
|
+
declaring the fields, and go out only if the request declared `TE: trailers`; otherwise they
|
|
209
|
+
are dropped and the body ends normally, as hyper's server does.
|
|
210
|
+
On HTTP/2 you can also abort a single stream with `request.reset()` instead of responding
|
|
211
|
+
(e.g. when a handler fails) — the connection and its other streams keep running — and
|
|
212
|
+
`await request.reset_received()` resolves with the reason
|
|
213
|
+
when the *client* abandons the request (`RST_STREAM`, even after it finished sending), the
|
|
214
|
+
signal a long-running or streaming handler races against its own completion to stop early.
|
|
215
|
+
A reset also fails an in-flight `respond()` / `send_data` with `StreamResetError` carrying the
|
|
216
|
+
client's reason, including while the body is waiting on the app's next chunk. The HTTP/1 twin
|
|
217
|
+
is `await request.peer_closed()`: once the request body is complete, a client that closes the
|
|
218
|
+
connection before the response is done is detected (hyper's `mid_message_detect_eof`), the
|
|
219
|
+
in-flight `respond()` / `send_data` fail with `H1IncompleteMessageError`, and the accept loop
|
|
220
|
+
ends. The await resolves either way and never hangs: `True` when the client closed
|
|
221
|
+
mid-request, `False` once the exchange completed (or failed) first, at once if it already had.
|
|
222
|
+
`H1Server(half_close=True)` turns the detection off, as hyper's `half_close` does. A request
|
|
223
|
+
announcing an upgrade (`Upgrade: h2c`, `Upgrade: websocket`, CONNECT) is watched only from its
|
|
224
|
+
response head on, when it can no longer be detached or switched.
|
|
225
|
+
|
|
226
|
+
When `respond()` fails this way while streaming an async body, the failure is raised at once
|
|
227
|
+
even if the body is parked waiting for its next chunk: the producer is cancelled at that
|
|
228
|
+
`await` (as hyper drops the body future), its `finally` blocks run, and only then does
|
|
229
|
+
`respond()` raise. Cancelling the task that awaits `respond()` cancels the producer the same way.
|
|
230
|
+
|
|
231
|
+
For push-style producers (an ASGI `send()` loop, a server-sent-events endpoint) use
|
|
232
|
+
`request.send_response(status, *, headers=None, end_stream=False)`: it writes the head now and
|
|
233
|
+
returns a `SendStream` — h2's `SendResponse`/`SendStream` shape, identical on both protocols:
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
stream = await request.send_response(200, headers={"content-type": "text/event-stream"})
|
|
237
|
+
await stream.send_data(b"data: 1\n\n") # each write awaits backpressure
|
|
238
|
+
await stream.send_data(b"data: 2\n\n", end_stream=True)
|
|
239
|
+
# stream.send_trailers({...}) ends the body with trailers; stream.send_reset() aborts it
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`end_stream=True` on `send_response` is a bodyless response. Framing follows hyper: a
|
|
243
|
+
`content-length` you set is honoured, otherwise the body is chunked (HTTP/1.1) or
|
|
244
|
+
close-delimited (HTTP/1.0); on HEAD/204/304 body chunks are discarded, as hyper never polls
|
|
245
|
+
that body. `send_reset` is `RST_STREAM(CANCEL)` on HTTP/2; HTTP/1 has no per-request reset, so
|
|
246
|
+
there it closes the connection (what hyper does when a response body errors). `respond()` is
|
|
247
|
+
the pull convenience built on the same path.
|
|
205
248
|
|
|
206
249
|
HTTP/1 serves one request/response at a time (the loop won't yield the next until the current
|
|
207
250
|
one is answered); HTTP/2 multiplexes, so for concurrent handling you would spawn a task per
|
|
@@ -239,24 +282,32 @@ h.raw_items() # [(b'content-type', b'text/plain'), (b'set-cookie'
|
|
|
239
282
|
|
|
240
283
|
### Errors
|
|
241
284
|
|
|
242
|
-
httpunk's exceptions all derive from a common `HTTPunkError` root
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
the
|
|
285
|
+
httpunk's exceptions all derive from a common `HTTPunkError` root, with one class per
|
|
286
|
+
public error kind of the upstream crate, so a caller can match on what hyper or h2 would
|
|
287
|
+
have reported. `ConnectionClosedError` is **protocol-neutral** — hyper's `Io` and h2's
|
|
288
|
+
`Io`: the transport failed with work in flight — so it sits directly under the root. The
|
|
289
|
+
**HTTP/1** errors mirror hyper's `Error` kinds under `H1Error`; the **HTTP/2** errors
|
|
290
|
+
mirror the h2 crate's under `H2Error`:
|
|
246
291
|
|
|
247
292
|
```
|
|
248
293
|
HTTPunkError
|
|
249
|
-
├── ConnectionClosedError
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
294
|
+
├── ConnectionClosedError transport closed / IO error with work in flight (hyper Io, h2 Io)
|
|
295
|
+
├── H1Error base for HTTP/1 errors (hyper `Error` kinds)
|
|
296
|
+
│ ├── H1ParseError malformed message head (hyper Parse) — args = (kind, message)
|
|
297
|
+
│ ├── H1BodyError malformed or truncated body (hyper Body) — args = (io_kind, message)
|
|
298
|
+
│ ├── H1IncompleteMessageError EOF while a message was still expected (hyper IncompleteMessage)
|
|
299
|
+
│ ├── H1UnexpectedMessageError bytes on an idle client connection (hyper UnexpectedMessage)
|
|
300
|
+
│ └── H1UserError local misuse hyper reports on the wire path (hyper User) — args = (kind, message)
|
|
301
|
+
└── H2Error base for HTTP/2 protocol errors
|
|
302
|
+
├── H2ProtocolError connection-level protocol violation (-> GOAWAY)
|
|
303
|
+
├── H2StreamError stream-level protocol violation (-> RST_STREAM)
|
|
304
|
+
├── H2UserError local API misuse
|
|
305
|
+
├── H2FlowControlError flow-control window over/underflow
|
|
306
|
+
├── GoAwayError the peer sent GOAWAY
|
|
307
|
+
└── StreamResetError the peer sent RST_STREAM for a stream
|
|
257
308
|
```
|
|
258
309
|
|
|
259
|
-
Catch `H2Error` for
|
|
310
|
+
Catch `H1Error` / `H2Error` for protocol failures, `ConnectionClosedError` for a dropped
|
|
260
311
|
transport, or `HTTPunkError` for anything httpunk raises.
|
|
261
312
|
|
|
262
313
|
`GoAwayError` carries `last_stream_id`, `error_code` and `debug_data`; `StreamResetError`
|
|
@@ -264,7 +315,7 @@ carries `stream_id` and `error_code`. Error codes are `H2Reason` members (an `In
|
|
|
264
315
|
they compare equal to plain ints) for known codes, or a raw int otherwise.
|
|
265
316
|
|
|
266
317
|
```python
|
|
267
|
-
from httpunk import ConnectionClosedError, GoAwayError, HTTPunkError, StreamResetError
|
|
318
|
+
from httpunk import ConnectionClosedError, GoAwayError, H1BodyError, HTTPunkError, StreamResetError
|
|
268
319
|
|
|
269
320
|
try:
|
|
270
321
|
resp = await conn.request("GET", "/")
|
|
@@ -274,6 +325,9 @@ except StreamResetError as exc:
|
|
|
274
325
|
except GoAwayError as exc:
|
|
275
326
|
# streams above last_stream_id were not processed and are safe to retry
|
|
276
327
|
print("server going away:", exc.last_stream_id)
|
|
328
|
+
except H1BodyError as exc:
|
|
329
|
+
io_kind, message = exc.args
|
|
330
|
+
print("truncated" if io_kind == "unexpected_eof" else "malformed body")
|
|
277
331
|
except ConnectionClosedError:
|
|
278
332
|
print("transport dropped")
|
|
279
333
|
except HTTPunkError:
|
|
@@ -319,6 +373,25 @@ async with server:
|
|
|
319
373
|
await request.respond(200, body=b"ok")
|
|
320
374
|
```
|
|
321
375
|
|
|
376
|
+
To configure per-protocol options, use `auto.Builder` (hyper-util's `auto::Builder`): `http1()` /
|
|
377
|
+
`http2()` return sub-builders whose setters chain, `http1_only()` / `http2_only()` force a protocol,
|
|
378
|
+
and `serve_connection(transport, cancel=None)` sniffs and builds. `serve()` is the default-options
|
|
379
|
+
shortcut over it.
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
builder = auto.Builder(backend=Backend.asyncio)
|
|
383
|
+
builder.http1().header_read_timeout(5.0).http2().max_concurrent_streams(200)
|
|
384
|
+
server = await builder.serve_connection(transport)
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The option set mirrors hyper's server builders, with hyper's defaults. HTTP/1
|
|
388
|
+
(`http1::Builder`): `header_read_timeout`, `keep_alive`, `max_headers`, `max_buf_size`,
|
|
389
|
+
`auto_date_header`, `title_case_headers`, `ignore_invalid_headers`. HTTP/2 (`http2::Builder`):
|
|
390
|
+
`max_concurrent_streams`, `initial_stream_window_size`, `initial_connection_window_size`,
|
|
391
|
+
`max_frame_size`, `max_header_list_size`, `max_pending_accept_reset_streams`,
|
|
392
|
+
`max_local_error_reset_streams`, `auto_date_header`, `max_send_buf_size`, plus h2's
|
|
393
|
+
`data_frame_budget`. The same keywords are accepted by `H1Server(...)` / `H2Server(...)` directly.
|
|
394
|
+
|
|
322
395
|
#### Connection pools
|
|
323
396
|
|
|
324
397
|
`httpunk.util.pool` provides three composable pools. A *connector* is an async callable
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
use crate::codec::{SendError, UserError};
|
|
2
|
+
use crate::frame::StreamId;
|
|
3
|
+
use crate::proto::{self, Initiator};
|
|
4
|
+
|
|
5
|
+
use bytes::Bytes;
|
|
6
|
+
use std::{error, fmt, io};
|
|
7
|
+
|
|
8
|
+
pub use crate::frame::Reason;
|
|
9
|
+
|
|
10
|
+
/// Represents HTTP/2 operation errors.
|
|
11
|
+
///
|
|
12
|
+
/// `Error` covers error cases raised by protocol errors caused by the
|
|
13
|
+
/// peer, I/O (transport) errors, and errors caused by the user of the library.
|
|
14
|
+
///
|
|
15
|
+
/// If the error was caused by the remote peer, then it will contain a
|
|
16
|
+
/// [`Reason`] which can be obtained with the [`reason`] function.
|
|
17
|
+
///
|
|
18
|
+
/// [`Reason`]: struct.Reason.html
|
|
19
|
+
/// [`reason`]: #method.reason
|
|
20
|
+
#[derive(Debug)]
|
|
21
|
+
pub struct Error {
|
|
22
|
+
kind: Kind,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
#[derive(Debug)]
|
|
26
|
+
enum Kind {
|
|
27
|
+
/// A RST_STREAM frame was received or sent.
|
|
28
|
+
#[allow(dead_code)]
|
|
29
|
+
Reset(StreamId, Reason, Initiator),
|
|
30
|
+
|
|
31
|
+
/// A GO_AWAY frame was received or sent.
|
|
32
|
+
GoAway(Bytes, Reason, Initiator),
|
|
33
|
+
|
|
34
|
+
/// The user created an error from a bare Reason.
|
|
35
|
+
Reason(Reason),
|
|
36
|
+
|
|
37
|
+
/// An error resulting from an invalid action taken by the user of this
|
|
38
|
+
/// library.
|
|
39
|
+
User(UserError),
|
|
40
|
+
|
|
41
|
+
/// An `io::Error` occurred while trying to read or write.
|
|
42
|
+
Io(io::Error),
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ===== impl Error =====
|
|
46
|
+
|
|
47
|
+
impl Error {
|
|
48
|
+
/// If the error was caused by the remote peer, the error reason.
|
|
49
|
+
///
|
|
50
|
+
/// This is either an error received by the peer or caused by an invalid
|
|
51
|
+
/// action taken by the peer (i.e. a protocol error).
|
|
52
|
+
pub fn reason(&self) -> Option<Reason> {
|
|
53
|
+
match self.kind {
|
|
54
|
+
Kind::Reset(_, reason, _) | Kind::GoAway(_, reason, _) | Kind::Reason(reason) => {
|
|
55
|
+
Some(reason)
|
|
56
|
+
}
|
|
57
|
+
_ => None,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Returns true if the error is an io::Error
|
|
62
|
+
pub fn is_io(&self) -> bool {
|
|
63
|
+
matches!(self.kind, Kind::Io(..))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Returns the error if the error is an io::Error
|
|
67
|
+
pub fn get_io(&self) -> Option<&io::Error> {
|
|
68
|
+
match self.kind {
|
|
69
|
+
Kind::Io(ref e) => Some(e),
|
|
70
|
+
_ => None,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// Returns the error if the error is an io::Error
|
|
75
|
+
pub fn into_io(self) -> Option<io::Error> {
|
|
76
|
+
match self.kind {
|
|
77
|
+
Kind::Io(e) => Some(e),
|
|
78
|
+
_ => None,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
pub fn from_io(err: io::Error) -> Self {
|
|
83
|
+
Error {
|
|
84
|
+
kind: Kind::Io(err),
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Returns true if the error is from a `GOAWAY`.
|
|
89
|
+
pub fn is_go_away(&self) -> bool {
|
|
90
|
+
matches!(self.kind, Kind::GoAway(..))
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Returns true if the error is from a `RST_STREAM`.
|
|
94
|
+
pub fn is_reset(&self) -> bool {
|
|
95
|
+
matches!(self.kind, Kind::Reset(..))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/// Returns true if the error was received in a frame from the remote.
|
|
99
|
+
///
|
|
100
|
+
/// Such as from a received `RST_STREAM` or `GOAWAY` frame.
|
|
101
|
+
pub fn is_remote(&self) -> bool {
|
|
102
|
+
matches!(
|
|
103
|
+
self.kind,
|
|
104
|
+
Kind::GoAway(_, _, Initiator::Remote) | Kind::Reset(_, _, Initiator::Remote)
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/// Returns true if the error was created by `h2`.
|
|
109
|
+
///
|
|
110
|
+
/// Such as noticing some protocol error and sending a GOAWAY or RST_STREAM.
|
|
111
|
+
pub fn is_library(&self) -> bool {
|
|
112
|
+
matches!(
|
|
113
|
+
self.kind,
|
|
114
|
+
Kind::GoAway(_, _, Initiator::Library) | Kind::Reset(_, _, Initiator::Library)
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
impl From<proto::Error> for Error {
|
|
120
|
+
fn from(src: proto::Error) -> Error {
|
|
121
|
+
use crate::proto::Error::*;
|
|
122
|
+
|
|
123
|
+
Error {
|
|
124
|
+
kind: match src {
|
|
125
|
+
Reset(stream_id, reason, initiator) => Kind::Reset(stream_id, reason, initiator),
|
|
126
|
+
GoAway(debug_data, reason, initiator) => {
|
|
127
|
+
Kind::GoAway(debug_data, reason, initiator)
|
|
128
|
+
}
|
|
129
|
+
Io(kind, inner) => {
|
|
130
|
+
Kind::Io(inner.map_or_else(|| kind.into(), |inner| io::Error::new(kind, inner)))
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
impl From<Reason> for Error {
|
|
138
|
+
fn from(src: Reason) -> Error {
|
|
139
|
+
Error {
|
|
140
|
+
kind: Kind::Reason(src),
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
impl From<SendError> for Error {
|
|
146
|
+
fn from(src: SendError) -> Error {
|
|
147
|
+
match src {
|
|
148
|
+
SendError::User(e) => e.into(),
|
|
149
|
+
SendError::Connection(e) => e.into(),
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
impl From<UserError> for Error {
|
|
155
|
+
fn from(src: UserError) -> Error {
|
|
156
|
+
Error {
|
|
157
|
+
kind: Kind::User(src),
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
impl fmt::Display for Error {
|
|
163
|
+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
164
|
+
let debug_data = match self.kind {
|
|
165
|
+
Kind::Reset(_, reason, Initiator::User) => {
|
|
166
|
+
return write!(fmt, "stream error sent by user: {}", reason)
|
|
167
|
+
}
|
|
168
|
+
Kind::Reset(_, reason, Initiator::Library) => {
|
|
169
|
+
return write!(fmt, "stream error detected: {}", reason)
|
|
170
|
+
}
|
|
171
|
+
Kind::Reset(_, reason, Initiator::Remote) => {
|
|
172
|
+
return write!(fmt, "stream error received: {}", reason)
|
|
173
|
+
}
|
|
174
|
+
Kind::GoAway(ref debug_data, reason, Initiator::User) => {
|
|
175
|
+
write!(fmt, "connection error sent by user: {}", reason)?;
|
|
176
|
+
debug_data
|
|
177
|
+
}
|
|
178
|
+
Kind::GoAway(ref debug_data, reason, Initiator::Library) => {
|
|
179
|
+
write!(fmt, "connection error detected: {}", reason)?;
|
|
180
|
+
debug_data
|
|
181
|
+
}
|
|
182
|
+
Kind::GoAway(ref debug_data, reason, Initiator::Remote) => {
|
|
183
|
+
write!(fmt, "connection error received: {}", reason)?;
|
|
184
|
+
debug_data
|
|
185
|
+
}
|
|
186
|
+
Kind::Reason(reason) => return write!(fmt, "protocol error: {}", reason),
|
|
187
|
+
Kind::User(ref e) => return write!(fmt, "user error: {}", e),
|
|
188
|
+
Kind::Io(ref e) => return e.fmt(fmt),
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if !debug_data.is_empty() {
|
|
192
|
+
write!(fmt, " ({:?})", debug_data)?;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
Ok(())
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
impl error::Error for Error {}
|
|
200
|
+
|
|
201
|
+
#[cfg(test)]
|
|
202
|
+
mod tests {
|
|
203
|
+
use super::Error;
|
|
204
|
+
use crate::Reason;
|
|
205
|
+
|
|
206
|
+
#[test]
|
|
207
|
+
fn error_from_reason() {
|
|
208
|
+
let err = Error::from(Reason::HTTP_1_1_REQUIRED);
|
|
209
|
+
assert_eq!(err.reason(), Some(Reason::HTTP_1_1_REQUIRED));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
//! with `scripts/vendor-h2.sh`.
|
|
4
4
|
//!
|
|
5
5
|
//! Only h2's **synchronous, sans-IO** modules are vendored: `frame` (HTTP/2
|
|
6
|
-
//! frame parse/serialize) and `hpack` (HPACK header compression),
|
|
7
|
-
//! `ext::Protocol` type they depend on
|
|
8
|
-
//!
|
|
9
|
-
//!
|
|
6
|
+
//! frame parse/serialize) and `hpack` (HPACK header compression), the tiny
|
|
7
|
+
//! `ext::Protocol` type they depend on, the stream state machine + flow control
|
|
8
|
+
//! (`proto::streams::{state,flow_control}`), the error types (`proto::error`,
|
|
9
|
+
//! `codec::error`, the public `error`). h2's async `codec`, the streams store
|
|
10
|
+
//! and orchestration, `client` and `server` are intentionally *not* vendored —
|
|
11
|
+
//! their state is re-expressed in httpunk's `src/h2/conn.rs`, their async half
|
|
12
|
+
//! in Python.
|
|
10
13
|
//!
|
|
11
14
|
//! The `.rs` files under here are kept **byte-identical to upstream modulo a
|
|
12
15
|
//! uniform `crate::` -> `crate::` rewrite** the vendor script applies, so
|
|
@@ -33,7 +36,12 @@ macro_rules! proto_err {
|
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
pub mod codec;
|
|
39
|
+
pub mod error;
|
|
36
40
|
pub mod ext;
|
|
37
41
|
pub mod frame;
|
|
38
42
|
pub mod hpack;
|
|
39
43
|
pub mod proto;
|
|
44
|
+
|
|
45
|
+
// h2 lib.rs re-exports its public `Error` at the crate root; `proto/streams/state.rs`
|
|
46
|
+
// names it as `crate::Error` (`ensure_reason`).
|
|
47
|
+
pub use crate::error::Error;
|