Skip to main content

wasmtime_wasi_http/p3/
mod.rs

1//! Experimental, unstable and incomplete implementation of wasip3 version of `wasi:http`.
2//!
3//! This module is under heavy development.
4//! It is not compliant with semver and is not ready
5//! for production use.
6//!
7//! Bug and security fixes limited to wasip3 will not be given patch releases.
8//!
9//! Documentation of this module may be incorrect or out-of-sync with the implementation.
10
11pub mod bindings;
12mod body;
13mod conv;
14mod helpers;
15mod host;
16mod proxy;
17mod request;
18mod response;
19
20#[cfg(feature = "default-send-request")]
21pub use request::default_send_request;
22pub use request::{Request, RequestOptions};
23pub use response::Response;
24
25use crate::p3::bindings::http::types::ErrorCode;
26use crate::{DEFAULT_FORBIDDEN_HEADERS, FieldMapError, WasiHttpCtx};
27use bindings::http::{client, types};
28use bytes::Bytes;
29use core::ops::Deref;
30use http::HeaderName;
31use http::uri::Scheme;
32use http_body_util::combinators::UnsyncBoxBody;
33use std::sync::Arc;
34use wasmtime::component::{HasData, Linker, ResourceTable};
35use wasmtime_wasi::TrappableError;
36
37pub(crate) type HttpResult<T> = Result<T, HttpError>;
38pub(crate) type HttpError = TrappableError<types::ErrorCode>;
39
40pub(crate) type HeaderResult<T> = Result<T, HeaderError>;
41pub(crate) type HeaderError = TrappableError<types::HeaderError>;
42
43impl From<FieldMapError> for HeaderError {
44    fn from(e: FieldMapError) -> Self {
45        match e {
46            FieldMapError::Immutable => types::HeaderError::Immutable.into(),
47            FieldMapError::InvalidHeaderName => types::HeaderError::InvalidSyntax.into(),
48            FieldMapError::TooManyFields | FieldMapError::TotalSizeTooBig => {
49                types::HeaderError::SizeExceeded.into()
50            }
51        }
52    }
53}
54
55pub(crate) type RequestOptionsResult<T> = Result<T, RequestOptionsError>;
56pub(crate) type RequestOptionsError = TrappableError<types::RequestOptionsError>;
57
58/// The type for which this crate implements the `wasi:http` interfaces.
59pub struct WasiHttp;
60
61impl HasData for WasiHttp {
62    type Data<'a> = WasiHttpCtxView<'a>;
63}
64
65/// A trait which provides internal WASI HTTP state.
66pub trait WasiHttpHooks: Send {
67    /// Whether a given header should be considered forbidden and not allowed.
68    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
69        DEFAULT_FORBIDDEN_HEADERS.contains(name)
70    }
71
72    /// Whether a given scheme should be considered supported.
73    ///
74    /// `handle` will return [ErrorCode::HttpProtocolError] for unsupported schemes.
75    fn is_supported_scheme(&mut self, scheme: &Scheme) -> bool {
76        *scheme == Scheme::HTTP || *scheme == Scheme::HTTPS
77    }
78
79    /// Whether to set `host` header in the request passed to `send_request`.
80    fn set_host_header(&mut self) -> bool {
81        true
82    }
83
84    /// Scheme to default to, when not set by the guest.
85    ///
86    /// If [None], `handle` will return [ErrorCode::HttpProtocolError]
87    /// for requests missing a scheme.
88    fn default_scheme(&mut self) -> Option<Scheme> {
89        Some(Scheme::HTTPS)
90    }
91
92    /// Send an outgoing request.
93    ///
94    /// This function will be used by the `wasi:http/handler#handle` implementation.
95    ///
96    /// The specified [Future] `fut` will be used to communicate
97    /// a response processing error, if any.
98    /// For example, if the response body is consumed via `wasi:http/types.response#consume-body`,
99    /// a result will be sent on `fut`.
100    ///
101    /// The returned [Future] can be used to communicate
102    /// a request processing error, if any, to the constructor of the request.
103    /// For example, if the request was constructed via `wasi:http/types.request#new`,
104    /// a result resolved from it will be forwarded to the guest on the future handle returned.
105    ///
106    /// `Content-Length` of the request passed to this function will be validated, however no
107    /// `Content-Length` validation will be performed for the received response.
108    #[cfg(feature = "default-send-request")]
109    fn send_request(
110        &mut self,
111        request: http::Request<UnsyncBoxBody<Bytes, ErrorCode>>,
112        options: Option<RequestOptions>,
113        fut: Box<dyn Future<Output = Result<(), ErrorCode>> + Send>,
114    ) -> Box<
115        dyn Future<
116                Output = HttpResult<(
117                    http::Response<UnsyncBoxBody<Bytes, ErrorCode>>,
118                    Box<dyn Future<Output = Result<(), ErrorCode>> + Send>,
119                )>,
120            > + Send,
121    > {
122        _ = fut;
123        Box::new(async move {
124            use http_body_util::BodyExt;
125
126            let (res, io) = default_send_request(request, options).await?;
127            Ok((
128                res.map(BodyExt::boxed_unsync),
129                Box::new(io) as Box<dyn Future<Output = _> + Send>,
130            ))
131        })
132    }
133
134    /// Send an outgoing request.
135    ///
136    /// This function will be used by the `wasi:http/handler#handle` implementation.
137    ///
138    /// The specified [Future] `fut` will be used to communicate
139    /// a response processing error, if any.
140    /// For example, if the response body is consumed via `wasi:http/types.response#consume-body`,
141    /// a result will be sent on `fut`.
142    ///
143    /// The returned [Future] can be used to communicate
144    /// a request processing error, if any, to the constructor of the request.
145    /// For example, if the request was constructed via `wasi:http/types.request#new`,
146    /// a result resolved from it will be forwarded to the guest on the future handle returned.
147    ///
148    /// `Content-Length` of the request passed to this function will be validated, however no
149    /// `Content-Length` validation will be performed for the received response.
150    #[cfg(not(feature = "default-send-request"))]
151    fn send_request(
152        &mut self,
153        request: http::Request<UnsyncBoxBody<Bytes, ErrorCode>>,
154        options: Option<RequestOptions>,
155        fut: Box<dyn Future<Output = Result<(), ErrorCode>> + Send>,
156    ) -> Box<
157        dyn Future<
158                Output = HttpResult<(
159                    http::Response<UnsyncBoxBody<Bytes, ErrorCode>>,
160                    Box<dyn Future<Output = Result<(), ErrorCode>> + Send>,
161                )>,
162            > + Send,
163    >;
164}
165
166#[cfg(feature = "default-send-request")]
167impl<'a> Default for &'a mut dyn WasiHttpHooks {
168    fn default() -> Self {
169        let x: &mut [(); 0] = &mut [];
170        x
171    }
172}
173
174#[doc(hidden)]
175#[cfg(feature = "default-send-request")]
176impl WasiHttpHooks for [(); 0] {}
177
178/// Returns a value suitable for the `WasiHttpCtxView::hooks` field which has
179/// the default behavior for `wasi:http`.
180#[cfg(feature = "default-send-request")]
181pub fn default_hooks() -> &'static mut dyn WasiHttpHooks {
182    Default::default()
183}
184
185/// View into [WasiHttpCtx] implementation and [ResourceTable].
186pub struct WasiHttpCtxView<'a> {
187    /// Mutable reference to the WASI HTTP hooks.
188    pub hooks: &'a mut dyn WasiHttpHooks,
189
190    /// Mutable reference to table used to manage resources.
191    pub table: &'a mut ResourceTable,
192
193    /// Mutable reference to the WASI HTTP context.
194    pub ctx: &'a mut WasiHttpCtx,
195}
196
197/// A trait which provides internal WASI HTTP state.
198pub trait WasiHttpView: Send {
199    /// Return a [WasiHttpCtxView] from mutable reference to self.
200    fn http(&mut self) -> WasiHttpCtxView<'_>;
201}
202
203/// Add all interfaces from this module into the `linker` provided.
204///
205/// This function will add all interfaces implemented by this module to the
206/// [`Linker`], which corresponds to the `wasi:http/imports` world supported by
207/// this module.
208///
209/// # Example
210///
211/// ```
212/// use wasmtime::{Engine, Result, Store, Config};
213/// use wasmtime::component::{Linker, ResourceTable};
214/// use wasmtime_wasi_http::{WasiHttpCtx, p3::{WasiHttpCtxView, WasiHttpView}};
215///
216/// fn main() -> Result<()> {
217///     let mut config = Config::new();
218///     config.wasm_component_model_async(true);
219///     let engine = Engine::new(&config)?;
220///
221///     let mut linker = Linker::<MyState>::new(&engine);
222///     wasmtime_wasi_http::p3::add_to_linker(&mut linker)?;
223///     // ... add any further functionality to `linker` if desired ...
224///
225///     let mut store = Store::new(
226///         &engine,
227///         MyState::default(),
228///     );
229///
230///     // ... use `linker` to instantiate within `store` ...
231///
232///     Ok(())
233/// }
234///
235/// #[derive(Default)]
236/// struct MyState {
237///     http: WasiHttpCtx,
238///     table: ResourceTable,
239/// }
240///
241/// impl WasiHttpView for MyState {
242///     fn http(&mut self) -> WasiHttpCtxView<'_> {
243///         WasiHttpCtxView {
244///             ctx: &mut self.http,
245///             table: &mut self.table,
246///             hooks: Default::default(),
247///         }
248///     }
249/// }
250/// ```
251pub fn add_to_linker<T>(linker: &mut Linker<T>) -> wasmtime::Result<()>
252where
253    T: WasiHttpView + 'static,
254{
255    client::add_to_linker::<_, WasiHttp>(linker, T::http)?;
256    types::add_to_linker::<_, WasiHttp>(linker, T::http)?;
257    Ok(())
258}
259
260/// An [Arc], which may be immutable.
261///
262/// In `wasi:http` resources like `fields` or `request-options` may be
263/// mutable or immutable. This construct is used to model them efficiently.
264pub enum MaybeMutable<T> {
265    /// Clone-on-write, mutable [Arc]
266    Mutable(Arc<T>),
267    /// Immutable [Arc]
268    Immutable(Arc<T>),
269}
270
271impl<T> From<MaybeMutable<T>> for Arc<T> {
272    fn from(v: MaybeMutable<T>) -> Self {
273        v.into_arc()
274    }
275}
276
277impl<T> Deref for MaybeMutable<T> {
278    type Target = Arc<T>;
279
280    fn deref(&self) -> &Self::Target {
281        match self {
282            Self::Mutable(v) | Self::Immutable(v) => v,
283        }
284    }
285}
286
287impl<T> MaybeMutable<T> {
288    /// Construct a mutable [`MaybeMutable`].
289    pub fn new_mutable(v: impl Into<Arc<T>>) -> Self {
290        Self::Mutable(v.into())
291    }
292
293    /// Construct a mutable [`MaybeMutable`] filling it with default `T`.
294    pub fn new_mutable_default() -> Self
295    where
296        T: Default,
297    {
298        Self::new_mutable(T::default())
299    }
300
301    /// Construct an immutable [`MaybeMutable`].
302    pub fn new_immutable(v: impl Into<Arc<T>>) -> Self {
303        Self::Immutable(v.into())
304    }
305
306    /// Unwrap [`MaybeMutable`] into [`Arc`].
307    pub fn into_arc(self) -> Arc<T> {
308        match self {
309            Self::Mutable(v) | Self::Immutable(v) => v,
310        }
311    }
312
313    /// If this [`MaybeMutable`] is [`Mutable`](MaybeMutable::Mutable),
314    /// return a mutable reference to it, otherwise return `None`.
315    ///
316    /// Internally, this will use [`Arc::make_mut`] and will clone the underlying
317    /// value, if multiple strong references to the inner [`Arc`] exist.
318    pub fn get_mut(&mut self) -> Option<&mut T>
319    where
320        T: Clone,
321    {
322        match self {
323            Self::Mutable(v) => Some(Arc::make_mut(v)),
324            Self::Immutable(..) => None,
325        }
326    }
327}