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