Skip to main content

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}