Skip to main content

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