Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/wasi-http/src/p3/mod.rs
3088 views
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::types::DEFAULT_FORBIDDEN_HEADERS;
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
pub(crate) type RequestOptionsResult<T> = Result<T, RequestOptionsError>;
43
pub(crate) type RequestOptionsError = TrappableError<types::RequestOptionsError>;
44
45
/// The type for which this crate implements the `wasi:http` interfaces.
46
pub struct WasiHttp;
47
48
impl HasData for WasiHttp {
49
type Data<'a> = WasiHttpCtxView<'a>;
50
}
51
52
/// A trait which provides internal WASI HTTP state.
53
pub 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)]
156
pub struct DefaultWasiHttpCtx;
157
158
#[cfg(feature = "default-send-request")]
159
impl WasiHttpCtx for DefaultWasiHttpCtx {}
160
161
/// View into [WasiHttpCtx] implementation and [ResourceTable].
162
pub 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.
171
pub 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
/// ```
223
pub fn add_to_linker<T>(linker: &mut Linker<T>) -> wasmtime::Result<()>
224
where
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.
236
pub enum MaybeMutable<T> {
237
/// Clone-on-write, mutable [Arc]
238
Mutable(Arc<T>),
239
/// Immutable [Arc]
240
Immutable(Arc<T>),
241
}
242
243
impl<T> From<MaybeMutable<T>> for Arc<T> {
244
fn from(v: MaybeMutable<T>) -> Self {
245
v.into_arc()
246
}
247
}
248
249
impl<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
259
impl<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
}
300
301