Skip to main content

vibeio_http/h1/
zerocopy.rs

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