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.
Files changed (146) hide show
  1. {httpunk-0.3.0 → httpunk-0.4.1}/Cargo.lock +1 -1
  2. {httpunk-0.3.0 → httpunk-0.4.1}/Cargo.toml +1 -1
  3. {httpunk-0.3.0 → httpunk-0.4.1}/PKG-INFO +8 -2
  4. {httpunk-0.3.0 → httpunk-0.4.1}/README.md +6 -0
  5. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/common/date.rs +0 -8
  6. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/lib.rs +3 -3
  7. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/httpunk.rs +154 -46
  8. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/_backend/asyncio.py +157 -30
  9. httpunk-0.4.1/httpunk/_backend/tonio.py +308 -0
  10. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/_common.py +7 -6
  11. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/_httpunk.pyi +35 -20
  12. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/client.py +35 -7
  13. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/connection.py +30 -26
  14. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/server.py +229 -136
  15. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/share.py +36 -5
  16. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/client.py +7 -0
  17. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/connection.py +61 -34
  18. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/server.py +24 -22
  19. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/pool.py +1 -1
  20. {httpunk-0.3.0 → httpunk-0.4.1}/pyproject.toml +1 -1
  21. {httpunk-0.3.0 → httpunk-0.4.1}/src/h1/codec.rs +185 -77
  22. {httpunk-0.3.0 → httpunk-0.4.1}/src/h1/conn.rs +57 -42
  23. {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/codec.rs +22 -14
  24. {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/conn.rs +302 -168
  25. {httpunk-0.3.0 → httpunk-0.4.1}/src/http/mod.rs +1 -1
  26. {httpunk-0.3.0 → httpunk-0.4.1}/src/lib.rs +1 -0
  27. httpunk-0.4.1/src/py.rs +105 -0
  28. httpunk-0.4.1/tests/_transport.py +24 -0
  29. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_asyncio_backend.py +119 -4
  30. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_asyncio_e2e.py +243 -0
  31. httpunk-0.4.1/tests/test_backend.py +270 -0
  32. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_error_retention.py +3 -0
  33. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h1_codec.py +181 -0
  34. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h1_conn.py +48 -0
  35. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h1_server.py +245 -2
  36. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_codec.py +17 -0
  37. httpunk-0.4.1/tests/test_h2_flow_control.py +174 -0
  38. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_server.py +3 -2
  39. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_settings.py +13 -1
  40. httpunk-0.4.1/tests/test_h2_write_batch.py +115 -0
  41. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_auto.py +19 -0
  42. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_graceful.py +77 -5
  43. httpunk-0.4.1/tests/test_util_tls.py +397 -0
  44. httpunk-0.3.0/httpunk/_backend/tonio.py +0 -152
  45. httpunk-0.3.0/tests/test_backend.py +0 -130
  46. httpunk-0.3.0/tests/test_h2_flow_control.py +0 -74
  47. httpunk-0.3.0/tests/test_util_tls.py +0 -113
  48. {httpunk-0.3.0 → httpunk-0.4.1}/LICENSE +0 -0
  49. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/Cargo.toml +0 -0
  50. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/LICENSE +0 -0
  51. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/UPSTREAM_VERSION +0 -0
  52. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/codec/error.rs +0 -0
  53. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/codec/mod.rs +0 -0
  54. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/error.rs +0 -0
  55. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/ext.rs +0 -0
  56. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/data.rs +0 -0
  57. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/go_away.rs +0 -0
  58. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/head.rs +0 -0
  59. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/headers.rs +0 -0
  60. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/mod.rs +0 -0
  61. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/ping.rs +0 -0
  62. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/priority.rs +0 -0
  63. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/reason.rs +0 -0
  64. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/reset.rs +0 -0
  65. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/settings.rs +0 -0
  66. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/stream_id.rs +0 -0
  67. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/util.rs +0 -0
  68. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/frame/window_update.rs +0 -0
  69. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/decoder.rs +0 -0
  70. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/encoder.rs +0 -0
  71. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/header.rs +0 -0
  72. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/huffman/mod.rs +0 -0
  73. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/huffman/table.rs +0 -0
  74. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/mod.rs +0 -0
  75. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/hpack/table.rs +0 -0
  76. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/lib.rs +0 -0
  77. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/error.rs +0 -0
  78. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/mod.rs +0 -0
  79. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/streams/flow_control.rs +0 -0
  80. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/streams/mod.rs +0 -0
  81. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-h2/src/proto/streams/state.rs +0 -0
  82. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/Cargo.toml +0 -0
  83. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/LICENSE +0 -0
  84. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/UPSTREAM_VERSION +0 -0
  85. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/body/length.rs +0 -0
  86. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/body/mod.rs +0 -0
  87. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/cfg.rs +0 -0
  88. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/common/mod.rs +0 -0
  89. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/error.rs +0 -0
  90. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/ext/h1_reason_phrase.rs +0 -0
  91. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/ext/informational.rs +0 -0
  92. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/ext/mod.rs +0 -0
  93. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/headers.rs +0 -0
  94. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/decode.rs +0 -0
  95. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/encode.rs +0 -0
  96. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/io.rs +0 -0
  97. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/mod.rs +0 -0
  98. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/h1/role.rs +0 -0
  99. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/proto/mod.rs +0 -0
  100. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper/src/trace.rs +0 -0
  101. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/Cargo.toml +0 -0
  102. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/LICENSE +0 -0
  103. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/UPSTREAM_VERSION +0 -0
  104. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/src/client/mod.rs +0 -0
  105. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/src/client/proxy/matcher.rs +0 -0
  106. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/src/client/proxy/mod.rs +0 -0
  107. {httpunk-0.3.0 → httpunk-0.4.1}/crates/vendor-hyper-util/src/lib.rs +0 -0
  108. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/__init__.py +0 -0
  109. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/_backend/__init__.py +0 -0
  110. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/asyncio.py +0 -0
  111. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/exceptions.py +0 -0
  112. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h1/__init__.py +0 -0
  113. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/__init__.py +0 -0
  114. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/share.py +0 -0
  115. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/h2/stream.py +0 -0
  116. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/http.py +0 -0
  117. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/py.typed +0 -0
  118. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/types.py +0 -0
  119. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/__init__.py +0 -0
  120. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/auto.py +0 -0
  121. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/client.py +0 -0
  122. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/graceful.py +0 -0
  123. {httpunk-0.3.0 → httpunk-0.4.1}/httpunk/util/proxy.py +0 -0
  124. {httpunk-0.3.0 → httpunk-0.4.1}/src/errors.rs +0 -0
  125. {httpunk-0.3.0 → httpunk-0.4.1}/src/h1/errors.rs +0 -0
  126. {httpunk-0.3.0 → httpunk-0.4.1}/src/h1/mod.rs +0 -0
  127. {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/errors.rs +0 -0
  128. {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/mod.rs +0 -0
  129. {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/reason.rs +0 -0
  130. {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/settings.rs +0 -0
  131. {httpunk-0.3.0 → httpunk-0.4.1}/src/h2/streams.rs +0 -0
  132. {httpunk-0.3.0 → httpunk-0.4.1}/src/proxy.rs +0 -0
  133. {httpunk-0.3.0 → httpunk-0.4.1}/tests/_client.py +0 -0
  134. {httpunk-0.3.0 → httpunk-0.4.1}/tests/conftest.py +0 -0
  135. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_asyncio_client_protocol.py +0 -0
  136. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_asyncio_protocol.py +0 -0
  137. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h1_decode.py +0 -0
  138. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_conn.py +0 -0
  139. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_errors.py +0 -0
  140. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_flow.py +0 -0
  141. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_ready.py +0 -0
  142. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_h2_state.py +0 -0
  143. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_headermap.py +0 -0
  144. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_connect.py +0 -0
  145. {httpunk-0.3.0 → httpunk-0.4.1}/tests/test_util_pool.py +0 -0
  146. {httpunk-0.3.0 → httpunk-0.4.1}/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.3.0"
121
+ version = "0.4.1"
122
122
  dependencies = [
123
123
  "bytes",
124
124
  "h2",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "httpunk"
3
- version = "0.3.0"
3
+ version = "0.4.1"
4
4
  description = "The Rust HTTP library for Python"
5
5
  authors = [
6
6
  "Giovanni Barillari <g@baro.dev>",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httpunk
3
- Version: 0.3.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.15 ; python_full_version >= '3.14' and extra == 'tonio'
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, ParsedHead,
62
- ParsedRequest, CONTINUE_RESPONSE, DEFAULT_MAX_BUFFER_SIZE, H2_PREFACE,
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
- pub method: String,
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: String,
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 request-body chunks into bytes,
222
- /// so hyper's body `Encoder` never crosses the crate boundary.
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
- /// Frame one body chunk (chunked size-prefix/CRLF, or raw for content-length).
249
- pub fn encode(&mut self, chunk: &[u8]) -> Vec<u8> {
250
- if chunk.is_empty() {
251
- return Vec::new();
252
- }
253
- let mut buf = self.0.encode(Bytes::copy_from_slice(chunk));
254
- let n = buf.remaining();
255
- buf.copy_to_bytes(n).to_vec()
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 `0\r\n\r\n`, or empty for a
259
- /// content-length body. `Err` if a declared Content-Length wasn't filled —
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<Vec<u8>, crate::Error> {
359
+ pub fn end(self) -> Result<BodyTail, crate::Error> {
264
360
  match self.0.end::<Bytes>() {
265
- Ok(Some(mut buf)) => {
266
- let n = buf.remaining();
267
- Ok(buf.copy_to_bytes(n).to_vec())
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(Vec::new()),
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: emits the terminating `0\r\n` +
275
- /// the trailer block + `\r\n`. Only fields declared via
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<Vec<u8>, crate::Error> {
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(mut buf) => {
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 bytes and return the body `BodyEncoder`.
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
- ) -> Result<(Vec<u8>, BodyEncoder), crate::Error> {
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
- let mut dst = Vec::new();
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, &mut dst)?;
354
- Ok((dst, BodyEncoder(encoder)))
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.to_string(),
427
- target: uri.to_string(),
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 bytes and return the body `BodyEncoder` (server
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
- ) -> Result<(Vec<u8>, BodyEncoder), crate::Error> {
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
- let mut dst = Vec::new();
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, &mut dst)?;
503
- Ok((dst, BodyEncoder(encoder)))
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() -> Vec<u8> {
511
- crate::common::date::update();
512
- let mut out = Vec::with_capacity(crate::common::date::DATE_VALUE_LENGTH);
513
- crate::common::date::extend(&mut out);
514
- out
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) -> Vec<u8> {
602
- self.read.buf.split().to_vec()
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,