httpunk 0.3.0__tar.gz → 0.4.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.3.0 → httpunk-0.4.1}/Cargo.lock +1 -1
- {httpunk-0.3.0 → httpunk-0.4.1}/Cargo.toml +1 -1
- {httpunk-0.3.0 → httpunk-0.4.1}/PKG-INFO +8 -2
- {httpunk-0.3.0 → httpunk-0.4.1}/README.md +6 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/common/date.rs +0 -8
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/lib.rs +3 -3
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/httpunk.rs +154 -46
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/_backend/asyncio.py +157 -30
- httpunk-0.4.1/httpunk/_backend/tonio.py +308 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/_common.py +7 -6
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/_httpunk.pyi +35 -20
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/client.py +35 -7
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/connection.py +30 -26
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/server.py +229 -136
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/share.py +36 -5
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/client.py +7 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/connection.py +61 -34
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/server.py +24 -22
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/pool.py +1 -1
- {httpunk-0.3.0 → httpunk-0.4.1}/pyproject.toml +1 -1
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h1/codec.rs +185 -77
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h1/conn.rs +57 -42
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/codec.rs +22 -14
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/conn.rs +302 -168
- {httpunk-0.3.0 → httpunk-0.4.1}/src/http/mod.rs +1 -1
- {httpunk-0.3.0 → httpunk-0.4.1}/src/lib.rs +1 -0
- httpunk-0.4.1/src/py.rs +105 -0
- httpunk-0.4.1/tests/_transport.py +24 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_asyncio_backend.py +119 -4
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_asyncio_e2e.py +243 -0
- httpunk-0.4.1/tests/test_backend.py +270 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_error_retention.py +3 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h1_codec.py +181 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h1_conn.py +48 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h1_server.py +245 -2
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_codec.py +17 -0
- httpunk-0.4.1/tests/test_h2_flow_control.py +174 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_server.py +3 -2
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_settings.py +13 -1
- httpunk-0.4.1/tests/test_h2_write_batch.py +115 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_auto.py +19 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_graceful.py +77 -5
- httpunk-0.4.1/tests/test_util_tls.py +397 -0
- httpunk-0.3.0/httpunk/_backend/tonio.py +0 -152
- httpunk-0.3.0/tests/test_backend.py +0 -130
- httpunk-0.3.0/tests/test_h2_flow_control.py +0 -74
- httpunk-0.3.0/tests/test_util_tls.py +0 -113
- {httpunk-0.3.0 → httpunk-0.4.1}/LICENSE +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/Cargo.toml +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/LICENSE +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/UPSTREAM_VERSION +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/codec/error.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/codec/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/error.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/ext.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/data.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/go_away.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/head.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/headers.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/ping.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/priority.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/reason.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/reset.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/settings.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/stream_id.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/util.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/window_update.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/decoder.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/encoder.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/header.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/huffman/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/huffman/table.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/table.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/lib.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/error.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/streams/flow_control.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/streams/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/streams/state.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/Cargo.toml +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/LICENSE +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/UPSTREAM_VERSION +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/body/length.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/body/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/cfg.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/common/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/error.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/ext/h1_reason_phrase.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/ext/informational.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/ext/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/headers.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/decode.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/encode.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/io.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/role.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/trace.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/Cargo.toml +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/LICENSE +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/UPSTREAM_VERSION +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/src/client/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/src/client/proxy/matcher.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/src/client/proxy/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/src/lib.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/__init__.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/_backend/__init__.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/asyncio.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/exceptions.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/__init__.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/__init__.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/share.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/stream.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/http.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/py.typed +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/types.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/__init__.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/auto.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/client.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/graceful.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/proxy.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/errors.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h1/errors.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h1/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/errors.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/mod.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/reason.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/settings.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/streams.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/src/proxy.rs +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/_client.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/conftest.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_asyncio_client_protocol.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_asyncio_protocol.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h1_decode.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_conn.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_errors.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_flow.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_ready.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_state.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_headermap.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_connect.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_pool.py +0 -0
- {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_proxy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: httpunk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
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.17 ; 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
|
|
@@ -275,6 +275,12 @@ await stream.send_data(b"data: 2\n\n", end_stream=True)
|
|
|
275
275
|
# stream.send_trailers({...}) ends the body with trailers; stream.send_reset() aborts it
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
+
On HTTP/1, `send_response` arms the mid-message detection described above (the body may
|
|
279
|
+
park between chunks); `detect_eof=False` skips that, so no read is parked for the response
|
|
280
|
+
and a client that closes is noticed one write later, at the peer's RST, instead — unless
|
|
281
|
+
`peer_closed()` asks, which still arms on demand. This is httpunk's own knob (hyper's read is
|
|
282
|
+
always on); HTTP/2 accepts it and ignores it, a reset arrives through the connection anyway.
|
|
283
|
+
|
|
278
284
|
`end_stream=True` on `send_response` is a bodyless response. Framing follows hyper: a
|
|
279
285
|
`content-length` you set is honoured, otherwise the body is chunked (HTTP/1.1) or
|
|
280
286
|
close-delimited (HTTP/1.0); on HEAD/204/304 body chunks are discarded, as hyper never polls
|
|
@@ -239,6 +239,12 @@ await stream.send_data(b"data: 2\n\n", end_stream=True)
|
|
|
239
239
|
# stream.send_trailers({...}) ends the body with trailers; stream.send_reset() aborts it
|
|
240
240
|
```
|
|
241
241
|
|
|
242
|
+
On HTTP/1, `send_response` arms the mid-message detection described above (the body may
|
|
243
|
+
park between chunks); `detect_eof=False` skips that, so no read is parked for the response
|
|
244
|
+
and a client that closes is noticed one write later, at the peer's RST, instead — unless
|
|
245
|
+
`peer_closed()` asks, which still arms on demand. This is httpunk's own knob (hyper's read is
|
|
246
|
+
always on); HTTP/2 accepts it and ignores it, a reset arrives through the connection anyway.
|
|
247
|
+
|
|
242
248
|
`end_stream=True` on `send_response` is a bodyless response. Framing follows hyper: a
|
|
243
249
|
`content-length` you set is honoured, otherwise the body is chunked (HTTP/1.1) or
|
|
244
250
|
close-delimited (HTTP/1.0); on HEAD/204/304 body chunks are discarded, as hyper never polls
|
|
@@ -3,7 +3,6 @@ use std::fmt::{self, Write};
|
|
|
3
3
|
use std::str;
|
|
4
4
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
5
5
|
|
|
6
|
-
#[cfg(feature = "http2")]
|
|
7
6
|
use http::header::HeaderValue;
|
|
8
7
|
use httpdate::HttpDate;
|
|
9
8
|
|
|
@@ -24,7 +23,6 @@ pub fn update() {
|
|
|
24
23
|
});
|
|
25
24
|
}
|
|
26
25
|
|
|
27
|
-
#[cfg(feature = "http2")]
|
|
28
26
|
pub fn update_and_header_value() -> HeaderValue {
|
|
29
27
|
CACHED.with(|cache| {
|
|
30
28
|
let mut cache = cache.borrow_mut();
|
|
@@ -36,7 +34,6 @@ pub fn update_and_header_value() -> HeaderValue {
|
|
|
36
34
|
struct CachedDate {
|
|
37
35
|
bytes: [u8; DATE_VALUE_LENGTH],
|
|
38
36
|
pos: usize,
|
|
39
|
-
#[cfg(feature = "http2")]
|
|
40
37
|
header_value: HeaderValue,
|
|
41
38
|
next_update: SystemTime,
|
|
42
39
|
}
|
|
@@ -48,7 +45,6 @@ impl CachedDate {
|
|
|
48
45
|
let mut cache = CachedDate {
|
|
49
46
|
bytes: [0; DATE_VALUE_LENGTH],
|
|
50
47
|
pos: 0,
|
|
51
|
-
#[cfg(feature = "http2")]
|
|
52
48
|
header_value: HeaderValue::from_static(""),
|
|
53
49
|
next_update: SystemTime::now(),
|
|
54
50
|
};
|
|
@@ -84,14 +80,10 @@ impl CachedDate {
|
|
|
84
80
|
self.render_http2();
|
|
85
81
|
}
|
|
86
82
|
|
|
87
|
-
#[cfg(feature = "http2")]
|
|
88
83
|
fn render_http2(&mut self) {
|
|
89
84
|
self.header_value = HeaderValue::from_bytes(self.buffer())
|
|
90
85
|
.expect("Date format should be valid HeaderValue");
|
|
91
86
|
}
|
|
92
|
-
|
|
93
|
-
#[cfg(not(feature = "http2"))]
|
|
94
|
-
fn render_http2(&mut self) {}
|
|
95
87
|
}
|
|
96
88
|
|
|
97
89
|
impl fmt::Write for CachedDate {
|
|
@@ -58,7 +58,7 @@ pub mod proto;
|
|
|
58
58
|
pub use crate::proto::h1::httpunk::{
|
|
59
59
|
connection_any_close, date_header_value, encode_request, encode_response, error_kind,
|
|
60
60
|
error_parse_kind, error_user_kind, has_h2_prefix, parse_request, parse_response,
|
|
61
|
-
server_parse_failure, too_large_error, BodyDecode, BodyDecoder, BodyEncoder,
|
|
62
|
-
|
|
63
|
-
MINIMUM_MAX_BUFFER_SIZE,
|
|
61
|
+
server_parse_failure, too_large_error, BodyDecode, BodyDecoder, BodyEncoder, BodyTail,
|
|
62
|
+
FramedChunk, ParsedHead, ParsedRequest, CHUNKED_TERMINATOR, CONTINUE_RESPONSE,
|
|
63
|
+
DEFAULT_MAX_BUFFER_SIZE, H2_PREFACE, MINIMUM_MAX_BUFFER_SIZE,
|
|
64
64
|
};
|
|
@@ -19,7 +19,7 @@ use http::header::{HeaderValue, CONNECTION};
|
|
|
19
19
|
use super::decode::Decoder;
|
|
20
20
|
use super::io::MemRead;
|
|
21
21
|
use super::role::{Client, Server};
|
|
22
|
-
use super::{Encode, Encoder, Http1Transaction, ParseContext};
|
|
22
|
+
use super::{Encode, EncodedBuf, Encoder, Http1Transaction, ParseContext};
|
|
23
23
|
use crate::body::DecodedLength;
|
|
24
24
|
use crate::error::{Header, Kind, Parse, User};
|
|
25
25
|
use crate::headers::connection_keep_alive;
|
|
@@ -189,10 +189,12 @@ pub struct ParsedHead {
|
|
|
189
189
|
|
|
190
190
|
/// A parsed request head (server side), with everything the Python driver needs.
|
|
191
191
|
pub struct ParsedRequest {
|
|
192
|
-
|
|
192
|
+
/// The parsed method and request-target as hyper holds them (`RequestLine`):
|
|
193
|
+
/// no string is rendered here — the caller decides what to build from them.
|
|
194
|
+
pub method: Method,
|
|
193
195
|
/// The request-target verbatim: origin-form path+query, absolute-form (proxy),
|
|
194
196
|
/// or authority-form (CONNECT).
|
|
195
|
-
pub target:
|
|
197
|
+
pub target: Uri,
|
|
196
198
|
pub headers: HeaderMap,
|
|
197
199
|
pub body: BodyDecode,
|
|
198
200
|
pub keep_alive: bool,
|
|
@@ -218,10 +220,95 @@ pub fn connection_any_close(headers: &HeaderMap) -> bool {
|
|
|
218
220
|
crate::headers::connection_any_close(headers)
|
|
219
221
|
}
|
|
220
222
|
|
|
221
|
-
/// Owns the vendored body `Encoder` and frames
|
|
222
|
-
///
|
|
223
|
+
/// Owns the vendored body `Encoder` and frames body chunks, so hyper's body
|
|
224
|
+
/// `Encoder` never crosses the crate boundary. Nothing here copies payload bytes:
|
|
225
|
+
/// a chunk is framed over a BORROW of the caller's buffer (`FramedChunk`) and
|
|
226
|
+
/// written out once, into the caller's destination, by `write_to` — hyper's own
|
|
227
|
+
/// shape, where `Encoder::encode` wraps the caller's `Buf` (`BufKind::Exact` /
|
|
228
|
+
/// `Chunked`) and the write buffer copies it at most once.
|
|
223
229
|
pub struct BodyEncoder(Encoder);
|
|
224
230
|
|
|
231
|
+
/// One body chunk as hyper's `Encoder` framed it, over a borrowed chunk: the
|
|
232
|
+
/// chunked size line + chunk + CRLF, the chunk verbatim (content-length within the
|
|
233
|
+
/// declared length, or close-delimited), or the chunk truncated to what the declared
|
|
234
|
+
/// Content-Length still allows (`BufKind::Limited`). Payload bytes are touched only
|
|
235
|
+
/// by `write_to`.
|
|
236
|
+
pub struct FramedChunk<'a> {
|
|
237
|
+
buf: EncodedBuf<&'a [u8]>,
|
|
238
|
+
verbatim: bool,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
impl FramedChunk<'_> {
|
|
242
|
+
/// Bytes `write_to` will produce.
|
|
243
|
+
pub fn len(&self) -> usize {
|
|
244
|
+
self.buf.remaining()
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
pub fn is_empty(&self) -> bool {
|
|
248
|
+
self.len() == 0
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// The framed bytes ARE the chunk (hyper `BufKind::Exact`): the caller can hand
|
|
252
|
+
/// its own buffer on without copying anything.
|
|
253
|
+
pub fn is_verbatim(&self) -> bool {
|
|
254
|
+
self.verbatim
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// Write the framed chunk into `dst`, which must be exactly `len()` long.
|
|
258
|
+
pub fn write_to(self, dst: &mut [u8]) {
|
|
259
|
+
write_buf(self.buf, dst);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/// How a body ends: nothing (content-length / close-delimited), the fixed chunked
|
|
264
|
+
/// terminator, or a chunked terminator carrying a trailer block.
|
|
265
|
+
pub enum BodyTail {
|
|
266
|
+
None,
|
|
267
|
+
/// `0\r\n\r\n` (`CHUNKED_TERMINATOR`), what hyper's `Encoder::end` emits for a
|
|
268
|
+
/// chunked body — a constant, so the caller may keep one buffer for it.
|
|
269
|
+
Terminator,
|
|
270
|
+
Trailers(EncodedBuf<Bytes>),
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// The chunked body terminator hyper's `Encoder::end` emits (encode.rs
|
|
274
|
+
/// `BufKind::ChunkedEnd`).
|
|
275
|
+
pub const CHUNKED_TERMINATOR: &[u8] = b"0\r\n\r\n";
|
|
276
|
+
|
|
277
|
+
impl BodyTail {
|
|
278
|
+
pub fn len(&self) -> usize {
|
|
279
|
+
match self {
|
|
280
|
+
BodyTail::None => 0,
|
|
281
|
+
BodyTail::Terminator => CHUNKED_TERMINATOR.len(),
|
|
282
|
+
BodyTail::Trailers(b) => b.remaining(),
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
pub fn is_empty(&self) -> bool {
|
|
287
|
+
self.len() == 0
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// Write the tail into `dst`, which must be exactly `len()` long.
|
|
291
|
+
pub fn write_to(self, dst: &mut [u8]) {
|
|
292
|
+
match self {
|
|
293
|
+
BodyTail::None => {}
|
|
294
|
+
BodyTail::Terminator => dst.copy_from_slice(CHUNKED_TERMINATOR),
|
|
295
|
+
BodyTail::Trailers(b) => write_buf(b, dst),
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
fn write_buf(mut buf: impl Buf, dst: &mut [u8]) {
|
|
301
|
+
debug_assert_eq!(buf.remaining(), dst.len());
|
|
302
|
+
let mut off = 0;
|
|
303
|
+
while buf.has_remaining() {
|
|
304
|
+
let chunk = buf.chunk();
|
|
305
|
+
let n = chunk.len();
|
|
306
|
+
dst[off..off + n].copy_from_slice(chunk);
|
|
307
|
+
off += n;
|
|
308
|
+
buf.advance(n);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
225
312
|
impl BodyEncoder {
|
|
226
313
|
/// True when the framing carries no body (a `Content-Length: 0` / bodyless
|
|
227
314
|
/// encoder). hyper's `write_head` uses `Encoder::is_eof()` to skip polling the
|
|
@@ -245,43 +332,50 @@ impl BodyEncoder {
|
|
|
245
332
|
self.0.is_close_delimited()
|
|
246
333
|
}
|
|
247
334
|
|
|
248
|
-
///
|
|
249
|
-
pub fn
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
335
|
+
/// The body is `Transfer-Encoding: chunked` (`Encoder::is_chunked`).
|
|
336
|
+
pub fn is_chunked(&self) -> bool {
|
|
337
|
+
self.0.is_chunked()
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// Frame one non-empty body chunk over a borrow of it: hyper's `Encoder::encode`
|
|
341
|
+
/// with the chunk as the `Buf` — the encoder's Content-Length accounting and its
|
|
342
|
+
/// truncation of an over-long chunk (`BufKind::Limited`) run exactly as upstream's.
|
|
343
|
+
pub fn encode<'a>(&mut self, chunk: &'a [u8]) -> FramedChunk<'a> {
|
|
344
|
+
debug_assert!(!chunk.is_empty(), "encode() called with an empty chunk");
|
|
345
|
+
let chunked = self.0.is_chunked();
|
|
346
|
+
let buf = self.0.encode(chunk);
|
|
347
|
+
// `Exact` is the only kind whose output is the input: a content-length chunk
|
|
348
|
+
// within the declared length, or a close-delimited one. `Limited` is shorter,
|
|
349
|
+
// `Chunked` longer.
|
|
350
|
+
let verbatim = !chunked && buf.remaining() == chunk.len();
|
|
351
|
+
FramedChunk { buf, verbatim }
|
|
256
352
|
}
|
|
257
353
|
|
|
258
|
-
/// Finish the body: chunked terminator
|
|
259
|
-
///
|
|
354
|
+
/// Finish the body: the chunked terminator, or nothing for a content-length /
|
|
355
|
+
/// close-delimited body. `Err` if a declared Content-Length wasn't filled —
|
|
260
356
|
/// hyper's `end_body` maps the encoder's `NotEof` to
|
|
261
357
|
/// `Error::new_body_write_aborted()` (`User::BodyWriteAborted`, conn.rs
|
|
262
358
|
/// `end_body`) and closes the write side; the driver does the same.
|
|
263
|
-
pub fn end(self) -> Result<
|
|
359
|
+
pub fn end(self) -> Result<BodyTail, crate::Error> {
|
|
264
360
|
match self.0.end::<Bytes>() {
|
|
265
|
-
Ok(Some(
|
|
266
|
-
|
|
267
|
-
|
|
361
|
+
Ok(Some(buf)) => {
|
|
362
|
+
// encode.rs `end`: the one buffer it returns is `ChunkedEnd(b"0\r\n\r\n")`.
|
|
363
|
+
debug_assert_eq!(buf.remaining(), CHUNKED_TERMINATOR.len());
|
|
364
|
+
Ok(BodyTail::Terminator)
|
|
268
365
|
}
|
|
269
|
-
Ok(None) => Ok(
|
|
366
|
+
Ok(None) => Ok(BodyTail::None),
|
|
270
367
|
Err(not_eof) => Err(crate::Error::new_body_write_aborted().with(not_eof)),
|
|
271
368
|
}
|
|
272
369
|
}
|
|
273
370
|
|
|
274
|
-
/// Finish a chunked body with trailing headers:
|
|
275
|
-
///
|
|
371
|
+
/// Finish a chunked body with trailing headers: the terminating `0\r\n` + the
|
|
372
|
+
/// trailer block + `\r\n`. Only fields declared via
|
|
276
373
|
/// `into_chunked_with_trailing_fields` (the `Trailer` header) are sent (hyper's
|
|
277
374
|
/// `encode_trailers` filters + validates). Falls back to the plain terminator when
|
|
278
375
|
/// no declared trailer survives (or the body isn't chunked).
|
|
279
|
-
pub fn end_with_trailers(self, trailers: HeaderMap) -> Result<
|
|
376
|
+
pub fn end_with_trailers(self, trailers: HeaderMap) -> Result<BodyTail, crate::Error> {
|
|
280
377
|
match self.0.encode_trailers::<Bytes>(trailers, false) {
|
|
281
|
-
Some(
|
|
282
|
-
let n = buf.remaining();
|
|
283
|
-
Ok(buf.copy_to_bytes(n).to_vec())
|
|
284
|
-
}
|
|
378
|
+
Some(buf) => Ok(BodyTail::Trailers(buf)),
|
|
285
379
|
None => self.end(),
|
|
286
380
|
}
|
|
287
381
|
}
|
|
@@ -300,7 +394,7 @@ fn map_body(d: DecodedLength) -> BodyDecode {
|
|
|
300
394
|
}
|
|
301
395
|
}
|
|
302
396
|
|
|
303
|
-
/// Encode a request head into
|
|
397
|
+
/// Encode a request head into `dst` and return the body `BodyEncoder`.
|
|
304
398
|
///
|
|
305
399
|
/// `body`: `None` = no body; `Some(Some(n))` = `Content-Length: n`; `Some(None)`
|
|
306
400
|
/// = `Transfer-Encoding: chunked`. (hyper's `set_length` injects the matching
|
|
@@ -311,7 +405,8 @@ pub fn encode_request(
|
|
|
311
405
|
headers: HeaderMap,
|
|
312
406
|
body: Option<Option<u64>>,
|
|
313
407
|
http10: bool,
|
|
314
|
-
|
|
408
|
+
dst: &mut Vec<u8>,
|
|
409
|
+
) -> Result<BodyEncoder, crate::Error> {
|
|
315
410
|
// The request-target is serialized *as given* (hyper role.rs L1200 writes
|
|
316
411
|
// `msg.head.subject.1` via its `Display`, "not enforced or validated" —
|
|
317
412
|
// client/conn/http1.rs L194-204): a path-and-query `Uri` yields origin-form
|
|
@@ -346,12 +441,14 @@ pub fn encode_request(
|
|
|
346
441
|
title_case_headers: false,
|
|
347
442
|
date_header: false,
|
|
348
443
|
};
|
|
349
|
-
|
|
444
|
+
// `dst` is the caller's reusable head buffer (hyper's `WriteBuf.headers`, kept
|
|
445
|
+
// across messages): cleared here, written once; on error hyper rewinds it.
|
|
446
|
+
dst.clear();
|
|
350
447
|
// `Client::encode` reads the request's own `Trailer` header to allow-list the
|
|
351
448
|
// chunked trailer fields the body may emit (role.rs L1420-1431); undeclared
|
|
352
449
|
// fields are dropped by `encode_trailers`, as on the server.
|
|
353
|
-
let encoder = Client::encode(enc,
|
|
354
|
-
Ok(
|
|
450
|
+
let encoder = Client::encode(enc, dst)?;
|
|
451
|
+
Ok(BodyEncoder(encoder))
|
|
355
452
|
}
|
|
356
453
|
|
|
357
454
|
/// Parse a response head from `buf`. `Ok(None)` means "need more bytes"; on
|
|
@@ -423,8 +520,8 @@ pub fn parse_request(
|
|
|
423
520
|
Some(parsed) => {
|
|
424
521
|
let RequestLine(m, uri) = parsed.head.subject;
|
|
425
522
|
Ok(Some(ParsedRequest {
|
|
426
|
-
method: m
|
|
427
|
-
target: uri
|
|
523
|
+
method: m,
|
|
524
|
+
target: uri,
|
|
428
525
|
allow_trailers: crate::headers::te_is_trailers(&parsed.head.headers),
|
|
429
526
|
headers: parsed.head.headers,
|
|
430
527
|
body: map_body(parsed.decode),
|
|
@@ -438,7 +535,7 @@ pub fn parse_request(
|
|
|
438
535
|
}
|
|
439
536
|
}
|
|
440
537
|
|
|
441
|
-
/// Encode a response head into
|
|
538
|
+
/// Encode a response head into `dst` and return the body `BodyEncoder` (server
|
|
442
539
|
/// side; hyper role.rs `Server::encode` L364). `body`: `None` = no body;
|
|
443
540
|
/// `Some(Some(n))` = `Content-Length: n`; `Some(None)` = `Transfer-Encoding:
|
|
444
541
|
/// chunked`. `req_method` is the method of the request being answered (a response
|
|
@@ -458,7 +555,8 @@ pub fn encode_response(
|
|
|
458
555
|
http10: bool,
|
|
459
556
|
title_case_headers: bool,
|
|
460
557
|
date_header: bool,
|
|
461
|
-
|
|
558
|
+
dst: &mut Vec<u8>,
|
|
559
|
+
) -> Result<BodyEncoder, crate::Error> {
|
|
462
560
|
// `keep_alive` is hyper's `wants_keep_alive()` (the request was keep-alive, no
|
|
463
561
|
// graceful shutdown, keep-alive enabled); `http10` its `state.version == HTTP_10`
|
|
464
562
|
// (the request was 1.0). conn.rs `encode_head` runs `enforce_version` over the
|
|
@@ -495,23 +593,25 @@ pub fn encode_response(
|
|
|
495
593
|
if date_header {
|
|
496
594
|
crate::common::date::update();
|
|
497
595
|
}
|
|
498
|
-
|
|
596
|
+
// `dst` is the caller's reusable head buffer (hyper's `WriteBuf.headers`, kept
|
|
597
|
+
// across messages): cleared here, written once; on error hyper rewinds it.
|
|
598
|
+
dst.clear();
|
|
499
599
|
// `Server::encode` fails for the user errors hyper reports on the connection
|
|
500
600
|
// (`User::UnexpectedHeader`, `User::UnsupportedStatusCode`); `dst` is rewound
|
|
501
601
|
// to before the half-pushed head, so nothing of it reaches the wire.
|
|
502
|
-
let encoder = Server::encode(enc,
|
|
503
|
-
Ok(
|
|
602
|
+
let encoder = Server::encode(enc, dst)?;
|
|
603
|
+
Ok(BodyEncoder(encoder))
|
|
504
604
|
}
|
|
505
605
|
|
|
506
606
|
/// The current `Date` header value, from hyper's per-thread once-a-second cache
|
|
507
607
|
/// (common/date.rs `update` + `extend`) — so the h2 server's `Date` (hyper
|
|
508
608
|
/// proto/h2/server.rs L484 `or_insert_with(date::update_and_header_value)`) is the
|
|
509
609
|
/// same bytes the h1 encoder writes.
|
|
510
|
-
pub fn date_header_value() ->
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
610
|
+
pub fn date_header_value() -> HeaderValue {
|
|
611
|
+
// hyper's h2 server path (proto/h2/server.rs `date::update_and_header_value`):
|
|
612
|
+
// the cached `HeaderValue` beside the cached bytes, refreshed once a second and
|
|
613
|
+
// handed out as a clone (a refcount bump). The vendoring shim un-gates it.
|
|
614
|
+
crate::common::date::update_and_header_value()
|
|
515
615
|
}
|
|
516
616
|
|
|
517
617
|
/// A synchronous `MemRead` over an in-memory buffer, so the vendored `Decoder`
|
|
@@ -585,6 +685,14 @@ impl BodyDecoder {
|
|
|
585
685
|
self.read.buf.extend_from_slice(data);
|
|
586
686
|
}
|
|
587
687
|
|
|
688
|
+
/// Take over a buffer of body bytes another Rust buffer already holds (the
|
|
689
|
+
/// bytes that arrived with the head, split off the codec's read buffer): a move
|
|
690
|
+
/// when this decoder's buffer is empty, an append after any it already holds —
|
|
691
|
+
/// `BytesMut::unsplit` — never a copy through a Python object.
|
|
692
|
+
pub fn feed_buf(&mut self, data: BytesMut) {
|
|
693
|
+
self.read.buf.unsplit(data);
|
|
694
|
+
}
|
|
695
|
+
|
|
588
696
|
pub fn mark_eof(&mut self) {
|
|
589
697
|
self.read.eof = true;
|
|
590
698
|
}
|
|
@@ -598,8 +706,8 @@ impl BodyDecoder {
|
|
|
598
706
|
/// connection read buffer; the sans-IO facade hands them back so the driver
|
|
599
707
|
/// can carry them into the next request's codec (else a pipelined request is
|
|
600
708
|
/// lost and the connection deadlocks). Empty if none buffered.
|
|
601
|
-
pub fn take_buffered(&mut self) ->
|
|
602
|
-
self.read.buf.split()
|
|
709
|
+
pub fn take_buffered(&mut self) -> BytesMut {
|
|
710
|
+
self.read.buf.split()
|
|
603
711
|
}
|
|
604
712
|
|
|
605
713
|
/// Bytes buffered past the body (hyper's `read_buf` non-emptiness check,
|