Skip to main content

vibeio_http/h1/
mod.rs

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