Skip to main content

vibeio_http/h2/
mod.rs

1mod date;
2mod options;
3mod send;
4mod upgrade;
5
6pub use options::*;
7use pin_project_lite::pin_project;
8use tokio_util::sync::CancellationToken;
9
10use std::{
11    future::Future,
12    pin::Pin,
13    sync::{atomic::AtomicBool, Arc},
14    task::{Context, Poll},
15};
16
17use bytes::Bytes;
18use http::{Request, Response};
19use http_body::{Body, Frame};
20
21use crate::{
22    early_hints::EarlyHintsReceiver,
23    h2::{
24        date::DateCache,
25        send::{PipeToSendStream, SendBuf},
26    },
27    EarlyHints, HttpProtocol, Incoming, Upgrade, Upgraded,
28};
29
30static HTTP2_INVALID_HEADERS: [http::header::HeaderName; 5] = [
31    http::header::HeaderName::from_static("keep-alive"),
32    http::header::HeaderName::from_static("proxy-connection"),
33    http::header::CONNECTION,
34    http::header::TRANSFER_ENCODING,
35    http::header::UPGRADE,
36];
37
38pub(crate) struct H2Body {
39    recv: h2::RecvStream,
40    data_done: bool,
41    send_continue_body: Option<Arc<AtomicBool>>,
42}
43
44impl H2Body {
45    #[inline]
46    fn new(recv: h2::RecvStream, send_continue_body: Option<Arc<AtomicBool>>) -> Self {
47        Self {
48            recv,
49            data_done: false,
50            send_continue_body,
51        }
52    }
53}
54
55impl Body for H2Body {
56    type Data = Bytes;
57    type Error = std::io::Error;
58
59    #[inline]
60    fn poll_frame(
61        mut self: Pin<&mut Self>,
62        cx: &mut Context<'_>,
63    ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
64        if !self.data_done {
65            match self.recv.poll_data(cx) {
66                Poll::Ready(Some(Ok(data))) => {
67                    let _ = self.recv.flow_control().release_capacity(data.len());
68                    return Poll::Ready(Some(Ok(Frame::data(data))));
69                }
70                Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(h2_error_to_io(err)))),
71                Poll::Ready(None) => self.data_done = true,
72                Poll::Pending => {
73                    if let Some(scb) = self.send_continue_body.as_ref() {
74                        scb.store(true, std::sync::atomic::Ordering::Relaxed);
75                    }
76                    return Poll::Pending;
77                }
78            }
79        }
80
81        match self.recv.poll_trailers(cx) {
82            Poll::Ready(Ok(Some(trailers))) => Poll::Ready(Some(Ok(Frame::trailers(trailers)))),
83            Poll::Ready(Ok(None)) => Poll::Ready(None),
84            Poll::Ready(Err(err)) => Poll::Ready(Some(Err(h2_error_to_io(err)))),
85            Poll::Pending => {
86                if let Some(scb) = self.send_continue_body.as_ref() {
87                    scb.store(true, std::sync::atomic::Ordering::Relaxed);
88                }
89                Poll::Pending
90            }
91        }
92    }
93}
94
95#[inline]
96pub(super) fn h2_error_to_io(error: h2::Error) -> std::io::Error {
97    if error.is_io() {
98        error.into_io().unwrap_or(std::io::Error::other("io error"))
99    } else {
100        std::io::Error::other(error)
101    }
102}
103
104#[inline]
105pub(super) fn h2_reason_to_io(reason: h2::Reason) -> std::io::Error {
106    std::io::Error::other(h2::Error::from(reason))
107}
108
109#[inline]
110fn sanitize_response<ResB>(
111    response: &mut Response<ResB>,
112    send_date_header: bool,
113    date_cache: &DateCache,
114) where
115    ResB: Body<Data = bytes::Bytes>,
116{
117    let response_headers = response.headers_mut();
118    if send_date_header {
119        if let Some(http_date) = date_cache.get_date_header_value() {
120            response_headers
121                .entry(http::header::DATE)
122                .or_insert(http_date);
123        }
124    }
125    for header in &HTTP2_INVALID_HEADERS {
126        if let http::header::Entry::Occupied(entry) = response_headers.entry(header) {
127            entry.remove();
128        }
129    }
130    if response_headers
131        .get(http::header::TE)
132        .is_some_and(|v| v != "trailers")
133    {
134        response_headers.remove(http::header::TE);
135    }
136}
137
138struct PendingUpgrade {
139    tx: oneshot::Sender<Upgraded>,
140    upgraded: std::sync::Arc<std::sync::atomic::AtomicBool>,
141    recv_stream: h2::RecvStream,
142}
143
144pin_project! {
145    struct H2Stream<Fut, ResB>
146    where
147        Fut: Future,
148        ResB: Body<Data = bytes::Bytes>,
149    {
150        stream: h2::server::SendResponse<SendBuf<ResB::Data>>,
151        #[pin]
152        state: H2StreamState<Fut, ResB>,
153    }
154}
155
156pin_project! {
157    #[project = H2StreamStateProj]
158    enum H2StreamState<Fut, ResB>
159    where
160        Fut: Future,
161        ResB: Body<Data = bytes::Bytes>,
162    {
163        Service {
164            #[pin]
165            response_fut: Fut,
166            early_hints_rx: EarlyHintsReceiver,
167            date_cache: DateCache,
168            send_date_header: bool,
169            upgrade: Option<PendingUpgrade>,
170            send_continue: bool,
171            early_hints_open: bool,
172            send_continue_body: Option<Arc<AtomicBool>>,
173            continue_sent: bool
174        },
175        Body {
176            #[pin]
177            pipe: PipeToSendStream<ResB>,
178        },
179    }
180}
181
182impl<Fut, ResB> H2Stream<Fut, ResB>
183where
184    Fut: Future,
185    ResB: Body<Data = bytes::Bytes>,
186{
187    #[allow(clippy::too_many_arguments)]
188    #[inline]
189    const fn new(
190        stream: h2::server::SendResponse<SendBuf<ResB::Data>>,
191        response_fut: Fut,
192        early_hints_rx: EarlyHintsReceiver,
193        date_cache: DateCache,
194        send_date_header: bool,
195        upgrade: Option<PendingUpgrade>,
196        send_continue: bool,
197        send_continue_body: Option<Arc<AtomicBool>>,
198    ) -> Self {
199        Self {
200            stream,
201            state: H2StreamState::Service {
202                response_fut,
203                early_hints_rx,
204                date_cache,
205                send_date_header,
206                upgrade,
207                send_continue,
208                early_hints_open: true,
209                send_continue_body,
210                continue_sent: false,
211            },
212        }
213    }
214}
215
216impl<Fut, ResB, ResBE, ResE> Future for H2Stream<Fut, ResB>
217where
218    Fut: Future<Output = Result<Response<ResB>, ResE>>,
219    ResB: Body<Data = bytes::Bytes, Error = ResBE>,
220    ResE: std::error::Error,
221    ResBE: std::error::Error,
222{
223    type Output = ();
224
225    #[inline]
226    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
227        let mut this = self.project();
228
229        loop {
230            match this.state.as_mut().project() {
231                H2StreamStateProj::Service { .. } => {
232                    match Self::poll_service(this.stream, this.state.as_mut(), cx) {
233                        ServicePoll::Done => return Poll::Ready(()),
234                        ServicePoll::Pending => return Poll::Pending,
235                        ServicePoll::Body(pipe) => {
236                            this.state.set(H2StreamState::Body { pipe });
237                            continue;
238                        }
239                    }
240                }
241                H2StreamStateProj::Body { pipe } => return pipe.poll(cx).map(|_| ()),
242            }
243        }
244    }
245}
246
247/// The outcome of polling the `H2Stream` service state.
248enum ServicePoll<ResB>
249where
250    ResB: Body<Data = bytes::Bytes>,
251{
252    /// The stream has finished.
253    Done,
254    /// The stream is not ready to make progress yet.
255    Pending,
256    /// The response body is ready to be piped to the send stream.
257    Body(PipeToSendStream<ResB>),
258}
259
260impl<Fut, ResB, ResBE, ResE> H2Stream<Fut, ResB>
261where
262    Fut: Future<Output = Result<Response<ResB>, ResE>>,
263    ResB: Body<Data = bytes::Bytes, Error = ResBE>,
264    ResE: std::error::Error,
265    ResBE: std::error::Error,
266{
267    /// Polls the `H2StreamState::Service` state until the response future
268    /// resolves, interim responses are sent, or the stream would block.
269    #[inline]
270    fn poll_service(
271        stream: &mut h2::server::SendResponse<SendBuf<ResB::Data>>,
272        state: Pin<&mut H2StreamState<Fut, ResB>>,
273        cx: &mut Context<'_>,
274    ) -> ServicePoll<ResB> {
275        let mut state = state;
276        loop {
277            let H2StreamStateProj::Service {
278                response_fut,
279                early_hints_rx,
280                date_cache,
281                send_date_header,
282                upgrade,
283                send_continue,
284                early_hints_open,
285                send_continue_body,
286                continue_sent,
287            } = state.as_mut().project()
288            else {
289                unreachable!("poll_service called for non-Service state");
290            };
291
292            if let Poll::Ready(response_result) = response_fut.poll(cx) {
293                let Ok(mut response) = response_result else {
294                    return ServicePoll::Done;
295                };
296
297                sanitize_response(&mut response, *send_date_header, date_cache);
298
299                let response_is_end_stream = response.body().is_end_stream();
300                if !response_is_end_stream {
301                    if let Some(content_length) = response.body().size_hint().exact() {
302                        if !response
303                            .headers()
304                            .contains_key(http::header::CONTENT_LENGTH)
305                        {
306                            response
307                                .headers_mut()
308                                .insert(http::header::CONTENT_LENGTH, content_length.into());
309                        }
310                    }
311                }
312
313                if *send_continue && !*continue_sent {
314                    if !response.status().is_client_error() && !response.status().is_server_error()
315                    {
316                        let mut response = Response::new(());
317                        *response.status_mut() = http::StatusCode::CONTINUE;
318                        let _ = stream.send_informational(response).map_err(h2_error_to_io);
319                    }
320                    *continue_sent = true;
321                }
322
323                let (response_parts, response_body) = response.into_parts();
324                let Ok(send) = stream.send_response(
325                    Response::from_parts(response_parts, ()),
326                    response_is_end_stream && upgrade.is_none(),
327                ) else {
328                    return ServicePoll::Done;
329                };
330
331                if let Some(PendingUpgrade {
332                    tx,
333                    upgraded,
334                    recv_stream,
335                }) = upgrade.take()
336                {
337                    if upgraded.load(std::sync::atomic::Ordering::Relaxed) {
338                        let (upgraded, task) = self::upgrade::pair(send, recv_stream);
339                        let _ = tx.send(Upgraded::new(upgraded, None));
340                        vibeio::spawn(task);
341                        return ServicePoll::Done;
342                    }
343                }
344
345                if response_is_end_stream {
346                    return ServicePoll::Done;
347                }
348
349                return ServicePoll::Body(PipeToSendStream::new(send, response_body));
350            }
351
352            match stream.poll_reset(cx) {
353                Poll::Ready(Ok(_)) | Poll::Ready(Err(_)) => return ServicePoll::Done,
354                Poll::Pending => {}
355            }
356
357            if *send_continue
358                && !*continue_sent
359                && send_continue_body
360                    .as_ref()
361                    .is_some_and(|scb| scb.load(std::sync::atomic::Ordering::Relaxed))
362            {
363                let mut response = Response::new(());
364                *response.status_mut() = http::StatusCode::CONTINUE;
365                let _ = stream.send_informational(response).map_err(h2_error_to_io);
366                *continue_sent = true;
367            }
368
369            if *early_hints_open {
370                match early_hints_rx.poll_recv(cx) {
371                    Poll::Ready(Some((headers, sender))) => {
372                        let mut response = Response::new(());
373                        *response.status_mut() = http::StatusCode::EARLY_HINTS;
374                        *response.headers_mut() = headers;
375                        sender
376                            .into_inner()
377                            .send(stream.send_informational(response).map_err(h2_error_to_io))
378                            .ok();
379                        continue;
380                    }
381                    Poll::Ready(None) => {
382                        *early_hints_open = false;
383                        continue;
384                    }
385                    Poll::Pending => {}
386                }
387            }
388
389            return ServicePoll::Pending;
390        }
391    }
392}
393
394/// An HTTP/2 connection handler.
395///
396/// `Http2` wraps an async I/O stream (`Io`) and drives the HTTP/2 server
397/// connection using the [`h2`] crate. It supports:
398///
399/// - Concurrent request stream handling
400/// - Streaming request/response bodies and trailers
401/// - Automatic `100 Continue` and `103 Early Hints` interim responses
402/// - Per-connection `Date` header caching
403/// - Graceful shutdown via a [`CancellationToken`]
404///
405/// # Construction
406///
407/// ```rust,ignore
408/// let http2 = Http2::new(tcp_stream, Http2Options::default());
409/// ```
410///
411/// # Serving requests
412///
413/// Use the [`HttpProtocol`] trait methods ([`handle`](HttpProtocol::handle) /
414/// [`handle_with_error_fn`](HttpProtocol::handle_with_error_fn)) to drive the
415/// connection to completion.
416pub struct Http2<Io> {
417    io_to_handshake: Option<Io>,
418    date_header_value_cached: DateCache,
419    options: Http2Options,
420    cancel_token: Option<CancellationToken>,
421}
422
423impl<Io> Http2<Io>
424where
425    Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static,
426{
427    /// Creates a new `Http2` connection handler wrapping the given I/O stream.
428    ///
429    /// The `options` value controls HTTP/2 protocol configuration, handshake
430    /// and accept timeouts, and optional behaviour such as automatic
431    /// `100 Continue` responses; see [`Http2Options`] for details.
432    ///
433    /// # Example
434    ///
435    /// ```rust,ignore
436    /// let http2 = Http2::new(tcp_stream, Http2Options::default());
437    /// ```
438    #[inline]
439    pub fn new(io: Io, options: Http2Options) -> Self {
440        Self {
441            io_to_handshake: Some(io),
442            date_header_value_cached: DateCache::default(),
443            options,
444            cancel_token: None,
445        }
446    }
447
448    /// Attaches a [`CancellationToken`] for graceful shutdown.
449    ///
450    /// When the token is cancelled, the handler sends HTTP/2 graceful shutdown
451    /// signals (GOAWAY), stops accepting new streams, and exits cleanly.
452    #[inline]
453    pub fn graceful_shutdown_token(mut self, token: CancellationToken) -> Self {
454        self.cancel_token = Some(token);
455        self
456    }
457}
458
459impl<Io> HttpProtocol for Http2<Io>
460where
461    Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static,
462{
463    #[allow(clippy::manual_async_fn)]
464    #[inline]
465    fn handle<F, Fut, ResB, ResBE, ResE>(
466        mut self,
467        request_fn: F,
468    ) -> impl std::future::Future<Output = Result<(), std::io::Error>>
469    where
470        F: Fn(Request<super::Incoming>) -> Fut + 'static,
471        Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
472        ResB: http_body::Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
473        ResE: std::error::Error,
474        ResBE: std::error::Error,
475    {
476        async move {
477            let handshake_fut = self.options.h2.handshake(
478                self.io_to_handshake
479                    .take()
480                    .ok_or_else(|| std::io::Error::other("no io to handshake"))?,
481            );
482            let mut h2 = (if let Some(timeout) = self.options.handshake_timeout {
483                vibeio::time::timeout(timeout, handshake_fut).await
484            } else {
485                Ok(handshake_fut.await)
486            })
487            .map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut, "handshake timeout"))?
488            .map_err(|e| {
489                if e.is_io() {
490                    e.into_io().unwrap_or(std::io::Error::other("io error"))
491                } else {
492                    std::io::Error::other(e)
493                }
494            })?;
495
496            while let Some(request) = {
497                let res = {
498                    let accept_fut_orig = h2.accept();
499                    let accept_fut_orig_pin = std::pin::pin!(accept_fut_orig);
500                    let cancel_token = self.cancel_token.clone();
501                    let cancel_fut = async move {
502                        if let Some(token) = cancel_token {
503                            token.cancelled().await
504                        } else {
505                            futures_util::future::pending().await
506                        }
507                    };
508                    let cancel_fut_pin = std::pin::pin!(cancel_fut);
509                    let accept_fut =
510                        futures_util::future::select(cancel_fut_pin, accept_fut_orig_pin);
511
512                    match if let Some(timeout) = self.options.accept_timeout {
513                        vibeio::time::timeout(timeout, accept_fut).await
514                    } else {
515                        Ok(accept_fut.await)
516                    } {
517                        Ok(futures_util::future::Either::Right((request, _))) => {
518                            (Some(request), false)
519                        }
520                        Ok(futures_util::future::Either::Left((_, _))) => {
521                            // Canceled
522                            (None, true)
523                        }
524                        Err(_) => {
525                            // Timeout
526                            (None, false)
527                        }
528                    }
529                };
530                match res {
531                    (Some(request), _) => request,
532                    (None, graceful) => {
533                        h2.graceful_shutdown();
534                        let _ = h2.accept().await;
535                        if graceful || !h2.has_streams() {
536                            return Ok(());
537                        }
538                        return Err(std::io::Error::new(
539                            std::io::ErrorKind::TimedOut,
540                            "accept timeout",
541                        ));
542                    }
543                }
544            } {
545                let (request, stream) = match request {
546                    Ok(d) => d,
547                    Err(e) if e.is_go_away() => {
548                        continue;
549                    }
550                    Err(e) if e.is_io() => {
551                        let e_io = e.into_io().unwrap_or(std::io::Error::other("io error"));
552                        if h2.has_streams()
553                            && matches!(
554                                e_io.kind(),
555                                std::io::ErrorKind::BrokenPipe
556                                    | std::io::ErrorKind::ConnectionReset
557                                    | std::io::ErrorKind::ConnectionAborted
558                                    | std::io::ErrorKind::UnexpectedEof
559                            )
560                        {
561                            // HTTP/2 abruptly closed when idle
562                            return Ok(());
563                        }
564                        return Err(e_io);
565                    }
566                    Err(e) => {
567                        return Err(std::io::Error::other(e));
568                    }
569                };
570
571                // 100 Continue
572                let is_100_continue = self.options.send_continue_response
573                    && request
574                        .headers()
575                        .get(http::header::EXPECT)
576                        .and_then(|v| v.to_str().ok())
577                        .is_some_and(|v| v.eq_ignore_ascii_case("100-continue"));
578
579                let date_cache = self.date_header_value_cached.clone();
580                let send_continue_body = is_100_continue.then(|| Arc::new(AtomicBool::new(false)));
581                let (request_parts, recv_stream) = request.into_parts();
582                let (request_body, upgrade) = if request_parts.method == http::Method::CONNECT {
583                    (Incoming::Empty, Some(recv_stream))
584                } else {
585                    (
586                        Incoming::H2(H2Body::new(recv_stream, send_continue_body.clone())),
587                        None,
588                    )
589                };
590                let mut request = Request::from_parts(request_parts, request_body);
591
592                // Install early hints
593                let (early_hints, early_hints_rx) = EarlyHints::new_lazy();
594                request.extensions_mut().insert(early_hints);
595
596                // Install HTTP upgrade
597                let upgrade = if let Some(recv_stream) = upgrade {
598                    let (upgrade_tx, upgrade_rx) = oneshot::async_channel();
599                    let upgrade = Upgrade::new(upgrade_rx);
600                    let upgraded = upgrade.upgraded.clone();
601                    request.extensions_mut().insert(upgrade);
602                    Some(PendingUpgrade {
603                        tx: upgrade_tx,
604                        upgraded,
605                        recv_stream,
606                    })
607                } else {
608                    None
609                };
610
611                vibeio::spawn(H2Stream::new(
612                    stream,
613                    request_fn(request),
614                    early_hints_rx,
615                    date_cache,
616                    self.options.send_date_header,
617                    upgrade,
618                    is_100_continue,
619                    send_continue_body,
620                ));
621            }
622
623            Ok(())
624        }
625    }
626}