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
20pub use request::Request;
21pub use response::Response;
22
23/// The default value configured for [`WasiHttpHooks::p3_outgoing_body_chunk_size`].
24///
25/// [`WasiHttpHooks::p3_outgoing_body_chunk_size`]: crate::WasiHttpHooks::p3_outgoing_body_chunk_size
26pub const DEFAULT_OUTGOING_BODY_CHUNK_SIZE: usize = 1024 * 1024;
27
28use crate::{FieldMapError, WasiHttp, WasiHttpNamed, WasiHttpNamedView, WasiHttpView};
29use bindings::http::{client, types};
30use core::ops::Deref;
31use std::sync::Arc;
32use wasmtime::component::{Component, Linker};
33use wasmtime_wasi::{NamedId, TrappableError, WasiCtxNamedView};
34
35pub(crate) type HttpResult<T> = Result<T, HttpError>;
36pub(crate) type HttpError = TrappableError<types::ErrorCode>;
37
38pub(crate) type HeaderResult<T> = Result<T, HeaderError>;
39pub(crate) type HeaderError = TrappableError<types::HeaderError>;
40
41impl From<FieldMapError> for HeaderError {
42    fn from(e: FieldMapError) -> Self {
43        match e {
44            FieldMapError::Immutable => types::HeaderError::Immutable.into(),
45            FieldMapError::InvalidHeaderName | FieldMapError::InvalidHeaderValue => {
46                types::HeaderError::InvalidSyntax.into()
47            }
48            FieldMapError::TooManyFields | FieldMapError::TotalSizeTooBig => {
49                types::HeaderError::SizeExceeded.into()
50            }
51            FieldMapError::Forbidden => types::HeaderError::Forbidden.into(),
52        }
53    }
54}
55
56pub(crate) type RequestOptionsResult<T> = Result<T, RequestOptionsError>;
57pub(crate) type RequestOptionsError = TrappableError<types::RequestOptionsError>;
58
59/// Add all interfaces from this module into the `linker` provided.
60///
61/// This function will add all interfaces implemented by this module to the
62/// [`Linker`], which corresponds to the `wasi:http/imports` world supported by
63/// this module.
64///
65/// # Example
66///
67/// ```
68/// use wasmtime::{Engine, Result, Store, Config};
69/// use wasmtime::component::{Linker, ResourceTable};
70/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpCtxView, WasiHttpView};
71///
72/// fn main() -> Result<()> {
73///     let mut config = Config::new();
74///     config.wasm_component_model_async(true);
75///     let engine = Engine::new(&config)?;
76///
77///     let mut linker = Linker::<MyState>::new(&engine);
78///     wasmtime_wasi_http::p3::add_to_linker(&mut linker)?;
79///     // ... add any further functionality to `linker` if desired ...
80///
81///     let mut store = Store::new(
82///         &engine,
83///         MyState::default(),
84///     );
85///
86///     // ... use `linker` to instantiate within `store` ...
87///
88///     Ok(())
89/// }
90///
91/// #[derive(Default)]
92/// struct MyState {
93///     http: WasiHttpCtx,
94///     table: ResourceTable,
95/// }
96///
97/// impl WasiHttpView for MyState {
98///     fn http(&mut self) -> WasiHttpCtxView<'_> {
99///         WasiHttpCtxView {
100///             ctx: &mut self.http,
101///             table: &mut self.table,
102///             hooks: Default::default(),
103///         }
104///     }
105/// }
106/// ```
107pub fn add_to_linker<T>(linker: &mut Linker<T>) -> wasmtime::Result<()>
108where
109    T: WasiHttpView + 'static,
110{
111    client::add_to_linker::<_, WasiHttp>(linker, T::http)?;
112    types::add_to_linker::<_, WasiHttp>(linker, T::http)?;
113    Ok(())
114}
115
116/// Interfaces that are added via [`add_named_to_linker`].
117#[derive(Copy, Clone, PartialEq, Eq, Debug)]
118pub enum Interface {
119    /// `wasi:http/client`
120    HttpClient,
121    /// `wasi:http/types`
122    HttpTypes,
123}
124
125/// Add all interfaces from this module into the `linker` provided for any
126/// named imports that a component has.
127///
128/// This function is similar to [`add_to_linker`] except that it's specifically
129/// designed to work with named imports of `wasi:http` interfaces that
130/// components may have. This requires a [`Component`] parameter to be passed in
131/// when populating the [`Linker`] provided to see what the [`Component`]
132/// actually imports.
133///
134/// If this isn't low level enough you can invoke the bindgen-generated
135/// `add_to_linker` functions within the [`named_imports`] module directly
136/// instead.
137///
138/// [`named_imports`]: crate::p3::bindings::named_imports
139///
140/// The `lookup` function provided here is invoked for every named import found
141/// for a particular interface. The [`Interface`] given is what's being bound,
142/// and the `&str` argument is the name that the component imports it as. The
143/// embedder can then decide how it would like to allocate a [`NamedId`] for
144/// this import. If `Ok` is returned then the linker is populated with this
145/// name, and imported functions will pass the [`NamedId`] later to the
146/// implementation of [`WasiHttpNamedView`] on `T` when invoked. If `Err` is
147/// returned then the error will cause this entire function to fail and this
148/// function call will return the same error.
149///
150/// # Example
151///
152/// ```
153/// use std::collections::HashMap;
154/// use wasmtime::component::{Component, Linker, ResourceTable};
155/// use wasmtime::{Engine, Result, Store, Config};
156/// use wasmtime_wasi::NamedId;
157/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpCtxView, WasiHttpNamedView};
158///
159/// fn main() -> Result<()> {
160///     let mut config = Config::new();
161///     config.wasm_component_model_async(true);
162///     let engine = Engine::new(&config)?;
163///     let component = Component::new(&engine, "(component)")?;
164///
165///     let mut linker = Linker::<MyState>::new(&engine);
166///
167///     // ... add default functionality to `linker` as needed ...
168///
169///     // and then additionally fill in any specific named imports `component`
170///     // might have for `wasi:http` interfaces.
171///     let mut name_map = HashMap::new();
172///     wasmtime_wasi_http::p3::add_named_to_linker(&mut linker, &component, |_i, name| {
173///         let len = name_map.len();
174///         Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
175///     })?;
176///
177///     // Here a `WasiHttpCtx` is allocated per-named-import and will then be
178///     // referred to internally by the [`NamedId`] allocated above. You could
179///     // also use `name_map` to configure each context differently.
180///     let mut my_state = MyState::default();
181///     for _ in 0..name_map.len() {
182///         my_state.contexts.push(WasiHttpCtx::default());
183///     }
184///     let mut store = Store::new(&engine, my_state);
185///
186///     // ... use `linker` to instantiate within `store` ...
187///
188///     Ok(())
189/// }
190///
191/// #[derive(Default)]
192/// struct MyState {
193///     table: ResourceTable,
194///     contexts: Vec<WasiHttpCtx>,
195/// }
196///
197/// impl WasiHttpNamedView for MyState {
198///     fn http(&mut self, id: NamedId) -> WasiHttpCtxView<'_> {
199///         WasiHttpCtxView {
200///             ctx: &mut self.contexts[id.0],
201///             table: &mut self.table,
202///             hooks: Default::default(),
203///         }
204///     }
205/// }
206/// ```
207pub fn add_named_to_linker<T>(
208    linker: &mut Linker<T>,
209    component: &Component,
210    mut lookup: impl FnMut(Interface, &str) -> wasmtime::Result<NamedId>,
211) -> wasmtime::Result<()>
212where
213    T: WasiHttpNamedView,
214{
215    use crate::p3::bindings::named_imports::wasi::http::{client, types};
216    client::add_to_linker::<_, WasiHttpNamed<T>>(
217        linker,
218        component,
219        |name| lookup(Interface::HttpClient, name),
220        |x| WasiCtxNamedView(x),
221    )?;
222    types::add_to_linker::<_, WasiHttpNamed<T>>(
223        linker,
224        component,
225        |name| lookup(Interface::HttpTypes, name),
226        |x| WasiCtxNamedView(x),
227    )?;
228    Ok(())
229}
230
231/// An [Arc], which may be immutable.
232///
233/// In `wasi:http` resources like `fields` or `request-options` may be
234/// mutable or immutable. This construct is used to model them efficiently.
235pub enum MaybeMutable<T> {
236    /// Clone-on-write, mutable [Arc]
237    Mutable(Arc<T>),
238    /// Immutable [Arc]
239    Immutable(Arc<T>),
240}
241
242impl<T> From<MaybeMutable<T>> for Arc<T> {
243    fn from(v: MaybeMutable<T>) -> Self {
244        v.into_arc()
245    }
246}
247
248impl<T> Deref for MaybeMutable<T> {
249    type Target = Arc<T>;
250
251    fn deref(&self) -> &Self::Target {
252        match self {
253            Self::Mutable(v) | Self::Immutable(v) => v,
254        }
255    }
256}
257
258impl<T> MaybeMutable<T> {
259    /// Construct a mutable [`MaybeMutable`].
260    pub fn new_mutable(v: impl Into<Arc<T>>) -> Self {
261        Self::Mutable(v.into())
262    }
263
264    /// Construct a mutable [`MaybeMutable`] filling it with default `T`.
265    pub fn new_mutable_default() -> Self
266    where
267        T: Default,
268    {
269        Self::new_mutable(T::default())
270    }
271
272    /// Construct an immutable [`MaybeMutable`].
273    pub fn new_immutable(v: impl Into<Arc<T>>) -> Self {
274        Self::Immutable(v.into())
275    }
276
277    /// Unwrap [`MaybeMutable`] into [`Arc`].
278    pub fn into_arc(self) -> Arc<T> {
279        match self {
280            Self::Mutable(v) | Self::Immutable(v) => v,
281        }
282    }
283
284    /// If this [`MaybeMutable`] is [`Mutable`](MaybeMutable::Mutable),
285    /// return a mutable reference to it, otherwise return `None`.
286    ///
287    /// Internally, this will use [`Arc::make_mut`] and will clone the underlying
288    /// value, if multiple strong references to the inner [`Arc`] exist.
289    pub fn get_mut(&mut self) -> Option<&mut T>
290    where
291        T: Clone,
292    {
293        match self {
294            Self::Mutable(v) => Some(Arc::make_mut(v)),
295            Self::Immutable(..) => None,
296        }
297    }
298}