vibeio_http/h2/connection/mod.rs
1//! HTTP/2 connection (RFC 9113 Sections 3.5, 5.1, 6.5, 8.1).
2//!
3//! Drives one HTTP/2 connection over an async I/O stream: reads the
4//! client's 24-octet preface (with a timeout), sends the server
5//! SETTINGS, maintains the peer's SETTINGS state (applying
6//! `SETTINGS_HEADER_TABLE_SIZE` to the HPACK encoder and
7//! `SETTINGS_MAX_FRAME_SIZE` to the outgoing frame writer), answers
8//! SETTINGS frames with ACK, echoes PING, and reports protocol
9//! violations with GOAWAY before closing.
10//!
11//! Frame parsing and validation happen in [`super::codec`]; this module
12//! adds the connection- and stream-level wiring: per-stream state,
13//! request/response header parsing, request dispatch to the handler,
14//! response framing, and the connection-level error handling that
15//! h2spec's `http2/4.3`, `http2/5.1`, `http2/6.1`, `http2/6.2`,
16//! `http2/6.4` and `http2/8.1` groups cover.
17
18use std::{
19 future::Future,
20 pin::Pin,
21 sync::{atomic::AtomicBool, Arc},
22 task::{Context, Poll},
23 time::Duration,
24};
25
26use bytes::Bytes;
27use futures_util::{pin_mut, FutureExt};
28use http::{Request, Response, StatusCode};
29use http_body::Body;
30use rustc_hash::{FxHashMap, FxHashSet};
31use tokio_util::sync::CancellationToken;
32
33use super::codec::{
34 Frame, FrameDecoder, FrameWriter, Setting, CLIENT_PREFACE, DEFAULT_INITIAL_WINDOW_SIZE,
35 DEFAULT_MAX_FRAME_SIZE, MAX_FRAME_SIZE_LIMIT,
36};
37use super::date::DateCache;
38use super::error::Reason;
39use super::hpack::{Decoder as HpackDecoder, Encoder, Header as HpackHeader, HpackError};
40use super::sanitize_response;
41use super::stream::{
42 BodyMsg, H2Body, MalformedRequest, ParsedRequest, StreamDriver, StreamEntry, StreamMsg,
43};
44use crate::early_hints::EarlyHints;
45use crate::Incoming;
46
47/// Per-connection behavior options.
48#[derive(Debug, Clone, Copy)]
49pub struct ConnectionOptions {
50 /// Answer requests with `100 Continue` when they carry
51 /// `expect: 100-continue`.
52 pub send_continue_response: bool,
53 /// Add a `Date` header to responses.
54 pub send_date_header: bool,
55 /// `SETTINGS_MAX_CONCURRENT_STREAMS` announced to the peer.
56 pub max_concurrent_streams: u32,
57 /// `SETTINGS_INITIAL_WINDOW_SIZE`: per-stream DATA credit we start with.
58 pub initial_stream_window_size: u32,
59 /// Connection-level DATA credit we start with (RFC 9113 Section 6.9.1).
60 pub initial_connection_window_size: u32,
61 /// Largest frame (payload) we send or receive (`SETTINGS_MAX_FRAME_SIZE`).
62 pub max_frame_size: u32,
63 /// Largest decoded header list we accept (`SETTINGS_MAX_HEADER_LIST_SIZE`).
64 pub max_header_list_size: u32,
65 /// Whether to enable Extended CONNECT
66 pub enable_connect_protocol: bool,
67 /// Close the connection after this long with no frame from the peer
68 /// (RFC 9113 Section 10.5). `None` disables the idle timeout.
69 pub idle_timeout: Option<Duration>,
70 /// Maximum number of RST_STREAM frames we send in response to
71 /// protocol errors made by the peer over the connection's lifetime.
72 /// `None` disables the limit; when it is exceeded the connection
73 /// closes with GOAWAY `ENHANCE_YOUR_CALM` (RFC 9113 Section 10.5.2).
74 pub max_local_error_reset_streams: Option<usize>,
75 /// Maximum number of streams the peer reset before we accepted them.
76 /// `None` disables the limit; when it is exceeded the connection
77 /// closes with GOAWAY `ENHANCE_YOUR_CALM` (RFC 9113 Section 10.5.2).
78 pub max_pending_accept_reset_streams: Option<usize>,
79 /// Maximum number of frames that may compose a single, not-yet-finalized
80 /// header field block (HEADERS/CONTINUATION). Beyond this a stream is
81 /// reset as a CONTINUATION flood (CVE-2024-27919).
82 pub max_continuation_frames: usize,
83}
84
85impl Default for ConnectionOptions {
86 #[inline]
87 fn default() -> Self {
88 ConnectionOptions {
89 send_continue_response: false,
90 send_date_header: true,
91 max_concurrent_streams: 100,
92 initial_stream_window_size: DEFAULT_INITIAL_WINDOW_SIZE,
93 initial_connection_window_size: DEFAULT_INITIAL_WINDOW_SIZE,
94 max_frame_size: DEFAULT_MAX_FRAME_SIZE as u32,
95 max_header_list_size: u32::MAX,
96 enable_connect_protocol: false,
97 idle_timeout: None,
98 max_local_error_reset_streams: Some(1024),
99 max_pending_accept_reset_streams: Some(20),
100 max_continuation_frames: 16,
101 }
102 }
103}
104
105/// The peer's current SETTINGS values. Defaults are the RFC 9113
106/// Section 6.5.2 initial values; they change as non-ACK SETTINGS frames
107/// arrive.
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109pub struct PeerSettings {
110 pub(crate) header_table_size: u32,
111 pub(crate) enable_push: u32,
112 pub(crate) initial_window_size: u32,
113 pub(crate) max_frame_size: usize,
114 /// Kept for the field block decoder's bomb protection (C2).
115 #[allow(dead_code)]
116 pub(crate) max_header_list_size: u32,
117}
118
119impl Default for PeerSettings {
120 #[inline]
121 fn default() -> Self {
122 PeerSettings {
123 header_table_size: 4096,
124 enable_push: 1,
125 initial_window_size: DEFAULT_INITIAL_WINDOW_SIZE,
126 max_frame_size: DEFAULT_MAX_FRAME_SIZE,
127 max_header_list_size: u32::MAX,
128 }
129 }
130}
131
132/// An HTTP/2 server connection.
133///
134/// `Io` must be a raw transport: the preface is read byte-exact, so
135/// any buffering layer between the socket and this type breaks the
136/// initial read.
137pub struct Connection<Io> {
138 io: Io,
139 decoder: FrameDecoder,
140 writer: FrameWriter,
141 out: Vec<u8>,
142 /// Encoder for the field blocks this connection sends; its table
143 /// size follows the peer's `SETTINGS_HEADER_TABLE_SIZE`.
144 encoder: Encoder,
145 /// Decoder for the peer's field blocks; sees every header block on
146 /// this connection (RFC 9113 Section 4.3).
147 request_decoder: HpackDecoder,
148 /// The peer's settings, updated by non-ACK SETTINGS frames.
149 peer: PeerSettings,
150 /// The settings this connection announced (public RFC defaults
151 /// plus `SETTINGS_MAX_CONCURRENT_STREAMS`).
152 #[allow(dead_code)]
153 local: PeerSettings,
154 /// Bounds the wait for the client's 24-octet preface. A peer that
155 /// is too slow is disconnected without a GOAWAY.
156 preface_timeout: Option<Duration>,
157 /// Active streams, keyed by stream id (RFC 9113 Section 5.1).
158 streams: FxHashMap<u32, StreamEntry>,
159 /// Connection-level send window for DATA payloads (RFC 9113
160 /// Section 6.9.1); initial 65,535 octets.
161 conn_window: i64,
162 /// Stream ids whose streams have ended (closed state per RFC 9113
163 /// Section 5.1); used to tell closed-stream frames apart from
164 /// idle-stream frames.
165 closed_streams: FxHashSet<u32>,
166 /// Behavior options for this connection (used by [`Connection::handle`]).
167 opts: ConnectionOptions,
168 /// RST_STREAM frames this endpoint has sent in response to the
169 /// peer's protocol errors (bounded by `opts.max_local_error_reset_streams`).
170 local_error_resets: usize,
171 /// Streams the peer reset before this endpoint accepted them
172 /// (bounded by `opts.max_pending_accept_reset_streams`).
173 pending_accept_resets: usize,
174 /// Wakes the drive loop when a stream task fills its outbound
175 /// channel; the loop drains channels between reads.
176 wake_tx: Option<kanal::AsyncSender<()>>,
177 /// Stream ids whose FIELD_BLOCK completed (END_HEADERS seen) and
178 /// awaits finalization; drained one per frame by
179 /// [`Connection::process_frames`].
180 complete_blocks: Vec<u32>,
181 /// Maximum number of frames a single header field block may span before
182 /// it is treated as a CONTINUATION flood and reset (CVE-2024-27919).
183 max_continuation_frames: usize,
184 /// Scratch buffer reused by [`Connection::drain_pending_data`] to
185 /// snapshot stream ids before pumping (avoids a per-call
186 /// allocation).
187 drain_ids: Vec<u32>,
188 /// Highest stream id opened by the peer (RFC 9113 Section 5.1.1).
189 highest_stream_id: u32,
190 /// A connection error is pending; the loop stops after flushing.
191 closing: bool,
192 /// Graceful shutdown is in progress: the first GOAWAY is sent and we
193 /// only drain already-open streams until they finish or the drain
194 /// window elapses (RFC 9113 Section 6.8).
195 graceful: bool,
196 /// Last stream id advertised in the graceful-shutdown GOAWAY; peers
197 /// must not open streams beyond it.
198 graceful_last_stream: u32,
199 /// Optional token that triggers graceful shutdown when cancelled.
200 shutdown: Option<CancellationToken>,
201 /// Shared `Date` header value, refreshed periodically for the
202 /// responses the stream tasks emit.
203 date_cache: Arc<DateCache>,
204}
205
206impl<Io> Connection<Io>
207where
208 Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
209{
210 /// Creates a connection over `io`.
211 #[inline]
212 pub fn new(io: Io, preface_timeout: Option<Duration>) -> Connection<Io> {
213 Connection {
214 io,
215 decoder: FrameDecoder::new(DEFAULT_MAX_FRAME_SIZE),
216 writer: FrameWriter::new(DEFAULT_MAX_FRAME_SIZE),
217 out: Vec::new(),
218 encoder: Encoder::new(4096),
219 request_decoder: HpackDecoder::new(4096),
220 peer: PeerSettings::default(),
221 local: PeerSettings::default(),
222 preface_timeout,
223 streams: FxHashMap::default(),
224 conn_window: DEFAULT_INITIAL_WINDOW_SIZE as i64,
225 closed_streams: FxHashSet::default(),
226 opts: ConnectionOptions::default(),
227 local_error_resets: 0,
228 pending_accept_resets: 0,
229 wake_tx: None,
230 complete_blocks: Vec::new(),
231 max_continuation_frames: 16,
232 drain_ids: Vec::new(),
233 highest_stream_id: 0,
234 closing: false,
235 graceful: false,
236 graceful_last_stream: 0,
237 shutdown: None,
238 date_cache: Arc::new(DateCache::new()),
239 }
240 }
241
242 /// Arms a [`CancellationToken`] that triggers a graceful shutdown
243 /// when cancelled: the connection sends GOAWAY, stops opening new
244 /// streams, drains in-flight responses, then closes (RFC 9113
245 /// Section 6.8).
246 #[inline]
247 pub fn with_shutdown(mut self, token: CancellationToken) -> Self {
248 self.shutdown = Some(token);
249 self
250 }
251
252 /// Drives a connection that never serves requests: the preface
253 /// handshake, SETTINGS/PING maintenance and error handling, but no
254 /// request dispatch (any request stream is refused).
255 ///
256 /// Equivalent to [`Connection::handle`] with a handler that never
257 /// completes; kept for tests and for callers that only want the
258 /// connection-level behavior.
259 #[inline]
260 pub async fn drive(self) -> std::io::Result<()> {
261 self.handle(
262 Arc::new(|_| std::future::pending::<Result<Response<Incoming>, std::io::Error>>()),
263 ConnectionOptions::default(),
264 )
265 .await
266 }
267
268 /// Serves requests to completion: peer EOF, GOAWAY received,
269 /// preface timeout, or an unrecoverable protocol error.
270 ///
271 /// Each decoded request starts a stream task running `request_fn`
272 /// (a clone-free sequential borrow: the loop owns the closure for
273 /// the connection's lifetime). Responses are framed onto the wire
274 /// by this task as the stream tasks emit them.
275 #[inline]
276 pub async fn handle<F, Fut, ResB, ResBE, ResE>(
277 mut self,
278 request_fn: Arc<F>,
279 options: ConnectionOptions,
280 ) -> std::io::Result<()>
281 where
282 F: Fn(Request<Incoming>) -> Fut + 'static,
283 Fut: Future<Output = Result<Response<ResB>, ResE>> + 'static,
284 ResB: Body<Data = Bytes, Error = ResBE> + Unpin + 'static,
285 ResBE: std::error::Error + 'static,
286 ResE: std::error::Error + 'static,
287 {
288 self.opts = options;
289 // Apply the negotiated native settings before the handshake.
290 self.request_decoder
291 .set_max_header_list_size(self.opts.max_header_list_size as usize);
292 self.decoder
293 .set_max_frame_size(self.opts.max_frame_size as usize);
294 self.writer.max_frame_size = self.opts.max_frame_size as usize;
295 self.peer.max_frame_size = self.opts.max_frame_size as usize;
296 self.conn_window = self.opts.initial_connection_window_size as i64;
297 // Resolved CONTINUATION-flood limit (CVE-2024-27919); `Http2Options`
298 // already applies the safe default when none was configured.
299 self.max_continuation_frames = options.max_continuation_frames;
300 match self.read_preface().await? {
301 None => return Ok(()), // preface timeout: close quietly
302 Some(false) => {
303 // Invalid preface: connection error PROTOCOL_ERROR
304 // (RFC 9113 Section 3.5), then close.
305 self.goaway(Reason::ProtocolError, b"invalid connection preface");
306 self.flush().await?;
307 return Ok(());
308 }
309 Some(true) => {}
310 }
311
312 // Our connection preface: SETTINGS announcing our flow-control
313 // windows, frame/header limits and concurrency (RFC 9113
314 // Sections 3.5 and 6.5.2).
315 self.writer.write_settings(
316 &mut self.out,
317 &[
318 Setting {
319 id: 0x03,
320 value: self.opts.max_concurrent_streams,
321 },
322 Setting {
323 id: 0x04,
324 value: self.opts.initial_stream_window_size,
325 },
326 Setting {
327 id: 0x05,
328 value: self.opts.max_frame_size,
329 },
330 Setting {
331 id: 0x08,
332 value: if self.opts.enable_connect_protocol {
333 1
334 } else {
335 0
336 },
337 },
338 ],
339 );
340 self.flush().await?;
341
342 let (wake_tx, wake_rx) = kanal::bounded_async(1);
343 self.wake_tx = Some(wake_tx);
344
345 let mut buf = [0u8; 8192];
346 let mut peer_goaway = false;
347 while !peer_goaway && !(self.graceful && self.streams.is_empty()) {
348 let wake_recv = wake_rx.recv().fuse();
349 let read = tokio::io::AsyncReadExt::read(&mut self.io, &mut buf).fuse();
350 // Graceful-shutdown signal: never fires without a token. The
351 // clone lives for the loop iteration so the boxed future can
352 // borrow it.
353 let shutdown_token = self.shutdown.clone();
354 let shutdown_fut: Pin<Box<dyn futures_util::future::FusedFuture<Output = ()> + Send>> =
355 match &shutdown_token {
356 Some(token) => Box::pin(token.cancelled().fuse()),
357 None => Box::pin(futures_util::future::pending().fuse()),
358 };
359 pin_mut!(wake_recv);
360 pin_mut!(read);
361 pin_mut!(shutdown_fut);
362 // Idle timeout (RFC 9113 Section 10.5): no frame received from the
363 // peer within `idle_timeout` => graceful shutdown. Recreated each
364 // iteration so it measures the gap since the last received frame.
365 let mut idle: Pin<Box<dyn futures_util::future::FusedFuture<Output = ()>>> =
366 match self.opts.idle_timeout {
367 Some(d) => Box::pin(
368 vibeio::time::timeout(d, futures_util::future::pending::<()>())
369 .map(|_| ())
370 .fuse(),
371 ),
372 None => Box::pin(futures_util::future::pending::<()>().fuse()),
373 };
374 futures_util::select! {
375 n = read => {
376 let n = match n {
377 Ok(n) => n,
378 Err(e) if self.streams.is_empty()
379 && matches!(
380 e.kind(),
381 std::io::ErrorKind::BrokenPipe
382 | std::io::ErrorKind::ConnectionReset
383 | std::io::ErrorKind::ConnectionAborted
384 | std::io::ErrorKind::UnexpectedEof
385 ) => {
386 // Connection abruptly closed while idle (no streams)...
387 return Ok(())
388 }
389 Err(e) => Err(e)?
390 };
391 if n == 0 {
392 break; // peer closed; nothing more to say
393 }
394 self.decoder.extend(&buf[..n]);
395 peer_goaway = self.process_frames(&request_fn).await?;
396 self.drain_outbound();
397 self.flush().await?;
398 }
399 _ = wake_recv => {
400 // A stream task parked on a full channel; drain it.
401 self.drain_outbound();
402 self.flush().await?;
403 }
404 _ = shutdown_fut => {
405 self.begin_graceful_shutdown();
406 self.flush().await?;
407 }
408 _ = idle => {
409 // The peer was silent for `idle_timeout`; close the
410 // connection gracefully (GOAWAY) and stop.
411 self.begin_graceful_shutdown();
412 self.flush().await?;
413 break;
414 }
415 }
416 }
417 if self.graceful {
418 self.finish_graceful_shutdown();
419 }
420 self.flush().await?;
421 Ok(())
422 }
423
424 /// Reads the 24-octet client preface.
425 ///
426 /// Returns `Ok(None)` on timeout (the connection closes quietly —
427 /// answering a peer that never spoke is meaningless), `Ok(Some(
428 /// true))` on a match, and `Ok(Some(false))` when the peer sent
429 /// something else (the caller answers with GOAWAY).
430 #[inline]
431 async fn read_preface(&mut self) -> std::io::Result<Option<bool>> {
432 let mut magic = [0u8; CLIENT_PREFACE.len()];
433 match self.preface_timeout {
434 Some(timeout) => {
435 match vibeio::time::timeout(
436 timeout,
437 tokio::io::AsyncReadExt::read_exact(&mut self.io, &mut magic),
438 )
439 .await
440 {
441 Ok(result) => {
442 result?;
443 }
444 Err(_elapsed) => return Ok(None),
445 }
446 }
447 None => {
448 tokio::io::AsyncReadExt::read_exact(&mut self.io, &mut magic).await?;
449 }
450 }
451 Ok(Some(magic == CLIENT_PREFACE))
452 }
453
454 /// Decodes and handles every frame currently buffered. Returns
455 /// `Ok(true)` when the connection should end (peer GOAWAY or an
456 /// error we answered with GOAWAY).
457 #[inline]
458 async fn process_frames<F, Fut, ResB, ResBE, ResE>(
459 &mut self,
460 request_fn: &Arc<F>,
461 ) -> std::io::Result<bool>
462 where
463 F: Fn(Request<Incoming>) -> Fut + 'static,
464 Fut: Future<Output = Result<Response<ResB>, ResE>> + 'static,
465 ResB: Body<Data = Bytes, Error = ResBE> + Unpin + 'static,
466 ResBE: std::error::Error + 'static,
467 ResE: std::error::Error + 'static,
468 {
469 loop {
470 let frame = match self.decoder.next_frame() {
471 Ok(Some(frame)) => frame,
472 Ok(None) => return Ok(false),
473 Err(error) => {
474 // Frame-level violation: GOAWAY with the code the
475 // codec determined (RFC 9113 Sections 6.10, 6.5.2),
476 // then close.
477 self.goaway(error.reason, b"frame error");
478 self.flush().await?;
479 return Ok(true);
480 }
481 };
482 match frame {
483 Frame::Settings {
484 ack: false,
485 settings,
486 } => {
487 self.apply_peer_settings(&settings);
488 self.writer.write_settings_ack(&mut self.out);
489 }
490 Frame::Settings { ack: true, .. } => {}
491 Frame::Ping {
492 ack: false,
493 payload,
494 } => {
495 self.writer.write_ping_ack(&mut self.out, &payload);
496 }
497 Frame::Ping { ack: true, .. } => {}
498 Frame::GoAway { .. } => return Ok(true),
499 Frame::Headers {
500 stream_id,
501 end_stream,
502 end_headers,
503 block,
504 ..
505 } => {
506 self.handle_headers_frame(stream_id, end_stream, end_headers, &block);
507 }
508 Frame::Continuation {
509 stream_id,
510 end_headers,
511 block,
512 } => {
513 self.handle_continuation(stream_id, end_headers, &block);
514 }
515 Frame::Data {
516 stream_id,
517 end_stream,
518 data,
519 } => {
520 self.handle_data_frame(stream_id, end_stream, data).await;
521 }
522 Frame::Reset {
523 stream_id,
524 error_code,
525 } => {
526 self.handle_reset_frame(stream_id, error_code);
527 }
528 Frame::Priority { .. } => {}
529 Frame::WindowUpdate {
530 stream_id,
531 increment,
532 } => self.handle_window_update(stream_id, increment),
533 Frame::PushPromise { .. } => {
534 self.goaway(Reason::ProtocolError, b"push promise to server");
535 }
536 Frame::Unknown { .. } => {}
537 }
538 // One HEADERS/CONTINUATION chain completes at most one
539 // field block per frame arrival; act on it while the
540 // handler closure is in scope.
541 if let Some(id) = self.take_complete_block() {
542 self.finalize_field_block(id, request_fn).await;
543 }
544 if self.closing {
545 self.flush().await?;
546 return Ok(true);
547 }
548 }
549 }
550}
551/// A boxed response body: the handler's body type is erased at the
552/// stream boundary so `Connection` stays monomorphic.
553struct ConnBody {
554 inner: Pin<Box<dyn Body<Data = Bytes, Error = std::io::Error>>>,
555}
556
557impl ConnBody {
558 #[inline]
559 fn new<ResB, ResBE>(body: ResB) -> Self
560 where
561 ResB: Body<Data = Bytes, Error = ResBE> + Unpin + 'static,
562 ResBE: std::error::Error + 'static,
563 {
564 ConnBody {
565 inner: Box::pin(BodyAdapter(Some(Box::pin(body)))),
566 }
567 }
568}
569
570impl Body for ConnBody {
571 type Data = Bytes;
572 type Error = std::io::Error;
573
574 #[inline]
575 fn poll_frame(
576 mut self: Pin<&mut Self>,
577 cx: &mut Context<'_>,
578 ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
579 self.inner.as_mut().poll_frame(cx)
580 }
581
582 #[inline]
583 fn size_hint(&self) -> http_body::SizeHint {
584 self.inner.size_hint()
585 }
586}
587
588/// Converts any displayable error into an [`io::Error`] without requiring
589/// `Send + Sync` (the native connection layer only needs the message, not the
590/// source; this keeps the public trait free of `Send`/`Sync` so it works on
591/// runtimes such as `vibeio` that do not demand them).
592#[inline]
593fn e2io<E: std::fmt::Display>(e: E) -> std::io::Error {
594 #[derive(Debug)]
595 struct Msg(String);
596 impl std::fmt::Display for Msg {
597 #[inline]
598 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
599 f.write_str(&self.0)
600 }
601 }
602 impl std::error::Error for Msg {}
603 std::io::Error::other(Msg(format!("{e}")))
604}
605
606/// Adapts an arbitrary body whose error is not `io::Error` into one
607/// that is (the stream layer only deals in `io::Error`).
608struct BodyAdapter<ResB>(Option<Pin<Box<ResB>>>);
609
610impl<ResB, ResBE> Body for BodyAdapter<ResB>
611where
612 ResB: Body<Data = Bytes, Error = ResBE>,
613 ResBE: std::error::Error + 'static,
614{
615 type Data = Bytes;
616 type Error = std::io::Error;
617
618 #[inline]
619 fn poll_frame(
620 self: Pin<&mut Self>,
621 cx: &mut Context<'_>,
622 ) -> Poll<Option<Result<http_body::Frame<Self::Data>, Self::Error>>> {
623 let this = self.get_mut();
624 let Some(inner) = this.0.as_mut() else {
625 return Poll::Ready(None);
626 };
627 match inner.as_mut().poll_frame(cx) {
628 Poll::Ready(Some(Ok(frame))) => Poll::Ready(Some(Ok(frame))),
629 Poll::Ready(Some(Err(error))) => Poll::Ready(Some(Err(e2io(error)))),
630 Poll::Ready(None) => {
631 this.0 = None;
632 Poll::Ready(None)
633 }
634 Poll::Pending => Poll::Pending,
635 }
636 }
637
638 #[inline]
639 fn size_hint(&self) -> http_body::SizeHint {
640 match &self.0 {
641 Some(body) => body.size_hint(),
642 None => http_body::SizeHint::default(),
643 }
644 }
645}
646
647#[derive(Clone, Copy, Debug, PartialEq, Eq)]
648enum StreamDataState {
649 Idle,
650 Closed,
651 Bad,
652 Gone,
653 Ok,
654}
655
656mod handlers;
657
658#[cfg(test)]
659mod tests;