wasmtime_wasi_http/ctx.rs
1#[cfg(feature = "p2")]
2use crate::p2::bindings::http::types as p2;
3#[cfg(feature = "p3")]
4use crate::p3::bindings::http::types as p3;
5use crate::{DEFAULT_FORBIDDEN_HEADERS, Error, RequestOptions, Result};
6use bytes::Bytes;
7use http::{HeaderName, uri::Scheme};
8use http_body_util::combinators::UnsyncBoxBody;
9use wasmtime::component::{HasData, ResourceTable};
10
11/// A helper struct which implements [`HasData`] for the `wasi:http` APIs.
12///
13/// This can be useful when directly calling `add_to_linker` functions directly,
14/// such as [`wasmtime_wasi_http::p3::bindings::http::types::add_to_linker`] as
15/// the `D` type parameter. See [`HasData`] for more information about the type
16/// parameter's purpose.
17///
18/// When using this type you can skip the [`WasiHttpView`] trait, for example.
19///
20/// [`wasmtime_wasi_http::p3::bindings::http::types::add_to_linker`]: crate::p3::bindings::http::types::add_to_linker
21///
22/// # Examples
23///
24/// ```
25/// use wasmtime::component::Linker;
26/// use wasmtime::{Engine, Result};
27/// use wasmtime_wasi_http::{WasiHttp, WasiHttpCtxView};
28///
29/// struct MyStoreState {
30/// // ...
31/// }
32///
33/// impl MyStoreState {
34/// fn http(&mut self) -> WasiHttpCtxView<'_> {
35/// // ...
36/// # todo!()
37/// }
38/// }
39///
40/// fn main() -> Result<()> {
41/// let engine = Engine::default();
42/// let mut linker = Linker::new(&engine);
43///
44/// wasmtime_wasi_http::p3::bindings::http::types::add_to_linker::<MyStoreState, WasiHttp>(
45/// &mut linker,
46/// |state| state.http(),
47/// )?;
48/// Ok(())
49/// }
50/// ```
51pub struct WasiHttp;
52
53impl HasData for WasiHttp {
54 type Data<'a> = WasiHttpCtxView<'a>;
55}
56
57/// A trait which provides internal WASI HTTP state.
58///
59/// This trait is used by the [`add_to_linker`] convenience functions of this
60/// crate. This trait can be implemented for the `T` in `Store<T>` to provide
61/// access to wasi:http information at runtime.
62///
63/// [`add_to_linker`]: crate::p3::add_to_linker
64///
65/// # Example
66///
67/// ```
68/// use wasmtime::component::ResourceTable;
69/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView};
70///
71/// struct MyState {
72/// http_ctx: WasiHttpCtx,
73/// table: ResourceTable,
74/// }
75///
76/// impl WasiHttpView for MyState {
77/// fn http(&mut self) -> WasiHttpCtxView<'_> {
78/// WasiHttpCtxView {
79/// ctx: &mut self.http_ctx,
80/// table: &mut self.table,
81/// hooks: Default::default(),
82/// }
83/// }
84/// }
85/// ```
86pub trait WasiHttpView: Send {
87 /// Return a [WasiHttpCtxView] from mutable reference to self.
88 fn http(&mut self) -> WasiHttpCtxView<'_>;
89}
90
91/// Basis of implementation of all `wasi:http` APIs in this crate.
92///
93/// This type provides a temporary view into information such as a WASI
94/// resource table, HTTP context information, and embedder-provided hooks if
95/// so desired. THe fields in this structure are typically stored within the
96/// `T` of `Store<T>` and this struct borrows from there. All `Host` traits
97/// generated by `bindgen!` are implemented for this type.
98pub struct WasiHttpCtxView<'a> {
99 /// Mutable reference to the WASI HTTP hooks.
100 ///
101 /// Note that [`default_hooks`] or [`Default::default()`] can be used if
102 /// you don't want or need to customize this.
103 pub hooks: &'a mut dyn WasiHttpHooks,
104
105 /// Mutable reference to table used to manage resources.
106 pub table: &'a mut ResourceTable,
107
108 /// Mutable reference to the WASI HTTP context.
109 pub ctx: &'a mut WasiHttpCtx,
110}
111
112/// Default maximum size for the contents of a fields resource.
113///
114/// Typically, HTTP proxies limit headers to 8k. This number is higher than that
115/// because it not only includes the wire-size of headers but it additionally
116/// includes factors for the in-memory representation of `HeaderMap`. This is in
117/// theory high enough that no one runs into it but low enough such that a
118/// completely full `HeaderMap` doesn't break the bank in terms of memory
119/// consumption.
120const DEFAULT_FIELD_SIZE_LIMIT: usize = 128 * 1024;
121
122/// Capture the state necessary for use in the wasi-http API implementation.
123#[derive(Debug, Clone)]
124pub struct WasiHttpCtx {
125 pub(crate) field_size_limit: usize,
126}
127
128impl WasiHttpCtx {
129 /// Create a new context.
130 pub fn new() -> Self {
131 Self {
132 field_size_limit: DEFAULT_FIELD_SIZE_LIMIT,
133 }
134 }
135
136 /// Set the maximum size for any fields resources created by this context.
137 ///
138 /// The limit specified here is roughly a byte limit for the size of the
139 /// in-memory representation of headers. This means that the limit needs to
140 /// be larger than the literal representation of headers on the wire to
141 /// account for in-memory Rust-side data structures representing the header
142 /// names/values/etc.
143 pub fn set_field_size_limit(&mut self, limit: usize) {
144 self.field_size_limit = limit;
145 }
146}
147
148impl Default for WasiHttpCtx {
149 fn default() -> Self {
150 Self::new()
151 }
152}
153
154/// Convenience type definition for the bodies used in this crate.
155pub type WasiBody = UnsyncBoxBody<Bytes, Error>;
156
157/// A trait which provides hooks into internal WASI HTTP operations.
158///
159/// Note that when using this type if state is needed to implement the methods
160/// the state will need to be stored separately in a distinct structure to
161/// implement this trait as the same type can't implement both [`WasiHttpView`]
162/// and [`WasiHttpHooks`] and be usable.
163///
164/// # Example
165///
166/// ```
167/// use wasmtime::component::ResourceTable;
168/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView, WasiHttpHooks};
169///
170/// struct MyState {
171/// http_ctx: WasiHttpCtx,
172/// table: ResourceTable,
173/// hooks: MyHooks,
174/// }
175///
176/// impl MyState {
177/// fn new() -> MyState {
178/// MyState {
179/// table: ResourceTable::new(),
180/// http_ctx: WasiHttpCtx::new(),
181/// hooks: MyHooks,
182/// }
183/// }
184/// }
185///
186/// impl WasiHttpView for MyState {
187/// fn http(&mut self) -> WasiHttpCtxView<'_> {
188/// WasiHttpCtxView {
189/// ctx: &mut self.http_ctx,
190/// table: &mut self.table,
191/// hooks: &mut self.hooks,
192/// }
193/// }
194/// }
195///
196/// struct MyHooks;
197///
198/// impl WasiHttpHooks for MyHooks {
199/// fn is_forbidden_header(&mut self, name: &http::HeaderName) -> bool {
200/// *name == http::header::AUTHORIZATION ||
201/// wasmtime_wasi_http::DEFAULT_FORBIDDEN_HEADERS.contains(name)
202/// }
203/// }
204/// ```
205pub trait WasiHttpHooks: Send {
206 /// Whether a given header should be considered forbidden and not allowed.
207 fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
208 DEFAULT_FORBIDDEN_HEADERS.contains(name)
209 }
210
211 /// Whether a given scheme should be considered supported.
212 ///
213 /// `handle` will return [Error::HttpProtocolError] for unsupported schemes.
214 fn is_supported_scheme(&mut self, scheme: &Scheme) -> bool {
215 *scheme == Scheme::HTTP || *scheme == Scheme::HTTPS
216 }
217
218 /// Whether to set `host` header in the request passed to `send_request`.
219 fn set_host_header(&mut self) -> bool {
220 true
221 }
222
223 /// Scheme to default to, when not set by the guest.
224 ///
225 /// If [None], `handle` will return [Error::HttpProtocolError]
226 /// for requests missing a scheme.
227 fn default_scheme(&mut self) -> Option<Scheme> {
228 Some(Scheme::HTTPS)
229 }
230
231 /// Send an outgoing request.
232 ///
233 /// This function will be used by the `wasi:http/handler#handle` implementation.
234 ///
235 /// The specified [Future] `fut` will be used to communicate
236 /// a response processing error, if any.
237 /// For example, if the response body is consumed via `wasi:http/types.response#consume-body`,
238 /// a result will be sent on `fut`.
239 ///
240 /// The returned [Future] can be used to communicate
241 /// a request processing error, if any, to the constructor of the request.
242 /// For example, if the request was constructed via `wasi:http/types.request#new`,
243 /// a result resolved from it will be forwarded to the guest on the future handle returned.
244 ///
245 /// `Content-Length` of the request passed to this function will be validated, however no
246 /// `Content-Length` validation will be performed for the received response.
247 #[cfg(feature = "default-send-request")]
248 fn send_request(
249 &mut self,
250 request: http::Request<WasiBody>,
251 options: Option<RequestOptions>,
252 fut: Box<dyn Future<Output = Result<(), Error>> + Send>,
253 ) -> Box<
254 dyn Future<
255 Output = Result<(
256 http::Response<WasiBody>,
257 Box<dyn Future<Output = Result<(), Error>> + Send>,
258 )>,
259 > + Send,
260 > {
261 _ = fut;
262 Box::new(async move {
263 use http_body_util::BodyExt;
264
265 let (res, io) = crate::default_send_request(request, options).await?;
266 Ok((
267 res.map(BodyExt::boxed_unsync),
268 Box::new(io) as Box<dyn Future<Output = _> + Send>,
269 ))
270 })
271 }
272
273 /// Send an outgoing request.
274 ///
275 /// This function will be used by the `wasi:http/handler#handle` implementation.
276 ///
277 /// The specified [Future] `fut` will be used to communicate
278 /// a response processing error, if any.
279 /// For example, if the response body is consumed via `wasi:http/types.response#consume-body`,
280 /// a result will be sent on `fut`.
281 ///
282 /// The returned [Future] can be used to communicate
283 /// a request processing error, if any, to the constructor of the request.
284 /// For example, if the request was constructed via `wasi:http/types.request#new`,
285 /// a result resolved from it will be forwarded to the guest on the future handle returned.
286 ///
287 /// `Content-Length` of the request passed to this function will be validated, however no
288 /// `Content-Length` validation will be performed for the received response.
289 #[cfg(not(feature = "default-send-request"))]
290 fn send_request(
291 &mut self,
292 request: http::Request<WasiBody>,
293 options: Option<RequestOptions>,
294 fut: Box<dyn Future<Output = Result<(), Error>> + Send>,
295 ) -> Box<
296 dyn Future<
297 Output = Result<(
298 http::Response<WasiBody>,
299 Box<dyn Future<Output = Result<(), Error>> + Send>,
300 )>,
301 > + Send,
302 >;
303
304 /// Number of distinct write calls to the outgoing body's output-stream
305 /// that the implementation will buffer.
306 /// Default: 1.
307 #[cfg(feature = "p2")]
308 fn p2_outgoing_body_buffer_chunks(&mut self) -> usize {
309 crate::p2::DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS
310 }
311
312 /// Maximum size allowed in a write call to the outgoing body's
313 /// output-stream. Default: 1024 * 1024.
314 #[cfg(feature = "p2")]
315 fn p2_outgoing_body_chunk_size(&mut self) -> usize {
316 crate::p2::DEFAULT_OUTGOING_BODY_CHUNK_SIZE
317 }
318
319 /// Optional hook to configure the error code for hyper errors.
320 #[cfg(feature = "p2")]
321 fn p2_error_from_hyper(&mut self, err: &hyper::Error) -> p2::ErrorCode {
322 tracing::warn!("hyper error: {err:?}");
323 p2::ErrorCode::HttpProtocolError
324 }
325
326 /// Optional hook to configure the error code for connect I/O errors.
327 #[cfg(feature = "p2")]
328 fn p2_error_from_connect(&mut self, err: &std::io::Error) -> p2::ErrorCode {
329 tracing::warn!("connect error: {err:?}");
330 p2::ErrorCode::ConnectionRefused
331 }
332
333 /// Optional hook to configure the error code for TLS I/O errors.
334 #[cfg(feature = "p2")]
335 fn p2_error_from_tls(&mut self, err: &std::io::Error) -> p2::ErrorCode {
336 tracing::warn!("tls error: {err:?}");
337 p2::ErrorCode::TlsProtocolError
338 }
339
340 /// Optional hook to configure the error code for DNS errors.
341 #[cfg(all(feature = "p2", feature = "default-send-request"))]
342 fn p2_error_from_dns(&mut self, err: &rustls::pki_types::InvalidDnsNameError) -> p2::ErrorCode {
343 tracing::warn!("dns lookup error: {err:?}");
344 p2::ErrorCode::DnsError(p2::DnsErrorPayload {
345 rcode: Some("invalid dns name".to_string()),
346 info_code: None,
347 })
348 }
349
350 /// Optional hook to configure the error code for hyper errors.
351 #[cfg(feature = "p3")]
352 fn p3_error_from_hyper(&mut self, err: &hyper::Error) -> p3::ErrorCode {
353 tracing::warn!("hyper error: {err:?}");
354 p3::ErrorCode::HttpProtocolError
355 }
356
357 /// Optional hook to configure the error code for connect I/O errors.
358 #[cfg(feature = "p3")]
359 fn p3_error_from_connect(&mut self, err: &std::io::Error) -> p3::ErrorCode {
360 tracing::warn!("connect error: {err:?}");
361 p3::ErrorCode::ConnectionRefused
362 }
363
364 /// Optional hook to configure the error code for TLS I/O errors.
365 #[cfg(feature = "p3")]
366 fn p3_error_from_tls(&mut self, err: &std::io::Error) -> p3::ErrorCode {
367 tracing::warn!("tls error: {err:?}");
368 p3::ErrorCode::TlsProtocolError
369 }
370
371 /// Optional hook to configure the error code for DNS errors.
372 #[cfg(all(feature = "p3", feature = "default-send-request"))]
373 fn p3_error_from_dns(&mut self, err: &rustls::pki_types::InvalidDnsNameError) -> p3::ErrorCode {
374 tracing::warn!("dns lookup error: {err:?}");
375 p3::ErrorCode::DnsError(p3::DnsErrorPayload {
376 rcode: Some("invalid dns name".to_string()),
377 info_code: None,
378 })
379 }
380}
381
382/// Returns a value suitable for the `WasiHttpCtxView::hooks` field which has
383/// the default behavior for `wasi:http`.
384#[cfg(feature = "default-send-request")]
385pub fn default_hooks() -> &'static mut dyn WasiHttpHooks {
386 Default::default()
387}
388
389#[cfg(feature = "default-send-request")]
390impl<'a> Default for &'a mut dyn WasiHttpHooks {
391 fn default() -> Self {
392 let x: &mut [(); 0] = &mut [];
393 x
394 }
395}
396
397#[doc(hidden)]
398#[cfg(feature = "default-send-request")]
399impl WasiHttpHooks for [(); 0] {}