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}