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