Skip to main content

vibeio_http/
upgrade.rs

1use std::{
2    future::Future,
3    pin::Pin,
4    sync::{atomic::AtomicBool, Arc},
5    task::ready,
6};
7
8use bytes::BufMut;
9use futures_util::FutureExt;
10use http::Request;
11use http_body::Body;
12use send_wrapper::SendWrapper;
13use tokio::io::{AsyncRead, AsyncWrite};
14
15/// Represents a successfully upgraded HTTP connection.
16///
17/// After a successful HTTP upgrade handshake (e.g. WebSocket or HTTP/2
18/// cleartext), the original TCP stream is handed off as an [`Upgraded`] value.
19/// It implements both [`AsyncRead`] and [`AsyncWrite`], so it can be used as a
20/// plain async I/O object by the protocol taking over the connection.
21///
22/// Any bytes that were already read from the socket as part of the HTTP request
23/// but not yet consumed are prepended to the read stream via the `leftover`
24/// buffer, ensuring no data is lost during the transition.
25pub struct Upgraded {
26    reader: SendWrapper<Pin<Box<dyn AsyncRead + Unpin>>>,
27    writer: SendWrapper<Pin<Box<dyn AsyncWrite + Unpin>>>,
28    leftover: Option<bytes::Bytes>,
29    has_ended: bool,
30}
31
32impl Upgraded {
33    #[inline]
34    pub(super) fn new(
35        io: impl AsyncRead + AsyncWrite + Unpin + 'static,
36        leftover: Option<bytes::Bytes>,
37    ) -> Self {
38        let (reader, writer) = tokio::io::split(io);
39        Self {
40            reader: SendWrapper::new(Box::pin(reader)),
41            writer: SendWrapper::new(Box::pin(writer)),
42            leftover,
43            has_ended: false,
44        }
45    }
46}
47
48impl AsyncRead for Upgraded {
49    #[inline]
50    fn poll_read(
51        mut self: std::pin::Pin<&mut Self>,
52        cx: &mut std::task::Context<'_>,
53        buf: &mut tokio::io::ReadBuf<'_>,
54    ) -> std::task::Poll<std::io::Result<()>> {
55        let orig_buf_len = buf.remaining();
56        if let Some(leftover) = &mut self.leftover {
57            let slice_len = leftover.len().min(buf.remaining());
58            let leftover_to_write = leftover.split_to(slice_len);
59            buf.put(leftover_to_write);
60            if leftover.is_empty() {
61                self.leftover = None;
62            }
63            return std::task::Poll::Ready(Ok(()));
64        }
65        match (*self.reader).as_mut().poll_read(cx, buf) {
66            std::task::Poll::Ready(Err(e))
67                if matches!(e.kind(), std::io::ErrorKind::UnexpectedEof) =>
68            {
69                self.has_ended = true;
70                std::task::Poll::Ready(Ok(()))
71            }
72            poll => {
73                if poll.is_ready() && buf.remaining() == orig_buf_len {
74                    // No data has been read, mark the I/O as "ended".
75                    //
76                    // If there was no data where more data would be expected,
77                    // this would be Poll::Pending (pending I/O op)...
78                    self.has_ended = true;
79                }
80                poll
81            }
82        }
83    }
84}
85
86impl AsyncWrite for Upgraded {
87    #[inline]
88    fn poll_write(
89        mut self: std::pin::Pin<&mut Self>,
90        cx: &mut std::task::Context<'_>,
91        buf: &[u8],
92    ) -> std::task::Poll<std::io::Result<usize>> {
93        match (*self.writer).as_mut().poll_write(cx, buf) {
94            std::task::Poll::Ready(Err(e))
95                if self.has_ended && matches!(e.kind(), std::io::ErrorKind::BrokenPipe) =>
96            {
97                std::task::Poll::Ready(Ok(0))
98            }
99            poll => poll,
100        }
101    }
102
103    #[inline]
104    fn poll_flush(
105        mut self: std::pin::Pin<&mut Self>,
106        cx: &mut std::task::Context<'_>,
107    ) -> std::task::Poll<std::io::Result<()>> {
108        match (*self.writer).as_mut().poll_flush(cx) {
109            std::task::Poll::Ready(Err(e))
110                if self.has_ended && matches!(e.kind(), std::io::ErrorKind::BrokenPipe) =>
111            {
112                std::task::Poll::Ready(Ok(()))
113            }
114            poll => poll,
115        }
116    }
117
118    #[inline]
119    fn poll_shutdown(
120        mut self: std::pin::Pin<&mut Self>,
121        cx: &mut std::task::Context<'_>,
122    ) -> std::task::Poll<std::io::Result<()>> {
123        match (*self.writer).as_mut().poll_shutdown(cx) {
124            std::task::Poll::Ready(Err(e))
125                if self.has_ended && matches!(e.kind(), std::io::ErrorKind::BrokenPipe) =>
126            {
127                std::task::Poll::Ready(Ok(()))
128            }
129            poll => poll,
130        }
131    }
132
133    #[inline]
134    fn is_write_vectored(&self) -> bool {
135        self.writer.is_write_vectored()
136    }
137
138    #[inline]
139    fn poll_write_vectored(
140        mut self: Pin<&mut Self>,
141        cx: &mut std::task::Context<'_>,
142        bufs: &[std::io::IoSlice<'_>],
143    ) -> std::task::Poll<std::io::Result<usize>> {
144        (*self.writer).as_mut().poll_write_vectored(cx, bufs)
145    }
146}
147
148#[derive(Clone)]
149pub(super) struct Upgrade {
150    inner: Arc<futures_util::lock::Mutex<oneshot::AsyncReceiver<Upgraded>>>,
151    pub(super) upgraded: Arc<AtomicBool>,
152}
153
154impl Upgrade {
155    #[inline]
156    pub(super) fn new(inner: oneshot::AsyncReceiver<Upgraded>) -> Self {
157        Self {
158            inner: Arc::new(futures_util::lock::Mutex::new(inner)),
159            upgraded: Arc::new(AtomicBool::new(false)),
160        }
161    }
162}
163
164impl Future for Upgrade {
165    type Output = Option<Upgraded>;
166
167    #[inline]
168    fn poll(
169        self: Pin<&mut Self>,
170        cx: &mut std::task::Context<'_>,
171    ) -> std::task::Poll<Self::Output> {
172        let mut inner = ready!(self.inner.lock().poll_unpin(cx));
173        match inner.poll_unpin(cx) {
174            std::task::Poll::Ready(result) => std::task::Poll::Ready(result.ok()),
175            std::task::Poll::Pending => std::task::Poll::Pending,
176        }
177    }
178}
179
180/// A future that resolves to an [`Upgraded`] connection once the HTTP upgrade
181/// handshake has been completed by the server side.
182///
183/// Obtain an `OnUpgrade` by calling [`prepare_upgrade`] on an incoming
184/// request. The future will yield `Some(Upgraded)` when the server has
185/// finished writing the upgrade response, or `None` if the upgrade was
186/// cancelled or the connection was closed before the handshake completed.
187///
188/// # Example
189///
190/// ```rust,ignore
191/// if let Some(on_upgrade) = prepare_upgrade(&mut request) {
192///     tokio::spawn(async move {
193///         if let Some(upgraded) = on_upgrade.await {
194///             // `upgraded` is now a raw async I/O stream
195///         }
196///     });
197/// }
198/// ```
199#[derive(Clone)]
200pub struct OnUpgrade {
201    inner: Upgrade,
202}
203
204impl Future for OnUpgrade {
205    type Output = Option<Upgraded>;
206
207    #[inline]
208    fn poll(
209        mut self: Pin<&mut Self>,
210        cx: &mut std::task::Context<'_>,
211    ) -> std::task::Poll<Self::Output> {
212        self.inner.poll_unpin(cx)
213    }
214}
215
216/// Prepares an HTTP upgrade on the given request.
217///
218/// This function removes the internal `Upgrade` token from the request's
219/// extensions and marks the connection as "to be upgraded". The returned
220/// [`OnUpgrade`] future resolves to the raw [`Upgraded`] I/O stream after the
221/// server has sent the `101 Switching Protocols` response.
222///
223/// Returns `None` if the request does not carry an upgrade token, which
224/// happens when the connection handler was not configured to support upgrades
225/// or the upgrade extension has already been consumed.
226///
227/// # Panics
228///
229/// Does not panic; returns `None` instead of panicking on missing state.
230#[inline]
231pub fn prepare_upgrade(req: &mut Request<impl Body>) -> Option<OnUpgrade> {
232    req.extensions_mut().remove::<Upgrade>().map(|inner| {
233        inner
234            .upgraded
235            .store(true, std::sync::atomic::Ordering::Relaxed);
236        OnUpgrade { inner }
237    })
238}