xref: /wasmtime-44.0.1/crates/wasi-http/src/p3/mod.rs (revision a1a74d3a)
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 
11 pub mod bindings;
12 mod body;
13 mod conv;
14 mod host;
15 mod proxy;
16 mod request;
17 mod response;
18 
19 #[cfg(feature = "default-send-request")]
20 pub use request::default_send_request;
21 pub use request::{Request, RequestOptions};
22 pub use response::Response;
23 
24 use crate::p3::bindings::http::types::ErrorCode;
25 use crate::{DEFAULT_FORBIDDEN_HEADERS, FieldMapError, WasiHttpCtx};
26 use bindings::http::{client, types};
27 use bytes::Bytes;
28 use core::ops::Deref;
29 use http::HeaderName;
30 use http::uri::Scheme;
31 use http_body_util::combinators::UnsyncBoxBody;
32 use std::sync::Arc;
33 use wasmtime::component::{HasData, Linker, ResourceTable};
34 use wasmtime_wasi::TrappableError;
35 
36 pub(crate) type HttpResult<T> = Result<T, HttpError>;
37 pub(crate) type HttpError = TrappableError<types::ErrorCode>;
38 
39 pub(crate) type HeaderResult<T> = Result<T, HeaderError>;
40 pub(crate) type HeaderError = TrappableError<types::HeaderError>;
41 
42 impl From<FieldMapError> for HeaderError {
from(e: FieldMapError) -> Self43     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 
54 pub(crate) type RequestOptionsResult<T> = Result<T, RequestOptionsError>;
55 pub(crate) type RequestOptionsError = TrappableError<types::RequestOptionsError>;
56 
57 /// The type for which this crate implements the `wasi:http` interfaces.
58 pub struct WasiHttp;
59 
60 impl HasData for WasiHttp {
61     type Data<'a> = WasiHttpCtxView<'a>;
62 }
63 
64 /// A trait which provides internal WASI HTTP state.
65 pub trait WasiHttpHooks: Send {
66     /// Whether a given header should be considered forbidden and not allowed.
is_forbidden_header(&mut self, name: &HeaderName) -> bool67     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.
is_supported_scheme(&mut self, scheme: &Scheme) -> bool74     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`.
set_host_header(&mut self) -> bool79     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.
default_scheme(&mut self) -> Option<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")]
send_request( &mut self, request: http::Request<UnsyncBoxBody<Bytes, ErrorCode>>, options: Option<RequestOptions>, fut: Box<dyn Future<Output = Result<(), ErrorCode>> + Send>, ) -> Box< dyn Future< Output = HttpResult<( http::Response<UnsyncBoxBody<Bytes, ErrorCode>>, Box<dyn Future<Output = Result<(), ErrorCode>> + Send>, )>, > + Send, >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"))]
send_request( &mut self, request: http::Request<UnsyncBoxBody<Bytes, ErrorCode>>, options: Option<RequestOptions>, fut: Box<dyn Future<Output = Result<(), ErrorCode>> + Send>, ) -> Box< dyn Future< Output = HttpResult<( http::Response<UnsyncBoxBody<Bytes, ErrorCode>>, Box<dyn Future<Output = Result<(), ErrorCode>> + Send>, )>, > + Send, >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")]
166 impl<'a> Default for &'a mut dyn WasiHttpHooks {
default() -> Self167     fn default() -> Self {
168         let x: &mut [(); 0] = &mut [];
169         x
170     }
171 }
172 
173 #[doc(hidden)]
174 #[cfg(feature = "default-send-request")]
175 impl 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")]
default_hooks() -> &'static mut dyn WasiHttpHooks180 pub fn default_hooks() -> &'static mut dyn WasiHttpHooks {
181     Default::default()
182 }
183 
184 /// View into [WasiHttpCtx] implementation and [ResourceTable].
185 pub 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.
197 pub trait WasiHttpView: Send {
198     /// Return a [WasiHttpCtxView] from mutable reference to self.
http(&mut self) -> WasiHttpCtxView<'_>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 /// ```
add_to_linker<T>(linker: &mut Linker<T>) -> wasmtime::Result<()> where T: WasiHttpView + 'static,250 pub fn add_to_linker<T>(linker: &mut Linker<T>) -> wasmtime::Result<()>
251 where
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.
263 pub enum MaybeMutable<T> {
264     /// Clone-on-write, mutable [Arc]
265     Mutable(Arc<T>),
266     /// Immutable [Arc]
267     Immutable(Arc<T>),
268 }
269 
270 impl<T> From<MaybeMutable<T>> for Arc<T> {
from(v: MaybeMutable<T>) -> Self271     fn from(v: MaybeMutable<T>) -> Self {
272         v.into_arc()
273     }
274 }
275 
276 impl<T> Deref for MaybeMutable<T> {
277     type Target = Arc<T>;
278 
deref(&self) -> &Self::Target279     fn deref(&self) -> &Self::Target {
280         match self {
281             Self::Mutable(v) | Self::Immutable(v) => v,
282         }
283     }
284 }
285 
286 impl<T> MaybeMutable<T> {
287     /// Construct a mutable [`MaybeMutable`].
new_mutable(v: impl Into<Arc<T>>) -> Self288     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`.
new_mutable_default() -> Self where T: Default,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`].
new_immutable(v: impl Into<Arc<T>>) -> Self301     pub fn new_immutable(v: impl Into<Arc<T>>) -> Self {
302         Self::Immutable(v.into())
303     }
304 
305     /// Unwrap [`MaybeMutable`] into [`Arc`].
into_arc(self) -> Arc<T>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.
get_mut(&mut self) -> Option<&mut T> where T: Clone,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 }
327