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 std::marker;
10use std::sync::{Arc, LazyLock};
11use wasmtime::component::{HasData, ResourceTable};
12use wasmtime_wasi::{NamedId, WasiCtxNamedView};
13
14/// A helper struct which implements [`HasData`] for the `wasi:http` APIs.
15///
16/// This can be useful when directly calling `add_to_linker` functions directly,
17/// such as [`wasmtime_wasi_http::p3::bindings::http::types::add_to_linker`] as
18/// the `D` type parameter. See [`HasData`] for more information about the type
19/// parameter's purpose.
20///
21/// When using this type you can skip the [`WasiHttpView`] trait, for example.
22///
23/// [`wasmtime_wasi_http::p3::bindings::http::types::add_to_linker`]: crate::p3::bindings::http::types::add_to_linker
24///
25/// # Examples
26///
27/// ```
28/// use wasmtime::component::Linker;
29/// use wasmtime::{Engine, Result};
30/// use wasmtime_wasi_http::{WasiHttp, WasiHttpCtxView};
31///
32/// struct MyStoreState {
33/// // ...
34/// }
35///
36/// impl MyStoreState {
37/// fn http(&mut self) -> WasiHttpCtxView<'_> {
38/// // ...
39/// # todo!()
40/// }
41/// }
42///
43/// fn main() -> Result<()> {
44/// let engine = Engine::default();
45/// let mut linker = Linker::new(&engine);
46///
47/// wasmtime_wasi_http::p3::bindings::http::types::add_to_linker::<MyStoreState, WasiHttp>(
48/// &mut linker,
49/// |state| state.http(),
50/// )?;
51/// Ok(())
52/// }
53/// ```
54pub struct WasiHttp;
55
56impl HasData for WasiHttp {
57 type Data<'a> = WasiHttpCtxView<'a>;
58}
59
60/// A trait which provides internal WASI HTTP state.
61///
62/// This trait is used by the [`add_to_linker`] convenience functions of this
63/// crate. This trait can be implemented for the `T` in `Store<T>` to provide
64/// access to wasi:http information at runtime.
65///
66/// [`add_to_linker`]: crate::p3::add_to_linker
67///
68/// # Example
69///
70/// ```
71/// use wasmtime::component::ResourceTable;
72/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView};
73///
74/// struct MyState {
75/// http_ctx: WasiHttpCtx,
76/// table: ResourceTable,
77/// }
78///
79/// impl WasiHttpView for MyState {
80/// fn http(&mut self) -> WasiHttpCtxView<'_> {
81/// WasiHttpCtxView {
82/// ctx: &mut self.http_ctx,
83/// table: &mut self.table,
84/// hooks: Default::default(),
85/// }
86/// }
87/// }
88/// ```
89pub trait WasiHttpView: Send {
90 /// Return a [WasiHttpCtxView] from mutable reference to self.
91 fn http(&mut self) -> WasiHttpCtxView<'_>;
92}
93
94/// Basis of implementation of all `wasi:http` APIs in this crate.
95///
96/// This type provides a temporary view into information such as a WASI
97/// resource table, HTTP context information, and embedder-provided hooks if
98/// so desired. THe fields in this structure are typically stored within the
99/// `T` of `Store<T>` and this struct borrows from there. All `Host` traits
100/// generated by `bindgen!` are implemented for this type.
101pub struct WasiHttpCtxView<'a> {
102 /// Mutable reference to the WASI HTTP hooks.
103 ///
104 /// Note that [`default_hooks`] or [`Default::default()`] can be used if
105 /// you don't want or need to customize this.
106 pub hooks: &'a mut dyn WasiHttpHooks,
107
108 /// Mutable reference to table used to manage resources.
109 pub table: &'a mut ResourceTable,
110
111 /// Mutable reference to the WASI HTTP context.
112 pub ctx: &'a mut WasiHttpCtx,
113}
114
115/// Default maximum size for the contents of a fields resource.
116///
117/// Typically, HTTP proxies limit headers to 8k. This number is higher than that
118/// because it not only includes the wire-size of headers but it additionally
119/// includes factors for the in-memory representation of `HeaderMap`. This is in
120/// theory high enough that no one runs into it but low enough such that a
121/// completely full `HeaderMap` doesn't break the bank in terms of memory
122/// consumption.
123const DEFAULT_FIELD_SIZE_LIMIT: usize = 128 * 1024;
124
125/// Capture the state necessary for use in the wasi-http API implementation.
126#[derive(Debug, Clone)]
127pub struct WasiHttpCtx {
128 pub(crate) field_size_limit: usize,
129}
130
131impl WasiHttpCtx {
132 /// Create a new context.
133 pub fn new() -> Self {
134 Self {
135 field_size_limit: DEFAULT_FIELD_SIZE_LIMIT,
136 }
137 }
138
139 /// Set the maximum size for any fields resources created by this context.
140 ///
141 /// The limit specified here is roughly a byte limit for the size of the
142 /// in-memory representation of headers. This means that the limit needs to
143 /// be larger than the literal representation of headers on the wire to
144 /// account for in-memory Rust-side data structures representing the header
145 /// names/values/etc.
146 pub fn set_field_size_limit(&mut self, limit: usize) {
147 self.field_size_limit = limit;
148 }
149}
150
151impl Default for WasiHttpCtx {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157/// Convenience type definition for the bodies used in this crate.
158pub type WasiBody = UnsyncBoxBody<Bytes, Error>;
159
160/// A cloneable predicate deciding whether a header name is forbidden. This
161/// predicate needs to be executed in tasks which do not have access to the
162/// store.
163pub type ForbiddenHeaderFilter = Arc<dyn Fn(&HeaderName) -> bool + Send + Sync>;
164
165/// A trait which provides hooks into internal WASI HTTP operations.
166///
167/// Note that when using this type if state is needed to implement the methods
168/// the state will need to be stored separately in a distinct structure to
169/// implement this trait as the same type can't implement both [`WasiHttpView`]
170/// and [`WasiHttpHooks`] and be usable.
171///
172/// # Example
173///
174/// ```
175/// use wasmtime::component::ResourceTable;
176/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView, WasiHttpCtxView, WasiHttpHooks};
177///
178/// struct MyState {
179/// http_ctx: WasiHttpCtx,
180/// table: ResourceTable,
181/// hooks: MyHooks,
182/// }
183///
184/// impl MyState {
185/// fn new() -> MyState {
186/// MyState {
187/// table: ResourceTable::new(),
188/// http_ctx: WasiHttpCtx::new(),
189/// hooks: MyHooks,
190/// }
191/// }
192/// }
193///
194/// impl WasiHttpView for MyState {
195/// fn http(&mut self) -> WasiHttpCtxView<'_> {
196/// WasiHttpCtxView {
197/// ctx: &mut self.http_ctx,
198/// table: &mut self.table,
199/// hooks: &mut self.hooks,
200/// }
201/// }
202/// }
203///
204/// struct MyHooks;
205///
206/// impl WasiHttpHooks for MyHooks {
207/// fn forbidden_header_filter(&mut self) -> wasmtime_wasi_http::ForbiddenHeaderFilter {
208/// std::sync::Arc::new(|name| {
209/// *name == http::header::AUTHORIZATION ||
210/// wasmtime_wasi_http::DEFAULT_FORBIDDEN_HEADERS.contains(name)
211/// })
212/// }
213/// }
214/// ```
215pub trait WasiHttpHooks: Send {
216 /// Returns a predicate deciding whether a given header should be
217 /// considered forbidden and not allowed.
218 ///
219 /// The predicate applies to header and trailer fields crossing the guest
220 /// boundary in either direction. It may be retained and invoked after the
221 /// store has been dropped, e.g. for trailers of a host body forwarded by a
222 /// guest, so it is obtained as a value rather than called on `self`.
223 ///
224 /// The default forbids [`DEFAULT_FORBIDDEN_HEADERS`].
225 fn forbidden_header_filter(&mut self) -> ForbiddenHeaderFilter {
226 static DEFAULT: LazyLock<ForbiddenHeaderFilter> =
227 LazyLock::new(|| Arc::new(|name| DEFAULT_FORBIDDEN_HEADERS.contains(name)));
228 DEFAULT.clone()
229 }
230
231 /// Whether a given scheme should be considered supported.
232 ///
233 /// `handle` will return [Error::HttpProtocolError] for unsupported schemes.
234 fn is_supported_scheme(&mut self, scheme: &Scheme) -> bool {
235 *scheme == Scheme::HTTP || *scheme == Scheme::HTTPS
236 }
237
238 /// Whether to set `host` header in the request passed to `send_request`.
239 fn set_host_header(&mut self) -> bool {
240 true
241 }
242
243 /// Scheme to default to, when not set by the guest.
244 ///
245 /// If [None], `handle` will return [Error::HttpProtocolError]
246 /// for requests missing a scheme.
247 fn default_scheme(&mut self) -> Option<Scheme> {
248 Some(Scheme::HTTPS)
249 }
250
251 /// Send an outgoing request.
252 ///
253 /// This function will be used by the `wasi:http/handler#handle` implementation.
254 ///
255 /// The specified [Future] `fut` will be used to communicate
256 /// a response processing error, if any.
257 /// For example, if the response body is consumed via `wasi:http/types.response#consume-body`,
258 /// a result will be sent on `fut`.
259 ///
260 /// The returned [Future] can be used to communicate
261 /// a request processing error, if any, to the constructor of the request.
262 /// For example, if the request was constructed via `wasi:http/types.request#new`,
263 /// a result resolved from it will be forwarded to the guest on the future handle returned.
264 ///
265 /// `Content-Length` of the request passed to this function will be validated, however no
266 /// `Content-Length` validation will be performed for the received response.
267 #[cfg(feature = "default-send-request")]
268 fn send_request(
269 &mut self,
270 request: http::Request<WasiBody>,
271 options: Option<RequestOptions>,
272 fut: Box<dyn Future<Output = Result<(), Error>> + Send>,
273 ) -> Box<
274 dyn Future<
275 Output = Result<(
276 http::Response<WasiBody>,
277 Box<dyn Future<Output = Result<(), Error>> + Send>,
278 )>,
279 > + Send,
280 > {
281 _ = fut;
282 Box::new(async move {
283 use http_body_util::BodyExt;
284
285 let (res, io) = crate::default_send_request(request, options).await?;
286 Ok((
287 res.map(BodyExt::boxed_unsync),
288 Box::new(io) as Box<dyn Future<Output = _> + Send>,
289 ))
290 })
291 }
292
293 /// Send an outgoing request.
294 ///
295 /// This function will be used by the `wasi:http/handler#handle` implementation.
296 ///
297 /// The specified [Future] `fut` will be used to communicate
298 /// a response processing error, if any.
299 /// For example, if the response body is consumed via `wasi:http/types.response#consume-body`,
300 /// a result will be sent on `fut`.
301 ///
302 /// The returned [Future] can be used to communicate
303 /// a request processing error, if any, to the constructor of the request.
304 /// For example, if the request was constructed via `wasi:http/types.request#new`,
305 /// a result resolved from it will be forwarded to the guest on the future handle returned.
306 ///
307 /// `Content-Length` of the request passed to this function will be validated, however no
308 /// `Content-Length` validation will be performed for the received response.
309 #[cfg(not(feature = "default-send-request"))]
310 fn send_request(
311 &mut self,
312 request: http::Request<WasiBody>,
313 options: Option<RequestOptions>,
314 fut: Box<dyn Future<Output = Result<(), Error>> + Send>,
315 ) -> Box<
316 dyn Future<
317 Output = Result<(
318 http::Response<WasiBody>,
319 Box<dyn Future<Output = Result<(), Error>> + Send>,
320 )>,
321 > + Send,
322 >;
323
324 /// Number of distinct write calls to the outgoing body's output-stream
325 /// that the implementation will buffer.
326 /// Default: 1.
327 #[cfg(feature = "p2")]
328 fn p2_outgoing_body_buffer_chunks(&mut self) -> usize {
329 crate::p2::DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS
330 }
331
332 /// Maximum size allowed in a write call to the outgoing body's
333 /// output-stream. Default: 1024 * 1024.
334 #[cfg(feature = "p2")]
335 fn p2_outgoing_body_chunk_size(&mut self) -> usize {
336 crate::p2::DEFAULT_OUTGOING_BODY_CHUNK_SIZE
337 }
338
339 /// Optional hook to configure the error code for hyper errors.
340 #[cfg(feature = "p2")]
341 fn p2_error_from_hyper(&mut self, err: &hyper::Error) -> p2::ErrorCode {
342 tracing::warn!("hyper error: {err:?}");
343 p2::ErrorCode::HttpProtocolError
344 }
345
346 /// Optional hook to configure the error code for connect I/O errors.
347 ///
348 /// The default distinguishes refused, unreachable, timed out, and terminated
349 /// connections. Unrecognized errors become `connection-refused`.
350 #[cfg(feature = "p2")]
351 fn p2_error_from_connect(&mut self, err: &std::io::Error) -> p2::ErrorCode {
352 tracing::warn!("connect error: {err:?}");
353 use std::io::ErrorKind;
354 match err.kind() {
355 ErrorKind::ConnectionRefused => p2::ErrorCode::ConnectionRefused,
356 ErrorKind::HostUnreachable | ErrorKind::NetworkUnreachable | ErrorKind::NetworkDown => {
357 p2::ErrorCode::DestinationUnavailable
358 }
359 ErrorKind::TimedOut => p2::ErrorCode::ConnectionTimeout,
360 ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted => {
361 p2::ErrorCode::ConnectionTerminated
362 }
363 _ if is_address_family_unsupported(err) => p2::ErrorCode::DestinationUnavailable,
364 _ => p2::ErrorCode::ConnectionRefused,
365 }
366 }
367
368 /// Optional hook to configure the error code for TLS I/O errors.
369 ///
370 /// The default distinguishes terminated connections and, with the
371 /// `default-send-request` feature, rustls certificate errors and received
372 /// alerts. Other errors become `TLS-protocol-error`.
373 #[cfg(feature = "p2")]
374 fn p2_error_from_tls(&mut self, err: &std::io::Error) -> p2::ErrorCode {
375 tracing::warn!("tls error: {err:?}");
376 #[cfg(feature = "default-send-request")]
377 if let Some(err) = err
378 .get_ref()
379 .and_then(|err| err.downcast_ref::<rustls::Error>())
380 {
381 return match err {
382 rustls::Error::InvalidCertificate(_) => p2::ErrorCode::TlsCertificateError,
383 rustls::Error::AlertReceived(alert) => {
384 p2::ErrorCode::TlsAlertReceived(p2::TlsAlertReceivedPayload {
385 alert_id: Some(u8::from(*alert)),
386 alert_message: None,
387 })
388 }
389 _ => p2::ErrorCode::TlsProtocolError,
390 };
391 }
392 match err.kind() {
393 std::io::ErrorKind::ConnectionReset
394 | std::io::ErrorKind::ConnectionAborted
395 | std::io::ErrorKind::BrokenPipe
396 | std::io::ErrorKind::UnexpectedEof => p2::ErrorCode::ConnectionTerminated,
397 _ => p2::ErrorCode::TlsProtocolError,
398 }
399 }
400
401 /// Optional hook to configure the error code for DNS errors.
402 #[cfg(all(feature = "p2", feature = "default-send-request"))]
403 fn p2_error_from_dns(&mut self, err: &rustls::pki_types::InvalidDnsNameError) -> p2::ErrorCode {
404 tracing::warn!("dns lookup error: {err:?}");
405 p2::ErrorCode::DnsError(p2::DnsErrorPayload {
406 rcode: Some("invalid dns name".to_string()),
407 info_code: None,
408 })
409 }
410
411 /// Number of distinct write calls to the outgoing body's output-stream
412 /// that the implementation will buffer.
413 /// Default: 1.
414 #[cfg(feature = "p3")]
415 fn p3_outgoing_body_buffer_chunks(&mut self) -> usize {
416 crate::p3::DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS
417 }
418
419 /// Maximum number of bytes the implementation will copy out of the guest in
420 /// a single write to an outgoing body's stream.
421 #[cfg(feature = "p3")]
422 fn p3_outgoing_body_chunk_size(&mut self) -> usize {
423 crate::p3::DEFAULT_OUTGOING_BODY_CHUNK_SIZE
424 }
425
426 /// Optional hook to configure the error code for hyper errors.
427 #[cfg(feature = "p3")]
428 fn p3_error_from_hyper(&mut self, err: &hyper::Error) -> p3::ErrorCode {
429 tracing::warn!("hyper error: {err:?}");
430 p3::ErrorCode::HttpProtocolError
431 }
432
433 /// Optional hook to configure the error code for connect I/O errors.
434 ///
435 /// The default distinguishes refused, unreachable, timed out, and terminated
436 /// connections. Unrecognized errors become `connection-refused`.
437 #[cfg(feature = "p3")]
438 fn p3_error_from_connect(&mut self, err: &std::io::Error) -> p3::ErrorCode {
439 tracing::warn!("connect error: {err:?}");
440 use std::io::ErrorKind;
441 match err.kind() {
442 ErrorKind::ConnectionRefused => p3::ErrorCode::ConnectionRefused,
443 ErrorKind::HostUnreachable | ErrorKind::NetworkUnreachable | ErrorKind::NetworkDown => {
444 p3::ErrorCode::DestinationUnavailable
445 }
446 ErrorKind::TimedOut => p3::ErrorCode::ConnectionTimeout,
447 ErrorKind::ConnectionReset | ErrorKind::ConnectionAborted => {
448 p3::ErrorCode::ConnectionTerminated
449 }
450 _ if is_address_family_unsupported(err) => p3::ErrorCode::DestinationUnavailable,
451 _ => p3::ErrorCode::ConnectionRefused,
452 }
453 }
454
455 /// Optional hook to configure the error code for TLS I/O errors.
456 ///
457 /// The default distinguishes terminated connections and, with the
458 /// `default-send-request` feature, rustls certificate errors and received
459 /// alerts. Other errors become `TLS-protocol-error`.
460 #[cfg(feature = "p3")]
461 fn p3_error_from_tls(&mut self, err: &std::io::Error) -> p3::ErrorCode {
462 tracing::warn!("tls error: {err:?}");
463 #[cfg(feature = "default-send-request")]
464 if let Some(err) = err
465 .get_ref()
466 .and_then(|err| err.downcast_ref::<rustls::Error>())
467 {
468 return match err {
469 rustls::Error::InvalidCertificate(_) => p3::ErrorCode::TlsCertificateError,
470 rustls::Error::AlertReceived(alert) => {
471 p3::ErrorCode::TlsAlertReceived(p3::TlsAlertReceivedPayload {
472 alert_id: Some(u8::from(*alert)),
473 alert_message: None,
474 })
475 }
476 _ => p3::ErrorCode::TlsProtocolError,
477 };
478 }
479 match err.kind() {
480 std::io::ErrorKind::ConnectionReset
481 | std::io::ErrorKind::ConnectionAborted
482 | std::io::ErrorKind::BrokenPipe
483 | std::io::ErrorKind::UnexpectedEof => p3::ErrorCode::ConnectionTerminated,
484 _ => p3::ErrorCode::TlsProtocolError,
485 }
486 }
487
488 /// Optional hook to configure the error code for DNS errors.
489 #[cfg(all(feature = "p3", feature = "default-send-request"))]
490 fn p3_error_from_dns(&mut self, err: &rustls::pki_types::InvalidDnsNameError) -> p3::ErrorCode {
491 tracing::warn!("dns lookup error: {err:?}");
492 p3::ErrorCode::DnsError(p3::DnsErrorPayload {
493 rcode: Some("invalid dns name".to_string()),
494 info_code: None,
495 })
496 }
497}
498
499// EAFNOSUPPORT has no corresponding stable ErrorKind.
500#[cfg(any(feature = "p2", feature = "p3"))]
501fn is_address_family_unsupported(err: &std::io::Error) -> bool {
502 use rustix::io::Errno;
503 Errno::from_io_error(err) == Some(Errno::AFNOSUPPORT)
504}
505
506/// Returns a value suitable for the `WasiHttpCtxView::hooks` field which has
507/// the default behavior for `wasi:http`.
508#[cfg(feature = "default-send-request")]
509pub fn default_hooks() -> &'static mut dyn WasiHttpHooks {
510 Default::default()
511}
512
513#[cfg(feature = "default-send-request")]
514impl<'a> Default for &'a mut dyn WasiHttpHooks {
515 fn default() -> Self {
516 let x: &mut [(); 0] = &mut [];
517 x
518 }
519}
520
521#[doc(hidden)]
522#[cfg(feature = "default-send-request")]
523impl WasiHttpHooks for [(); 0] {}
524
525/// A helper struct which implements [`HasData`] for the `wasi:http` APIs when
526/// used in combination with named imports.
527///
528/// This structure is similar in purpose to [`WasiHttp`] and is used
529/// when using the [`named_imports`] module for `wasi:http`. This structure
530/// serves as the `D` type parameter for `add_to_linker` functions.
531///
532/// [`named_imports`]: crate::p3::bindings::named_imports::wasi::http
533///
534/// # Meaning of the `T` parameter
535///
536/// Here the `T` must be something that implements [`WasiHttpNamedView`]. The
537/// corresponding `Data` for this type is [`WasiCtxNamedView`] which internally
538/// will contain `&mut T`.
539///
540/// Effectively you're going to implement [`WasiHttpNamedView`] for something in
541/// your embedding, and that's the `T` you'll fill in here.
542///
543/// # Examples
544///
545/// ```
546/// use wasmtime::component::{Linker, Component, ResourceTable};
547/// use wasmtime::{Engine, Result};
548/// use wasmtime_wasi::{NamedId, WasiCtxNamedView};
549/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpCtxView, WasiHttpNamed, WasiHttpNamedView};
550/// use wasmtime_wasi_http::p2::bindings::named_imports;
551/// use std::collections::HashMap;
552///
553/// struct MyStoreState {
554/// table: ResourceTable,
555/// states: HashMap<NamedId, WasiHttpCtx>,
556/// }
557///
558/// fn main() -> Result<()> {
559/// let engine = Engine::default();
560/// let mut linker = Linker::new(&engine);
561/// let component = Component::new(&engine, "(component)")?;
562/// let mut name_map = HashMap::new();
563///
564/// named_imports::wasi::http::outgoing_handler::add_to_linker::<MyStoreState, WasiHttpNamed<MyStoreState>>(
565/// &mut linker,
566/// &component,
567/// |name| {
568/// let len = name_map.len();
569/// Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
570/// },
571/// |state| WasiCtxNamedView(state),
572/// )?;
573/// Ok(())
574/// }
575///
576/// impl WasiHttpNamedView for MyStoreState {
577/// fn http(&mut self, id: NamedId) -> WasiHttpCtxView<'_> {
578/// let ctx = self.states.get_mut(&id).expect("state for id");
579/// WasiHttpCtxView {
580/// ctx,
581/// table: &mut self.table,
582/// hooks: Default::default(),
583/// }
584/// }
585/// }
586/// ```
587pub struct WasiHttpNamed<T>(marker::PhantomData<fn() -> T>);
588
589impl<T> HasData for WasiHttpNamed<T>
590where
591 T: WasiHttpNamedView,
592{
593 type Data<'a> = WasiCtxNamedView<'a, T>;
594}
595
596/// A trait used to look up a specific `wasi:http` context for a named import.
597///
598/// This trait is used in conjunction with the [`named_imports`] bindings
599/// generated for all `wasi:http` interfaces. The purpose of this trait is for
600/// embedders to define how a [`NamedId`] maps to a particular `wasi:http`
601/// context, here returned as [`WasiHttpCtxView`]. Embedders are responsible
602/// for assigning meaning to [`NamedId`] values themselves. These IDs are
603/// assigned when [`add_named_to_linker`] is called, for example, as the
604/// `lookup` argument to that function.
605///
606/// When using [`add_named_to_linker`] it's sufficient to implement this trait
607/// for the `T` in `Store<T>`.
608///
609/// When using `add_to_linker` in the generated `bindings::named_imports`
610/// module then values implementing this live within the `T` of `Store<T>`, and
611/// be temporarily referenced in [`WasiCtxNamedView`] where internally that'll
612/// hold `WasiCtxNamedView(&mut your_type)`.
613///
614/// [`named_imports`]: crate::p3::bindings::named_imports
615/// [`add_named_to_linker`]: crate::p3::add_named_to_linker
616///
617/// # Examples
618///
619/// ```
620/// use wasmtime::component::{Linker, Component, ResourceTable};
621/// use wasmtime::{Engine, Result};
622/// use wasmtime_wasi::NamedId;
623/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpCtxView, WasiHttpNamedView};
624/// use std::collections::HashMap;
625///
626/// struct MyStoreState {
627/// table: ResourceTable,
628/// states: HashMap<NamedId, WasiHttpCtx>,
629/// }
630///
631/// fn main() -> Result<()> {
632/// let engine = Engine::default();
633/// let mut linker = Linker::new(&engine);
634/// let component = Component::new(&engine, "(component)")?;
635/// let mut name_map = HashMap::new();
636///
637/// wasmtime_wasi_http::p3::add_named_to_linker::<MyStoreState>(
638/// &mut linker,
639/// &component,
640/// |_, name| {
641/// let len = name_map.len();
642/// Ok(NamedId(*name_map.entry(name.to_string()).or_insert(len)))
643/// },
644/// )?;
645/// Ok(())
646/// }
647///
648/// impl WasiHttpNamedView for MyStoreState {
649/// fn http(&mut self, id: NamedId) -> WasiHttpCtxView<'_> {
650/// let ctx = self.states.get_mut(&id).expect("state for id");
651/// WasiHttpCtxView {
652/// ctx,
653/// table: &mut self.table,
654/// hooks: Default::default(),
655/// }
656/// }
657/// }
658/// ```
659pub trait WasiHttpNamedView: Send + 'static {
660 /// Looks up the [`WasiHttpCtxView`] for the given [`NamedId`].
661 ///
662 /// This method will resolve the `id` specified to a specific HTTP context
663 /// that is available to be used. Note that this method is specifically
664 /// infallible meaning that an HTTP context must be returned and this cannot
665 /// generate a trap or panic or similar.
666 ///
667 /// Embedders are responsible for allocating [`NamedId`] and assigning
668 /// meaning to ids. When a `Linker` is populated embedders will have the
669 /// ability to generate a `NamedId` for all imports found, and then that
670 /// embedder-allocated id is then passed back here when the corresponding
671 /// imported function is invoked.
672 ///
673 /// It's strongly recommended, though not always required, that the
674 /// [`ResourceTable`] in the returned [`WasiHttpCtxView`] is the same table
675 /// that the default context, [`WasiHttpView::http`], uses. Resources
676 /// created through a named import can be used through interfaces bound to
677 /// the default context (and vice versa), and with separate tables those
678 /// lookups fail or find another context's resources.
679 fn http(&mut self, id: NamedId) -> WasiHttpCtxView<'_>;
680}