Skip to main content

rama_http/io/
upgrade.rs

1//! HTTP Upgrades.
2//!
3//! This module deals with managing [HTTP Upgrades][mdn] in rama_http_core. Since
4//! several concepts in HTTP allow for first talking HTTP, and then converting
5//! to a different protocol, this module conflates them into a single API.
6//! Those include:
7//!
8//! - HTTP/1.1 Upgrades
9//! - HTTP `CONNECT`
10//!
11//! You are responsible for any other pre-requisites to establish an upgrade,
12//! such as sending the appropriate headers, methods, and status codes. You can
13//! then use [`handle_upgrade`] to grab a `Future` which will resolve to the upgraded
14//! connection object, or an error if the upgrade fails.
15//!
16//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism
17//!
18//! # Client
19//!
20//! Sending an HTTP upgrade from the client involves setting
21//! either the appropriate method, if wanting to `CONNECT`, or headers such as
22//! `Upgrade` and `Connection`, on the `http::Request`. Once receiving the
23//! `http::Response` back, you must check for the specific information that the
24//! upgrade is agreed upon by the server (such as a `101` status code), and then
25//! get the `Future` from the `Response`.
26//!
27//! # Server
28//!
29//! Receiving upgrade requests in a server requires you to check the relevant
30//! headers in a `Request`, and if an upgrade should be done, you then send the
31//! corresponding headers in a response. To then wait for rama_http_core to finish the
32//! upgrade, you call `on()` with the `Request`, and then can spawn a task
33//! awaiting it.
34
35use rama_core::error::BoxErrorExt as _;
36use std::any::TypeId;
37use std::fmt;
38use std::io;
39use std::pin::Pin;
40use std::sync::Arc;
41use std::task::{Context, Poll};
42
43use parking_lot::Mutex;
44use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
45use tokio::sync::oneshot;
46
47use rama_core::bytes::Bytes;
48use rama_core::error::BoxError;
49use rama_core::extensions::Extension;
50use rama_core::extensions::Extensions;
51use rama_core::extensions::ExtensionsRef;
52use rama_core::io::Io;
53use rama_core::io::rewind::Rewind;
54use rama_core::telemetry::tracing::trace;
55use rama_net::extensions::StreamTransformed;
56use rama_utils::macros::generate_set_and_with;
57
58/// An upgraded HTTP connection.
59///
60/// This type holds a trait object internally of the original IO that
61/// was used to speak HTTP before the upgrade. It can be used directly
62/// as a [`AsyncRead`] or [`AsyncWrite`] for convenience.
63///
64/// Alternatively, if the exact type is known, this can be deconstructed
65/// into its parts.
66pub struct Upgraded {
67    io: Rewind<Box<dyn UpgradeIo>>,
68    extensions: Extensions,
69}
70
71/// A future for a possible HTTP upgrade.
72///
73/// If no upgrade was available, or it doesn't succeed, yields an `Error`.
74#[derive(Clone, Extension)]
75#[extension(tags(http))]
76pub struct OnUpgrade {
77    rx: Arc<Mutex<oneshot::Receiver<Result<Upgraded, BoxError>>>>,
78}
79
80/// The deconstructed parts of an [`Upgraded`] type.
81///
82/// Includes the original IO type, and a read buffer of bytes that the
83/// HTTP state machine may have already read before completing an upgrade.
84#[derive(Debug)]
85#[non_exhaustive]
86pub struct Parts<T> {
87    /// The original IO object used before the upgrade.
88    pub io: T,
89    /// A buffer of bytes that have been read but not processed as HTTP.
90    ///
91    /// For instance, if the `Connection` is used for an HTTP upgrade request,
92    /// it is possible the server sent back the first bytes of the new protocol
93    /// along with the response upgrade.
94    ///
95    /// You will want to check for any existing bytes if you plan to continue
96    /// communicating on the IO object.
97    pub read_buf: Bytes,
98    /// Extensions associated with this upgrade
99    pub extensions: Extensions,
100}
101
102/// Gets a pending HTTP upgrade from this message and handles it.
103///
104/// This can be called on types implementing [`ExtensionsRef`]:
105///
106/// Some notable examples are:
107/// - `http::Request<B>`
108/// - `http::Response<B>`
109/// - `&rama_http::Request<B>`
110/// - `&rama_http::Response<B>`
111pub fn handle_upgrade<T: ExtensionsRef>(
112    msg: T,
113) -> impl Future<Output = Result<Upgraded, BoxError>> + 'static {
114    let msg_ext = msg.extensions().clone();
115    let on_upgrade = match msg_ext.get_ref::<OnUpgrade>().cloned() {
116        Some(on_upgrade) => {
117            trace!("upgrading this: {:?}", on_upgrade);
118            if on_upgrade.has_handled_upgrade() {
119                Err(BoxError::from_static_str(
120                    "upgraded has already been handled",
121                ))
122            } else {
123                Ok(on_upgrade)
124            }
125        }
126        None => Err(BoxError::from_static_str("no pending update found")),
127    };
128
129    async move {
130        let upgraded = match on_upgrade {
131            Ok(on_upgrade) => on_upgrade.await?,
132            Err(err) => return Err(err),
133        };
134        Ok(upgraded)
135    }
136}
137
138/// A pending upgrade, created with [`pending`].
139pub struct Pending {
140    tx: oneshot::Sender<Result<Upgraded, BoxError>>,
141}
142
143/// Initiate an upgrade.
144#[must_use]
145pub fn pending() -> (Pending, OnUpgrade) {
146    let (tx, rx) = oneshot::channel();
147
148    (
149        Pending { tx },
150        OnUpgrade {
151            rx: Arc::new(Mutex::new(rx)),
152        },
153    )
154}
155
156// ===== impl Upgraded =====
157
158impl Upgraded {
159    /// Create a new [`Upgraded`] from an IO stream and existing buffer.
160    ///
161    /// The [`Upgraded`] starts with the io [`Extensions`]s. When
162    /// driven through [`handle_upgrade`] the parent is set to the message
163    /// that triggered the upgrade (which already encodes the underlying
164    /// connection through its `Ingress` / `Egress` wrapper), so the upgraded
165    /// blob inherits everything reachable from that message.
166    pub fn new<T>(io: T, read_buf: Bytes) -> Self
167    where
168        T: Io + Unpin + ExtensionsRef,
169    {
170        let extensions = io.extensions().clone();
171        extensions.insert(StreamTransformed {
172            by: "rama-http::Upgraded",
173        });
174        Self {
175            extensions,
176            io: Rewind::new_buffered(Box::new(io), read_buf),
177        }
178    }
179
180    generate_set_and_with! {
181        pub fn extensions(mut self, extensions: Extensions) -> Self {
182            self.extensions = extensions;
183            self
184        }
185    }
186
187    /// Tries to downcast the internal trait object to the type passed.
188    ///
189    /// On success, returns the downcasted parts. On error, returns the
190    /// `Upgraded` back.
191    pub fn downcast<T: Io + Unpin>(self) -> Result<Parts<T>, Self> {
192        let (io, buf) = self.io.into_inner();
193        match io.__downcast() {
194            Ok(t) => Ok(Parts {
195                io: *t,
196                read_buf: buf,
197                extensions: self.extensions,
198            }),
199            Err(io) => Err(Self {
200                io: Rewind::new_buffered(io, buf),
201                extensions: self.extensions,
202            }),
203        }
204    }
205}
206
207trait UpgradeIo: Io + Unpin {
208    fn __type_id(&self) -> TypeId {
209        TypeId::of::<Self>()
210    }
211}
212
213impl<T: Io + Unpin> UpgradeIo for T {}
214
215impl dyn UpgradeIo {
216    fn __is<T: UpgradeIo>(&self) -> bool {
217        let t = TypeId::of::<T>();
218        self.__type_id() == t
219    }
220
221    /// downcast a Box wrapped Type to a Box<T>
222    /// implemented by raw pointer cast.
223    fn __downcast<T: UpgradeIo>(self: Box<Self>) -> Result<Box<T>, Box<Self>> {
224        if self.__is::<T>() {
225            // Taken from `std::error::Error::downcast()`.
226            // SAFETY:
227            // 1. `self.__is::<T>()` performs a runtime type check (typically via `TypeId`),
228            //    guaranteeing that the underlying concrete type is indeed `T`.
229            // 2. We use `Box::into_raw` to obtain a pointer to the trait object, which
230            //    has the same memory layout as the underlying concrete type `T` at the
231            //    location identified by the runtime check.
232            // 3. `Box::from_raw` is safe to call here because we are reconstructing the
233            //    box from the pointer that was originally created by `Box::into_raw`,
234            //    and the type `T` matches the original type of the allocated memory.
235            unsafe {
236                let raw: *mut dyn UpgradeIo = Box::into_raw(self);
237                Ok(Box::from_raw(raw.cast()))
238            }
239        } else {
240            Err(self)
241        }
242    }
243}
244
245impl ExtensionsRef for Upgraded {
246    fn extensions(&self) -> &Extensions {
247        &self.extensions
248    }
249}
250
251#[warn(clippy::missing_trait_methods)]
252impl AsyncRead for Upgraded {
253    fn poll_read(
254        mut self: Pin<&mut Self>,
255        cx: &mut Context<'_>,
256        buf: &mut ReadBuf<'_>,
257    ) -> Poll<io::Result<()>> {
258        Pin::new(&mut self.io).poll_read(cx, buf)
259    }
260}
261
262#[warn(clippy::missing_trait_methods)]
263impl AsyncWrite for Upgraded {
264    fn poll_write(
265        mut self: Pin<&mut Self>,
266        cx: &mut Context<'_>,
267        buf: &[u8],
268    ) -> Poll<io::Result<usize>> {
269        Pin::new(&mut self.io).poll_write(cx, buf)
270    }
271
272    fn poll_write_vectored(
273        mut self: Pin<&mut Self>,
274        cx: &mut Context<'_>,
275        bufs: &[io::IoSlice<'_>],
276    ) -> Poll<io::Result<usize>> {
277        Pin::new(&mut self.io).poll_write_vectored(cx, bufs)
278    }
279
280    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
281        Pin::new(&mut self.io).poll_flush(cx)
282    }
283
284    fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
285        Pin::new(&mut self.io).poll_shutdown(cx)
286    }
287
288    fn is_write_vectored(&self) -> bool {
289        self.io.is_write_vectored()
290    }
291}
292
293impl fmt::Debug for Upgraded {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        f.debug_struct("Upgraded").finish()
296    }
297}
298
299impl fmt::Debug for Pending {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        f.debug_struct("Pending").finish()
302    }
303}
304
305// ===== impl OnUpgrade =====
306
307impl OnUpgrade {
308    /// Returns true if there was an upgrade and the upgrade has already been handled
309    #[must_use]
310    pub fn has_handled_upgrade(&self) -> bool {
311        self.rx.lock().is_terminated()
312    }
313}
314
315impl Future for OnUpgrade {
316    type Output = Result<Upgraded, BoxError>;
317
318    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
319        Pin::new(&mut *self.rx.lock())
320            .poll(cx)
321            .map(|res| match res {
322                Ok(Ok(upgraded)) => Ok(upgraded),
323                Ok(Err(err)) => Err(err),
324                Err(_oneshot_canceled) => Err(BoxError::from_static_str(
325                    "OnUpgrade: cancelled while expecting upgrade",
326                )),
327            })
328    }
329}
330
331impl fmt::Debug for OnUpgrade {
332    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333        f.debug_struct("OnUpgrade").field("rx", &self.rx).finish()
334    }
335}
336
337// ===== impl Pending =====
338
339impl Pending {
340    /// fulfill the pending upgrade with the given [`Upgraded`] stream.
341    pub fn fulfill(self, upgraded: Upgraded) {
342        trace!("pending upgrade fulfill");
343        _ = self.tx.send(Ok(upgraded));
344    }
345
346    /// Don't fulfill the pending Upgrade, but instead signal that
347    /// upgrades are handled manually.
348    pub fn manual(self) {
349        trace!("pending upgrade handled manually");
350        _ = self.tx.send(Err(BoxError::from_static_str(
351            "OnUpgrade: manual upgrade failed",
352        )));
353    }
354}
355
356#[cfg(test)]
357mod tests {
358    use rama_core::ServiceInput;
359    use tokio_test::io::{Builder, Mock};
360
361    use super::*;
362
363    #[test]
364    fn upgraded_downcast() {
365        let io = Builder::default().build();
366        let io = ServiceInput::new(io);
367        let upgraded = Upgraded::new(io, Bytes::new());
368        let upgraded = upgraded.downcast::<std::io::Cursor<Vec<u8>>>().unwrap_err();
369        upgraded.downcast::<ServiceInput<Mock>>().unwrap();
370    }
371
372    #[test]
373    fn upgraded_carries_stream_transformed_marker() {
374        let io = ServiceInput::new(Builder::default().build());
375        let upgraded = Upgraded::new(io, Bytes::new());
376        let marker = upgraded
377            .extensions()
378            .get_ref::<StreamTransformed>()
379            .expect("Upgraded::new must insert the StreamTransformed marker");
380        assert_eq!(marker.by, "rama-http::Upgraded");
381    }
382}