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