Skip to main content

vibeio_http/h1/
zerocopy.rs

1#[cfg(all(target_os = "linux", feature = "h1-zerocopy"))]
2use http::Response;
3use http_body::Body;
4#[cfg(all(target_os = "linux", feature = "h1-zerocopy"))]
5use http_body_util::Empty;
6
7#[cfg(all(target_os = "linux", feature = "h1-zerocopy"))]
8use super::{Http1, HttpProtocol};
9
10#[derive(Clone)]
11pub(super) struct ZerocopyResponse {
12    pub(super) handle: super::RawHandle,
13}
14
15unsafe impl Send for ZerocopyResponse {}
16unsafe impl Sync for ZerocopyResponse {}
17
18/// Installs a zero-copy hint on an HTTP response, directing the connection
19/// handler to use emulated sendfile (Linux only) to transmit the body.
20///
21/// # Parameters
22///
23/// - `response` – the response whose extensions will receive the hint.
24/// - `handle` – the raw file descriptor of the file to send. The caller is
25///   responsible for ensuring the file descriptor remains valid and open for
26///   the entire duration of the response write.
27///
28/// # Safety
29///
30/// The caller must guarantee that `handle` is a valid, open file descriptor
31/// that will not be closed before the response body has been fully sent.
32pub unsafe fn install_zerocopy(response: &mut http::Response<impl Body>, handle: super::RawHandle) {
33    response
34        .extensions_mut()
35        .insert(ZerocopyResponse { handle });
36}
37
38/// An HTTP/1.x connection handler that uses emulated sendfile for zero-copy
39/// response body transmission on Linux.
40///
41/// Obtain an instance via [`Http1::zerocopy`]. When a response has a
42/// `ZerocopyResponse` extension installed (see [`install_zerocopy`]), the
43/// handler will use emulated sendfile (utilizing the Linux `splice(2)` syscall
44/// and Unix pipes) to stream the file from the kernel page cache
45/// to the socket, bypassing user-space copies.
46///
47/// For responses without that extension the behaviour is identical to the
48/// regular [`Http1`] handler.
49///
50/// Only available on Linux (`target_os = "linux"`).
51#[cfg(all(target_os = "linux", feature = "h1-zerocopy"))]
52pub struct Http1Zerocopy<Io> {
53    pub(super) inner: Http1<Io>,
54}
55
56#[cfg(all(target_os = "linux", feature = "h1-zerocopy"))]
57impl<Io> HttpProtocol for Http1Zerocopy<Io>
58where
59    for<'a> Io: tokio::io::AsyncRead
60        + tokio::io::AsyncWrite
61        + vibeio::io::AsInnerRawHandle<'a>
62        + Unpin
63        + 'static,
64{
65    fn handle_with_error_fn<F, Fut, ResB, ResBE, ResE, EF, EFut, EResB, EResBE, EResE>(
66        self,
67        request_fn: F,
68        error_fn: EF,
69    ) -> impl std::future::Future<Output = Result<(), std::io::Error>>
70    where
71        F: Fn(http::Request<super::Incoming>) -> Fut + 'static,
72        Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
73        ResB: Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
74        ResE: std::error::Error,
75        ResBE: std::error::Error,
76        EF: FnOnce(bool) -> EFut,
77        EFut: std::future::Future<Output = Result<Response<EResB>, EResE>>,
78        EResB: Body<Data = bytes::Bytes, Error = EResBE> + Unpin + 'static,
79        EResE: std::error::Error,
80        EResBE: std::error::Error,
81    {
82        self.inner.handle_with_error_fn_and_zerocopy(
83            request_fn,
84            error_fn,
85            Some(move |fd, io, len| async move {
86                use std::os::fd::BorrowedFd;
87
88                let fd = unsafe { BorrowedFd::borrow_raw(fd) };
89                let _ = vibeio::io::sendfile_exact(&fd, io, len).await?;
90                Ok(())
91            }),
92        )
93    }
94
95    fn handle<F, Fut, ResB, ResBE, ResE>(
96        self,
97        request_fn: F,
98    ) -> impl std::future::Future<Output = Result<(), std::io::Error>>
99    where
100        F: Fn(http::Request<super::Incoming>) -> Fut + 'static,
101        Fut: std::future::Future<Output = Result<Response<ResB>, ResE>> + 'static,
102        ResB: Body<Data = bytes::Bytes, Error = ResBE> + Unpin + 'static,
103        ResE: std::error::Error,
104        ResBE: std::error::Error,
105    {
106        self.handle_with_error_fn(request_fn, |is_timeout| async move {
107            let mut response = Response::builder();
108            if is_timeout {
109                response = response.status(http::StatusCode::REQUEST_TIMEOUT);
110            } else {
111                response = response.status(http::StatusCode::BAD_REQUEST);
112            }
113            response.body(Empty::new())
114        })
115    }
116}