Skip to main content

vibeio_http/h1/
mod.rs

1mod body;
2mod options;
3mod write;
4mod writebuf;
5mod zerocopy;
6
7pub(crate) use body::Http1Body;
8pub use options::*;
9pub use zerocopy::*;
10
11#[cfg(unix)]
12pub(crate) type RawHandle = std::os::fd::RawFd;
13#[cfg(windows)]
14pub(crate) type RawHandle = std::os::windows::io::RawHandle;
15
16use std::{mem::MaybeUninit, time::UNIX_EPOCH};
17
18use bytes::{Buf, Bytes, BytesMut};
19use http::{header, HeaderMap, HeaderValue, Request, Response, Version};
20use http_body::Body;
21use http_body_util::Empty;
22use memchr::memchr3_iter;
23use tokio::io::AsyncReadExt;
24use tokio_util::sync::CancellationToken;
25
26use crate::{h1::writebuf::WriteBuf, EarlyHints, HttpProtocol, Incoming, Upgrade, Upgraded};
27
28const HEX_DIGITS: &[u8; 16] = b"0123456789ABCDEF";
29const WRITE_BUF_BATCH_THRESHOLD: usize = 16384;
30
31/// An HTTP/1.x connection handler.
32///
33/// `Http1` wraps an async I/O stream (`Io`) and provides a complete
34/// HTTP/1.0 and HTTP/1.1 server implementation, including:
35///
36/// - Request head parsing (via [`httparse`])
37/// - Streaming request bodies (content-length and chunked transfer-encoding)
38/// - Chunked response encoding and trailer support
39/// - `100 Continue` and `103 Early Hints` interim responses
40/// - HTTP connection upgrades (e.g. WebSocket)
41/// - Optional zero-copy response sending on Linux or FreeBSD (see `Http1::zerocopy`)
42/// - Keep-alive connection reuse
43/// - Graceful shutdown via a [`CancellationToken`]
44///
45/// # Construction
46///
47/// ```rust,ignore
48/// let http1 = Http1::new(tcp_stream, Http1Options::default());
49/// ```
50///
51/// # Serving requests
52///
53/// Use the [`HttpProtocol`] trait methods ([`handle`](HttpProtocol::handle) /
54/// [`handle_with_error_fn`](HttpProtocol::handle_with_error_fn)) to drive the
55/// connection to completion:
56///
57/// ```rust,ignore
58/// http1.handle(|req| async move {
59///     Ok::<_, Infallible>(Response::new(Full::new(Bytes::from("Hello!"))))
60/// }).await?;
61/// ```
62pub struct Http1<Io> {
63    io: Io,
64    options: options::Http1Options,
65    cancel_token: Option<CancellationToken>,
66    parsed_headers: Box<[MaybeUninit<httparse::Header<'static>>]>,
67    date_header_value_cached: Option<(String, std::time::SystemTime)>,
68    cached_headers: Option<HeaderMap>,
69    read_buf: BytesMut,
70    response_head_buf: Vec<u8>,
71    write_buf: WriteBuf,
72    connection_idle: bool,
73}
74
75#[cfg(all(
76    any(target_os = "linux", target_os = "freebsd"),
77    feature = "h1-zerocopy"
78))]
79impl<Io> Http1<Io>
80where
81    for<'a> Io: tokio::io::AsyncRead
82        + tokio::io::AsyncWrite
83        + vibeio::io::AsInnerRawHandle<'a>
84        + Unpin
85        + 'static,
86{
87    /// Converts this `Http1` into an [`Http1Zerocopy`] that uses emulated
88    /// sendfile (Linux only) to send response bodies without copying data
89    /// through user space.
90    ///
91    /// The response body must have a `ZerocopyResponse` extension installed
92    /// (via [`install_zerocopy`]) containing the file descriptor to send from.
93    /// Responses without that extension are sent normally.
94    ///
95    /// Only available on Linux and FreeBSD, and only when `Io`
96    /// implements [`vibeio::io::AsInnerRawHandle`].
97    #[inline]
98    pub fn zerocopy(self) -> Http1Zerocopy<Io> {
99        Http1Zerocopy { inner: self }
100    }
101}
102
103impl<Io> Http1<Io>
104where
105    Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static,
106{
107    /// Creates a new `Http1` connection handler wrapping the given I/O stream.
108    ///
109    /// The `options` value controls limits, timeouts, and optional features;
110    /// see [`Http1Options`] for details.
111    ///
112    /// # Example
113    ///
114    /// ```rust,ignore
115    /// let http1 = Http1::new(tcp_stream, Http1Options::default());
116    /// ```
117    #[inline]
118    pub fn new(io: Io, options: options::Http1Options) -> Self {
119        // Safety: u8 is a primitive type, so we can safely assume initialization
120        let read_buf = BytesMut::with_capacity(options.max_header_size);
121        let parsed_headers: Box<[MaybeUninit<httparse::Header<'static>>]> =
122            Box::new_uninit_slice(options.max_header_count);
123        Self {
124            io,
125            options,
126            cancel_token: None,
127            parsed_headers,
128            date_header_value_cached: None,
129            cached_headers: None,
130            read_buf,
131            response_head_buf: Vec::with_capacity(1024),
132            write_buf: WriteBuf::new(),
133            connection_idle: false,
134        }
135    }
136
137    #[inline]
138    fn get_date_header_value(&mut self) -> &str {
139        let now = std::time::SystemTime::now();
140        if self.date_header_value_cached.as_ref().is_none_or(|v| {
141            v.1.duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs())
142                != now.duration_since(UNIX_EPOCH).ok().map(|d| d.as_secs())
143        }) {
144            let value = httpdate::fmt_http_date(now).to_string();
145            self.date_header_value_cached = Some((value, now));
146        }
147        self.date_header_value_cached
148            .as_ref()
149            .map(|v| v.0.as_str())
150            .unwrap_or("")
151    }
152
153    /// Attaches a [`CancellationToken`] for graceful shutdown.
154    ///
155    /// After the current in-flight request has been fully handled and its
156    /// response written, the connection loop checks whether the token has been
157    /// cancelled. If it has, the loop exits cleanly instead of waiting for the
158    /// next request.
159    ///
160    /// This allows the server to drain active connections without abruptly
161    /// closing them mid-response.
162    #[inline]
163    pub fn graceful_shutdown_token(mut self, token: CancellationToken) -> Self {
164        self.cancel_token = Some(token);
165        self
166    }
167
168    #[inline]
169    async fn fill_buf(&mut self) -> Result<usize, std::io::Error> {
170        if self.read_buf.remaining() < 1024 {
171            self.read_buf.reserve(1024);
172        }
173        let spare_capacity = self.read_buf.spare_capacity_mut();
174        // Safety: The buffer is are read only after the request head has been parsed
175        let n = self
176            .io
177            .read(unsafe {
178                &mut *std::ptr::slice_from_raw_parts_mut(
179                    spare_capacity.as_mut_ptr() as *mut u8,
180                    spare_capacity.len(),
181                )
182            })
183            .await?;
184        if n == 0 {
185            return Ok(0);
186        }
187        unsafe { self.read_buf.set_len(self.read_buf.len() + n) };
188        Ok(n)
189    }
190
191    #[inline]
192    async fn get_head(
193        &mut self,
194    ) -> Result<Option<(Bytes, &mut [MaybeUninit<httparse::Header<'static>>])>, std::io::Error>
195    {
196        let mut request_line_read = false;
197        let mut bytes_read: usize = 0;
198        let mut whitespace_trimmed = None;
199        let mut just_started = true;
200        while bytes_read < self.options.max_header_size {
201            let old_bytes_read = bytes_read;
202            let begin_search = old_bytes_read.saturating_sub(3);
203
204            let have_to_read_buf = !just_started || self.read_buf.is_empty();
205            just_started = false;
206            if have_to_read_buf {
207                let n = self.fill_buf().await?;
208                if n == 0 {
209                    if whitespace_trimmed.is_none() {
210                        return Ok(None);
211                    }
212                    return Err(std::io::Error::new(
213                        std::io::ErrorKind::UnexpectedEof,
214                        "unexpected EOF",
215                    ));
216                } else {
217                    self.connection_idle = false;
218                }
219                bytes_read = (old_bytes_read + n).min(self.options.max_header_size);
220            } else {
221                bytes_read =
222                    (old_bytes_read + self.read_buf.len()).min(self.options.max_header_size)
223            }
224
225            if whitespace_trimmed.is_none() {
226                whitespace_trimmed = self.read_buf[old_bytes_read..bytes_read]
227                    .iter()
228                    .position(|b| !b.is_ascii_whitespace());
229            }
230
231            if let Some(whitespace_trimmed) = whitespace_trimmed {
232                // Validate first line (request line) before checking for header/body separator
233                if !request_line_read {
234                    let memchr = memchr3_iter(
235                        b' ',
236                        b'\r',
237                        b'\n',
238                        &self.read_buf[whitespace_trimmed..bytes_read],
239                    );
240                    let mut spaces = 0;
241                    for separator_index in memchr {
242                        if self.read_buf[whitespace_trimmed + separator_index] == b' ' {
243                            if spaces >= 2 {
244                                return Err(std::io::Error::new(
245                                    std::io::ErrorKind::InvalidInput,
246                                    "bad request first line",
247                                ));
248                            }
249                            spaces += 1;
250                        } else if spaces == 2 {
251                            request_line_read = true;
252                            break;
253                        } else {
254                            return Err(std::io::Error::new(
255                                std::io::ErrorKind::InvalidInput,
256                                "bad request first line",
257                            ));
258                        }
259                    }
260                }
261
262                if request_line_read {
263                    let begin_search = begin_search.max(whitespace_trimmed);
264                    if let Some((separator_index, separator_len)) =
265                        search_header_body_separator(&self.read_buf[begin_search..bytes_read])
266                    {
267                        let to_parse_length =
268                            begin_search + separator_index + separator_len - whitespace_trimmed;
269                        self.read_buf.advance(whitespace_trimmed);
270                        let head = self.read_buf.split_to(to_parse_length);
271                        return Ok(Some((head.freeze(), &mut self.parsed_headers)));
272                    }
273                }
274            }
275        }
276        Err(std::io::Error::new(
277            std::io::ErrorKind::InvalidData,
278            "request too large",
279        ))
280    }
281
282    #[inline]
283    pub(crate) async fn handle_with_error_fn_and_zerocopy<
284        F,
285        Fut,
286        ResB,
287        ResBE,
288        ResE,
289        EF,
290        EFut,
291        EResB,
292        EResBE,
293        EResE,
294        ZF,
295        ZFut,
296    >(
297        mut self,
298        request_fn: F,
299        error_fn: EF,
300        mut zerocopy_fn: Option<ZF>,
301    ) -> Result<(), std::io::Error>
302    where
303        F: Fn(Request<Incoming>) -> Fut + 'static,
304        Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
305        ResB: Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
306        ResE: std::error::Error + 'static,
307        ResBE: std::error::Error + 'static,
308        EF: FnOnce(bool) -> EFut,
309        EFut: std::future::Future<Output = Result<Response<EResB>, EResE>>,
310        EResB: Body<Data = bytes::Bytes, Error = EResBE> + Unpin + 'static,
311        EResE: std::error::Error + 'static,
312        EResBE: std::error::Error + 'static,
313        ZF: FnMut(RawHandle, &'static Io, u64) -> ZFut,
314        ZFut: std::future::Future<Output = Result<(), std::io::Error>>,
315    {
316        let mut keep_alive = true;
317
318        while keep_alive {
319            let (mut request, body_tx, send_continue_body) = match if let Some(timeout) =
320                self.options.header_read_timeout
321            {
322                vibeio::time::timeout(timeout, async {
323                    if let Some(token) = self.cancel_token.clone() {
324                        token.run_until_cancelled(self.read_request()).await
325                    } else {
326                        Some(self.read_request().await)
327                    }
328                })
329                .await
330            } else {
331                Ok(Some(self.read_request().await))
332            } {
333                Ok(Some(Ok(Some(d)))) => d,
334                Ok(Some(Ok(None))) => {
335                    return Ok(());
336                }
337                Ok(Some(Err(e)))
338                    if self.connection_idle
339                        && matches!(
340                            e.kind(),
341                            std::io::ErrorKind::BrokenPipe
342                                | std::io::ErrorKind::ConnectionReset
343                                | std::io::ErrorKind::ConnectionAborted
344                                | std::io::ErrorKind::UnexpectedEof
345                        ) =>
346                {
347                    // HTTP/1.x abruptly closed when idle
348                    return Ok(());
349                }
350                Ok(Some(Err(e))) => {
351                    if let Ok(mut response) = error_fn(false).await {
352                        response
353                            .headers_mut()
354                            .insert(header::CONNECTION, HeaderValue::from_static("close"));
355
356                        let _ = self
357                            .write_response(response, Version::HTTP_11, false, zerocopy_fn.as_mut())
358                            .await;
359                    }
360                    return Err(e);
361                }
362                Ok(None) => {
363                    // Graceful shutdown
364                    return Ok(());
365                }
366                Err(_) if self.connection_idle => {
367                    // Idle connection
368                    return Ok(());
369                }
370                Err(_) => {
371                    // Timeout error
372                    if let Ok(mut response) = error_fn(true).await {
373                        response
374                            .headers_mut()
375                            .insert(header::CONNECTION, HeaderValue::from_static("close"));
376
377                        let _ = self
378                            .write_response(response, Version::HTTP_11, false, zerocopy_fn.as_mut())
379                            .await;
380                    }
381                    return Err(std::io::Error::new(
382                        std::io::ErrorKind::TimedOut,
383                        "header read timeout",
384                    ));
385                }
386            };
387
388            // Connection header detection
389            let connection_header_split = request
390                .headers()
391                .get(header::CONNECTION)
392                .and_then(|v| v.to_str().ok())
393                .map(|v| v.split(",").map(|v| v.trim()));
394            let is_connection_close = connection_header_split
395                .clone()
396                .is_some_and(|mut split| split.any(|v| v.eq_ignore_ascii_case("close")));
397            let is_connection_keep_alive = connection_header_split
398                .is_some_and(|mut split| split.any(|v| v.eq_ignore_ascii_case("keep-alive")));
399            keep_alive = !is_connection_close
400                && (is_connection_keep_alive || request.version() == http::Version::HTTP_11);
401
402            let version = request.version();
403            let is_100_continue = send_continue_body.is_some();
404
405            // 103 Early Hints
406            let early_hints_fut = if self.options.enable_early_hints {
407                let (early_hints, mut early_hints_rx) = EarlyHints::new_lazy();
408                request.extensions_mut().insert(early_hints);
409                // Safety: the function below is used only in futures_util::future::select
410                // Also, another function that would borrow self would read data,
411                // while this function would write data
412                let mut_self = unsafe { std::mem::transmute::<&mut Self, &mut Self>(&mut self) };
413                futures_util::future::Either::Left(async move {
414                    while let Some((headers, sender)) =
415                        std::future::poll_fn(|cx| early_hints_rx.poll_recv(cx)).await
416                    {
417                        sender
418                            .into_inner()
419                            .send(mut_self.write_early_hints(version, headers).await)
420                            .ok();
421                    }
422                    futures_util::future::pending::<Result<(), std::io::Error>>().await
423                })
424            } else {
425                futures_util::future::Either::Right(futures_util::future::pending::<
426                    Result<(), std::io::Error>,
427                >())
428            };
429
430            // Content-Length header
431            let content_length = request
432                .headers()
433                .get(header::CONTENT_LENGTH)
434                .and_then(|v| v.to_str().ok())
435                .and_then(|v| v.parse::<u64>().ok())
436                .unwrap_or(0);
437            let chunked = request
438                .headers()
439                .get(header::TRANSFER_ENCODING)
440                .and_then(|v| v.to_str().ok())
441                .is_some_and(|v| {
442                    v.split(',')
443                        .any(|v| v.trim().eq_ignore_ascii_case("chunked"))
444                });
445            let has_trailers = request
446                .headers()
447                .get(header::TRAILER)
448                .map(|v| v.to_str().ok().is_some_and(|s| !s.is_empty()))
449                .unwrap_or(false);
450            let write_trailers = request
451                .headers()
452                .get(header::TE)
453                .and_then(|v| v.to_str().ok())
454                .map(|v| {
455                    v.split(',')
456                        .any(|v| v.trim().eq_ignore_ascii_case("trailers"))
457                })
458                .unwrap_or(false);
459
460            // Install HTTP upgrade
461            let (upgrade_tx, upgrade_rx) = oneshot::async_channel();
462            let upgrade = Upgrade::new(upgrade_rx);
463            let upgraded = upgrade.upgraded.clone();
464            request.extensions_mut().insert(upgrade);
465
466            let mut continue_sent = false;
467            let mut response = {
468                let read_body_fut = async {
469                    if chunked {
470                        self.read_chunked_body_fn(
471                            body_tx,
472                            has_trailers,
473                            &send_continue_body,
474                            &mut continue_sent,
475                            version,
476                        )
477                        .await
478                    } else {
479                        self.read_body_fn(
480                            body_tx,
481                            content_length,
482                            &send_continue_body,
483                            &mut continue_sent,
484                            version,
485                        )
486                        .await
487                    }
488                };
489                let read_body_fut_pin = std::pin::pin!(read_body_fut);
490                let request_fut = request_fn(request);
491                let request_fut_pin = std::pin::pin!(request_fut);
492                let early_hints_fut_pin = std::pin::pin!(early_hints_fut);
493
494                let select_read_body_either =
495                    futures_util::future::select(request_fut_pin, early_hints_fut_pin);
496                let select_either =
497                    futures_util::future::select(read_body_fut_pin, select_read_body_either).await;
498
499                let (response, body_fut) = match select_either {
500                    futures_util::future::Either::Left((result, request_fut)) => {
501                        result?;
502                        (
503                            match request_fut.await {
504                                futures_util::future::Either::Left((response, _)) => response,
505                                futures_util::future::Either::Right((_, _)) => unreachable!(),
506                            },
507                            None,
508                        )
509                    }
510                    futures_util::future::Either::Right((response, read_body_fut)) => (
511                        match response {
512                            futures_util::future::Either::Left((response, _)) => response,
513                            futures_util::future::Either::Right((_, _)) => unreachable!(),
514                        },
515                        Some(read_body_fut),
516                    ),
517                };
518
519                // Drain away remaining body
520                if let Some(body_fut) = body_fut {
521                    body_fut.await?;
522                }
523
524                response.map_err(|e| std::io::Error::other(e.to_string()))?
525            };
526
527            // Response-triggered 100 Continue
528            if !continue_sent
529                && is_100_continue
530                && !response.status().is_client_error()
531                && !response.status().is_server_error()
532            {
533                self.write_100_continue(version).await?;
534            }
535
536            let mut was_upgraded = false;
537            if upgraded.load(std::sync::atomic::Ordering::Relaxed) {
538                was_upgraded = true;
539                response
540                    .headers_mut()
541                    .insert(header::CONNECTION, HeaderValue::from_static("upgrade"));
542            } else if keep_alive {
543                if version == Version::HTTP_10
544                    || response.headers().contains_key(header::CONNECTION)
545                {
546                    response
547                        .headers_mut()
548                        .insert(header::CONNECTION, HeaderValue::from_static("keep-alive"));
549                }
550            } else if version == Version::HTTP_11
551                || response.headers().contains_key(header::CONNECTION)
552            {
553                response
554                    .headers_mut()
555                    .insert(header::CONNECTION, HeaderValue::from_static("close"));
556            }
557
558            self.write_response(response, version, write_trailers, zerocopy_fn.as_mut())
559                .await?;
560
561            if was_upgraded {
562                // HTTP upgrade
563                let frozen_buf = self.read_buf.freeze();
564                let _ = upgrade_tx.send(Upgraded::new(
565                    self.io,
566                    if frozen_buf.is_empty() {
567                        None
568                    } else {
569                        Some(frozen_buf)
570                    },
571                ));
572                return Ok(());
573            }
574
575            if self.cancel_token.as_ref().is_some_and(|t| t.is_cancelled()) {
576                // Graceful shutdown requested, break out of loop
577                break;
578            }
579
580            self.connection_idle = true;
581        }
582        Ok(())
583    }
584}
585
586impl<Io> HttpProtocol for Http1<Io>
587where
588    Io: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static,
589{
590    #[inline]
591    fn handle_with_error_fn<F, Fut, ResB, ResBE, ResE, EF, EFut, EResB, EResBE, EResE>(
592        self,
593        request_fn: F,
594        error_fn: EF,
595    ) -> impl std::future::Future<Output = Result<(), std::io::Error>>
596    where
597        F: Fn(Request<Incoming>) -> Fut + 'static,
598        Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
599        ResB: Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
600        ResE: std::error::Error + 'static,
601        ResBE: std::error::Error + 'static,
602        EF: FnOnce(bool) -> EFut,
603        EFut: std::future::Future<Output = Result<Response<EResB>, EResE>>,
604        EResB: Body<Data = bytes::Bytes, Error = EResBE> + Unpin + 'static,
605        EResE: std::error::Error + 'static,
606        EResBE: std::error::Error + 'static,
607    {
608        #[allow(clippy::type_complexity)]
609        let no_zerocopy: Option<
610            Box<
611                dyn FnMut(
612                    RawHandle,
613                    &Io,
614                    u64,
615                ) -> Box<
616                    dyn std::future::Future<Output = Result<(), std::io::Error>>
617                        + Unpin
618                        + Send
619                        + Sync,
620                >,
621            >,
622        > = None;
623        self.handle_with_error_fn_and_zerocopy(request_fn, error_fn, no_zerocopy)
624    }
625
626    #[inline]
627    fn handle<F, Fut, ResB, ResBE, ResE>(
628        self,
629        request_fn: F,
630    ) -> impl std::future::Future<Output = Result<(), std::io::Error>>
631    where
632        F: Fn(Request<Incoming>) -> Fut + 'static,
633        Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
634        ResB: Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
635        ResE: std::error::Error + 'static,
636        ResBE: std::error::Error + 'static,
637    {
638        self.handle_with_error_fn(request_fn, |is_timeout| async move {
639            let mut response = Response::builder();
640            if is_timeout {
641                response = response.status(http::StatusCode::REQUEST_TIMEOUT);
642            } else {
643                response = response.status(http::StatusCode::BAD_REQUEST);
644            }
645            response.body(Empty::new())
646        })
647    }
648}
649
650/// Searches for the header/body separator in a given slice.
651/// Returns the index of the separator and the length of the separator.
652#[inline]
653fn search_header_body_separator(slice: &[u8]) -> Option<(usize, usize)> {
654    if slice.len() < 2 {
655        // Slice too short
656        return None;
657    }
658    for (i, b) in slice.iter().copied().enumerate() {
659        if b == b'\r' {
660            if slice[i + 1..].chunks(3).next() == Some(&b"\n\r\n"[..]) {
661                return Some((i, 4));
662            }
663        } else if b == b'\n' && slice.get(i + 1) == Some(&b'\n') {
664            return Some((i, 2));
665        }
666    }
667    None
668}
669
670/// Writes the chunk size to the given buffer in hexadecimal format, followed by `\r\n`.
671#[inline]
672fn write_chunk_size(dst: &mut [u8; 18], len: usize) -> &[u8] {
673    let mut n = len;
674    let mut pos = dst.len() - 2;
675    loop {
676        pos -= 1;
677        dst[pos] = HEX_DIGITS[n & 0xF];
678        n >>= 4;
679        if n == 0 {
680            break;
681        }
682    }
683    dst[dst.len() - 2] = b'\r';
684    dst[dst.len() - 1] = b'\n';
685    &dst[pos..]
686}