Skip to main content

wasmtime_wasi_http/
types.rs

1//! Implements the base structure (i.e. [WasiHttpCtx]) that will provide the
2//! implementation of the wasi-http API.
3
4use crate::{
5    bindings::http::types::{self, ErrorCode, Method, Scheme},
6    body::{HostIncomingBody, HyperIncomingBody, HyperOutgoingBody},
7};
8use bytes::Bytes;
9use http::header::{HeaderMap, HeaderName, HeaderValue};
10use http_body_util::BodyExt;
11use hyper::body::Body;
12use std::any::Any;
13use std::fmt;
14use std::time::Duration;
15use wasmtime::component::{Resource, ResourceTable};
16use wasmtime::{Result, bail};
17use wasmtime_wasi::p2::Pollable;
18use wasmtime_wasi::runtime::AbortOnDropJoinHandle;
19
20#[cfg(feature = "default-send-request")]
21use {
22    crate::io::TokioIo,
23    crate::{error::dns_error, hyper_request_error},
24    tokio::net::TcpStream,
25    tokio::time::timeout,
26};
27
28/// Default maximum size for the contents of a fields resource.
29///
30/// Typically, HTTP proxies limit headers to 8k. This number is higher than that
31/// because it not only includes the wire-size of headers but it additionally
32/// includes factors for the in-memory representation of `HeaderMap`. This is in
33/// theory high enough that no one runs into it but low enough such that a
34/// completely full `HeaderMap` doesn't break the bank in terms of memory
35/// consumption.
36const DEFAULT_FIELD_SIZE_LIMIT: usize = 128 * 1024;
37
38/// Capture the state necessary for use in the wasi-http API implementation.
39#[derive(Debug)]
40pub struct WasiHttpCtx {
41    pub(crate) field_size_limit: usize,
42}
43
44impl WasiHttpCtx {
45    /// Create a new context.
46    pub fn new() -> Self {
47        Self {
48            field_size_limit: DEFAULT_FIELD_SIZE_LIMIT,
49        }
50    }
51
52    /// Set the maximum size for any fields resources created by this context.
53    ///
54    /// The limit specified here is roughly a byte limit for the size of the
55    /// in-memory representation of headers. This means that the limit needs to
56    /// be larger than the literal representation of headers on the wire to
57    /// account for in-memory Rust-side data structures representing the header
58    /// names/values/etc.
59    pub fn set_field_size_limit(&mut self, limit: usize) {
60        self.field_size_limit = limit;
61    }
62}
63
64/// A trait which provides internal WASI HTTP state.
65///
66/// # Example
67///
68/// ```
69/// use wasmtime::component::ResourceTable;
70/// use wasmtime_wasi::{WasiCtx, WasiCtxView, WasiView};
71/// use wasmtime_wasi_http::{WasiHttpCtx, WasiHttpView};
72///
73/// struct MyState {
74///     ctx: WasiCtx,
75///     http_ctx: WasiHttpCtx,
76///     table: ResourceTable,
77/// }
78///
79/// impl WasiHttpView for MyState {
80///     fn ctx(&mut self) -> &mut WasiHttpCtx { &mut self.http_ctx }
81///     fn table(&mut self) -> &mut ResourceTable { &mut self.table }
82/// }
83///
84/// impl WasiView for MyState {
85///     fn ctx(&mut self) -> WasiCtxView<'_> {
86///         WasiCtxView { ctx: &mut self.ctx, table: &mut self.table }
87///     }
88/// }
89///
90/// impl MyState {
91///     fn new() -> MyState {
92///         let mut wasi = WasiCtx::builder();
93///         wasi.arg("./foo.wasm");
94///         wasi.arg("--help");
95///         wasi.env("FOO", "bar");
96///
97///         MyState {
98///             ctx: wasi.build(),
99///             table: ResourceTable::new(),
100///             http_ctx: WasiHttpCtx::new(),
101///         }
102///     }
103/// }
104/// ```
105pub trait WasiHttpView {
106    /// Returns a mutable reference to the WASI HTTP context.
107    fn ctx(&mut self) -> &mut WasiHttpCtx;
108
109    /// Returns the table used to manage resources.
110    fn table(&mut self) -> &mut ResourceTable;
111
112    /// Create a new incoming request resource.
113    fn new_incoming_request<B>(
114        &mut self,
115        scheme: Scheme,
116        req: hyper::Request<B>,
117    ) -> wasmtime::Result<Resource<HostIncomingRequest>>
118    where
119        B: Body<Data = Bytes> + Send + 'static,
120        B::Error: Into<ErrorCode>,
121        Self: Sized,
122    {
123        let field_size_limit = self.ctx().field_size_limit;
124        let (parts, body) = req.into_parts();
125        let body = body.map_err(Into::into).boxed_unsync();
126        let body = HostIncomingBody::new(
127            body,
128            // TODO: this needs to be plumbed through
129            std::time::Duration::from_millis(600 * 1000),
130            field_size_limit,
131        );
132        let incoming_req =
133            HostIncomingRequest::new(self, parts, scheme, Some(body), field_size_limit)?;
134        Ok(self.table().push(incoming_req)?)
135    }
136
137    /// Create a new outgoing response resource.
138    fn new_response_outparam(
139        &mut self,
140        result: tokio::sync::oneshot::Sender<
141            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
142        >,
143    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
144        let id = self.table().push(HostResponseOutparam { result })?;
145        Ok(id)
146    }
147
148    /// Send an outgoing request.
149    #[cfg(feature = "default-send-request")]
150    fn send_request(
151        &mut self,
152        request: hyper::Request<HyperOutgoingBody>,
153        config: OutgoingRequestConfig,
154    ) -> crate::HttpResult<HostFutureIncomingResponse> {
155        Ok(default_send_request(request, config))
156    }
157
158    /// Send an outgoing request.
159    #[cfg(not(feature = "default-send-request"))]
160    fn send_request(
161        &mut self,
162        request: hyper::Request<HyperOutgoingBody>,
163        config: OutgoingRequestConfig,
164    ) -> crate::HttpResult<HostFutureIncomingResponse>;
165
166    /// Whether a given header should be considered forbidden and not allowed.
167    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
168        DEFAULT_FORBIDDEN_HEADERS.contains(name)
169    }
170
171    /// Number of distinct write calls to the outgoing body's output-stream
172    /// that the implementation will buffer.
173    /// Default: 1.
174    fn outgoing_body_buffer_chunks(&mut self) -> usize {
175        DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS
176    }
177
178    /// Maximum size allowed in a write call to the outgoing body's output-stream.
179    /// Default: 1024 * 1024.
180    fn outgoing_body_chunk_size(&mut self) -> usize {
181        DEFAULT_OUTGOING_BODY_CHUNK_SIZE
182    }
183}
184
185/// The default value configured for [`WasiHttpView::outgoing_body_buffer_chunks`] in [`WasiHttpView`].
186pub const DEFAULT_OUTGOING_BODY_BUFFER_CHUNKS: usize = 1;
187/// The default value configured for [`WasiHttpView::outgoing_body_chunk_size`] in [`WasiHttpView`].
188pub const DEFAULT_OUTGOING_BODY_CHUNK_SIZE: usize = 1024 * 1024;
189
190impl<T: ?Sized + WasiHttpView> WasiHttpView for &mut T {
191    fn ctx(&mut self) -> &mut WasiHttpCtx {
192        T::ctx(self)
193    }
194
195    fn table(&mut self) -> &mut ResourceTable {
196        T::table(self)
197    }
198
199    fn new_response_outparam(
200        &mut self,
201        result: tokio::sync::oneshot::Sender<
202            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
203        >,
204    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
205        T::new_response_outparam(self, result)
206    }
207
208    fn send_request(
209        &mut self,
210        request: hyper::Request<HyperOutgoingBody>,
211        config: OutgoingRequestConfig,
212    ) -> crate::HttpResult<HostFutureIncomingResponse> {
213        T::send_request(self, request, config)
214    }
215
216    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
217        T::is_forbidden_header(self, name)
218    }
219
220    fn outgoing_body_buffer_chunks(&mut self) -> usize {
221        T::outgoing_body_buffer_chunks(self)
222    }
223
224    fn outgoing_body_chunk_size(&mut self) -> usize {
225        T::outgoing_body_chunk_size(self)
226    }
227}
228
229impl<T: ?Sized + WasiHttpView> WasiHttpView for Box<T> {
230    fn ctx(&mut self) -> &mut WasiHttpCtx {
231        T::ctx(self)
232    }
233
234    fn table(&mut self) -> &mut ResourceTable {
235        T::table(self)
236    }
237
238    fn new_response_outparam(
239        &mut self,
240        result: tokio::sync::oneshot::Sender<
241            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
242        >,
243    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
244        T::new_response_outparam(self, result)
245    }
246
247    fn send_request(
248        &mut self,
249        request: hyper::Request<HyperOutgoingBody>,
250        config: OutgoingRequestConfig,
251    ) -> crate::HttpResult<HostFutureIncomingResponse> {
252        T::send_request(self, request, config)
253    }
254
255    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
256        T::is_forbidden_header(self, name)
257    }
258
259    fn outgoing_body_buffer_chunks(&mut self) -> usize {
260        T::outgoing_body_buffer_chunks(self)
261    }
262
263    fn outgoing_body_chunk_size(&mut self) -> usize {
264        T::outgoing_body_chunk_size(self)
265    }
266}
267
268/// A concrete structure that all generated `Host` traits are implemented for.
269///
270/// This type serves as a small newtype wrapper to implement all of the `Host`
271/// traits for `wasi:http`. This type is internally used and is only needed if
272/// you're interacting with `add_to_linker` functions generated by bindings
273/// themselves (or `add_to_linker_get_host`).
274///
275/// This type is automatically used when using
276/// [`add_to_linker_async`](crate::add_to_linker_async)
277/// or
278/// [`add_to_linker_sync`](crate::add_to_linker_sync)
279/// and doesn't need to be manually configured.
280#[repr(transparent)]
281pub struct WasiHttpImpl<T>(pub T);
282
283impl<T: WasiHttpView> WasiHttpView for WasiHttpImpl<T> {
284    fn ctx(&mut self) -> &mut WasiHttpCtx {
285        self.0.ctx()
286    }
287
288    fn table(&mut self) -> &mut ResourceTable {
289        self.0.table()
290    }
291
292    fn new_response_outparam(
293        &mut self,
294        result: tokio::sync::oneshot::Sender<
295            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
296        >,
297    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
298        self.0.new_response_outparam(result)
299    }
300
301    fn send_request(
302        &mut self,
303        request: hyper::Request<HyperOutgoingBody>,
304        config: OutgoingRequestConfig,
305    ) -> crate::HttpResult<HostFutureIncomingResponse> {
306        self.0.send_request(request, config)
307    }
308
309    fn is_forbidden_header(&mut self, name: &HeaderName) -> bool {
310        self.0.is_forbidden_header(name)
311    }
312
313    fn outgoing_body_buffer_chunks(&mut self) -> usize {
314        self.0.outgoing_body_buffer_chunks()
315    }
316
317    fn outgoing_body_chunk_size(&mut self) -> usize {
318        self.0.outgoing_body_chunk_size()
319    }
320}
321
322/// Set of [http::header::HeaderName], that are forbidden by default
323/// for requests and responses originating in the guest.
324pub const DEFAULT_FORBIDDEN_HEADERS: [http::header::HeaderName; 9] = [
325    hyper::header::CONNECTION,
326    HeaderName::from_static("keep-alive"),
327    hyper::header::PROXY_AUTHENTICATE,
328    hyper::header::PROXY_AUTHORIZATION,
329    HeaderName::from_static("proxy-connection"),
330    hyper::header::TRANSFER_ENCODING,
331    hyper::header::UPGRADE,
332    hyper::header::HOST,
333    HeaderName::from_static("http2-settings"),
334];
335
336/// Removes forbidden headers from a [`FieldMap`].
337pub(crate) fn remove_forbidden_headers(view: &mut dyn WasiHttpView, headers: &mut FieldMap) {
338    let forbidden_keys = Vec::from_iter(headers.as_ref().keys().filter_map(|name| {
339        if view.is_forbidden_header(name) {
340            Some(name.clone())
341        } else {
342            None
343        }
344    }));
345
346    for name in forbidden_keys {
347        headers.remove_all(&name);
348    }
349}
350
351/// Configuration for an outgoing request.
352pub struct OutgoingRequestConfig {
353    /// Whether to use TLS for the request.
354    pub use_tls: bool,
355    /// The timeout for connecting.
356    pub connect_timeout: Duration,
357    /// The timeout until the first byte.
358    pub first_byte_timeout: Duration,
359    /// The timeout between chunks of a streaming body
360    pub between_bytes_timeout: Duration,
361}
362
363/// The default implementation of how an outgoing request is sent.
364///
365/// This implementation is used by the `wasi:http/outgoing-handler` interface
366/// default implementation.
367#[cfg(feature = "default-send-request")]
368pub fn default_send_request(
369    request: hyper::Request<HyperOutgoingBody>,
370    config: OutgoingRequestConfig,
371) -> HostFutureIncomingResponse {
372    let handle = wasmtime_wasi::runtime::spawn(async move {
373        Ok(default_send_request_handler(request, config).await)
374    });
375    HostFutureIncomingResponse::pending(handle)
376}
377
378/// The underlying implementation of how an outgoing request is sent. This should likely be spawned
379/// in a task.
380///
381/// This is called from [default_send_request] to actually send the request.
382#[cfg(feature = "default-send-request")]
383pub async fn default_send_request_handler(
384    mut request: hyper::Request<HyperOutgoingBody>,
385    OutgoingRequestConfig {
386        use_tls,
387        connect_timeout,
388        first_byte_timeout,
389        between_bytes_timeout,
390    }: OutgoingRequestConfig,
391) -> Result<IncomingResponse, types::ErrorCode> {
392    let authority = if let Some(authority) = request.uri().authority() {
393        if authority.port().is_some() {
394            authority.to_string()
395        } else {
396            let port = if use_tls { 443 } else { 80 };
397            format!("{}:{port}", authority.to_string())
398        }
399    } else {
400        return Err(types::ErrorCode::HttpRequestUriInvalid);
401    };
402    let tcp_stream = timeout(connect_timeout, TcpStream::connect(&authority))
403        .await
404        .map_err(|_| types::ErrorCode::ConnectionTimeout)?
405        .map_err(|e| match e.kind() {
406            std::io::ErrorKind::AddrNotAvailable => {
407                dns_error("address not available".to_string(), 0)
408            }
409
410            _ => {
411                if e.to_string()
412                    .starts_with("failed to lookup address information")
413                {
414                    dns_error("address not available".to_string(), 0)
415                } else {
416                    types::ErrorCode::ConnectionRefused
417                }
418            }
419        })?;
420
421    let (mut sender, worker) = if use_tls {
422        use rustls::pki_types::ServerName;
423
424        // derived from https://github.com/rustls/rustls/blob/main/examples/src/bin/simpleclient.rs
425        let root_cert_store = rustls::RootCertStore {
426            roots: webpki_roots::TLS_SERVER_ROOTS.into(),
427        };
428        let config = rustls::ClientConfig::builder()
429            .with_root_certificates(root_cert_store)
430            .with_no_client_auth();
431        let connector = tokio_rustls::TlsConnector::from(std::sync::Arc::new(config));
432        let mut parts = authority.split(":");
433        let host = parts.next().unwrap_or(&authority);
434        let domain = ServerName::try_from(host)
435            .map_err(|e| {
436                tracing::warn!("dns lookup error: {e:?}");
437                dns_error("invalid dns name".to_string(), 0)
438            })?
439            .to_owned();
440        let stream = connector.connect(domain, tcp_stream).await.map_err(|e| {
441            tracing::warn!("tls protocol error: {e:?}");
442            types::ErrorCode::TlsProtocolError
443        })?;
444        let stream = TokioIo::new(stream);
445
446        let (sender, conn) = timeout(
447            connect_timeout,
448            hyper::client::conn::http1::handshake(stream),
449        )
450        .await
451        .map_err(|_| types::ErrorCode::ConnectionTimeout)?
452        .map_err(hyper_request_error)?;
453
454        let worker = wasmtime_wasi::runtime::spawn(async move {
455            match conn.await {
456                Ok(()) => {}
457                // TODO: shouldn't throw away this error and ideally should
458                // surface somewhere.
459                Err(e) => tracing::warn!("dropping error {e}"),
460            }
461        });
462
463        (sender, worker)
464    } else {
465        let tcp_stream = TokioIo::new(tcp_stream);
466        let (sender, conn) = timeout(
467            connect_timeout,
468            // TODO: we should plumb the builder through the http context, and use it here
469            hyper::client::conn::http1::handshake(tcp_stream),
470        )
471        .await
472        .map_err(|_| types::ErrorCode::ConnectionTimeout)?
473        .map_err(hyper_request_error)?;
474
475        let worker = wasmtime_wasi::runtime::spawn(async move {
476            match conn.await {
477                Ok(()) => {}
478                // TODO: same as above, shouldn't throw this error away.
479                Err(e) => tracing::warn!("dropping error {e}"),
480            }
481        });
482
483        (sender, worker)
484    };
485
486    // at this point, the request contains the scheme and the authority, but
487    // the http packet should only include those if addressing a proxy, so
488    // remove them here, since SendRequest::send_request does not do it for us
489    *request.uri_mut() = http::Uri::builder()
490        .path_and_query(
491            request
492                .uri()
493                .path_and_query()
494                .map(|p| p.as_str())
495                .unwrap_or("/"),
496        )
497        .build()
498        .expect("comes from valid request");
499
500    let resp = timeout(first_byte_timeout, sender.send_request(request))
501        .await
502        .map_err(|_| types::ErrorCode::ConnectionReadTimeout)?
503        .map_err(hyper_request_error)?
504        .map(|body| body.map_err(hyper_request_error).boxed_unsync());
505
506    Ok(IncomingResponse {
507        resp,
508        worker: Some(worker),
509        between_bytes_timeout,
510    })
511}
512
513impl From<http::Method> for types::Method {
514    fn from(method: http::Method) -> Self {
515        if method == http::Method::GET {
516            types::Method::Get
517        } else if method == hyper::Method::HEAD {
518            types::Method::Head
519        } else if method == hyper::Method::POST {
520            types::Method::Post
521        } else if method == hyper::Method::PUT {
522            types::Method::Put
523        } else if method == hyper::Method::DELETE {
524            types::Method::Delete
525        } else if method == hyper::Method::CONNECT {
526            types::Method::Connect
527        } else if method == hyper::Method::OPTIONS {
528            types::Method::Options
529        } else if method == hyper::Method::TRACE {
530            types::Method::Trace
531        } else if method == hyper::Method::PATCH {
532            types::Method::Patch
533        } else {
534            types::Method::Other(method.to_string())
535        }
536    }
537}
538
539impl TryInto<http::Method> for types::Method {
540    type Error = http::method::InvalidMethod;
541
542    fn try_into(self) -> Result<http::Method, Self::Error> {
543        match self {
544            Method::Get => Ok(http::Method::GET),
545            Method::Head => Ok(http::Method::HEAD),
546            Method::Post => Ok(http::Method::POST),
547            Method::Put => Ok(http::Method::PUT),
548            Method::Delete => Ok(http::Method::DELETE),
549            Method::Connect => Ok(http::Method::CONNECT),
550            Method::Options => Ok(http::Method::OPTIONS),
551            Method::Trace => Ok(http::Method::TRACE),
552            Method::Patch => Ok(http::Method::PATCH),
553            Method::Other(s) => http::Method::from_bytes(s.as_bytes()),
554        }
555    }
556}
557
558/// The concrete type behind a `wasi:http/types.incoming-request` resource.
559#[derive(Debug)]
560pub struct HostIncomingRequest {
561    pub(crate) method: http::method::Method,
562    pub(crate) uri: http::uri::Uri,
563    pub(crate) headers: FieldMap,
564    pub(crate) scheme: Scheme,
565    pub(crate) authority: String,
566    /// The body of the incoming request.
567    pub body: Option<HostIncomingBody>,
568}
569
570impl HostIncomingRequest {
571    /// Create a new `HostIncomingRequest`.
572    pub fn new(
573        view: &mut dyn WasiHttpView,
574        parts: http::request::Parts,
575        scheme: Scheme,
576        body: Option<HostIncomingBody>,
577        field_size_limit: usize,
578    ) -> wasmtime::Result<Self> {
579        let authority = match parts.uri.authority() {
580            Some(authority) => authority.to_string(),
581            None => match parts.headers.get(http::header::HOST) {
582                Some(host) => host.to_str()?.to_string(),
583                None => bail!("invalid HTTP request missing authority in URI and host header"),
584            },
585        };
586
587        let mut headers = FieldMap::new(parts.headers, field_size_limit);
588        remove_forbidden_headers(view, &mut headers);
589
590        Ok(Self {
591            method: parts.method,
592            uri: parts.uri,
593            headers,
594            authority,
595            scheme,
596            body,
597        })
598    }
599}
600
601/// The concrete type behind a `wasi:http/types.response-outparam` resource.
602pub struct HostResponseOutparam {
603    /// The sender for sending a response.
604    pub result:
605        tokio::sync::oneshot::Sender<Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>>,
606}
607
608/// The concrete type behind a `wasi:http/types.outgoing-response` resource.
609pub struct HostOutgoingResponse {
610    /// The status of the response.
611    pub status: http::StatusCode,
612    /// The headers of the response.
613    pub headers: FieldMap,
614    /// The body of the response.
615    pub body: Option<HyperOutgoingBody>,
616}
617
618impl TryFrom<HostOutgoingResponse> for hyper::Response<HyperOutgoingBody> {
619    type Error = http::Error;
620
621    fn try_from(
622        resp: HostOutgoingResponse,
623    ) -> Result<hyper::Response<HyperOutgoingBody>, Self::Error> {
624        use http_body_util::Empty;
625
626        let mut builder = hyper::Response::builder().status(resp.status);
627
628        *builder.headers_mut().unwrap() = resp.headers.map;
629
630        match resp.body {
631            Some(body) => builder.body(body),
632            None => builder.body(
633                Empty::<bytes::Bytes>::new()
634                    .map_err(|_| unreachable!("Infallible error"))
635                    .boxed_unsync(),
636            ),
637        }
638    }
639}
640
641/// The concrete type behind a `wasi:http/types.outgoing-request` resource.
642#[derive(Debug)]
643pub struct HostOutgoingRequest {
644    /// The method of the request.
645    pub method: Method,
646    /// The scheme of the request.
647    pub scheme: Option<Scheme>,
648    /// The authority of the request.
649    pub authority: Option<String>,
650    /// The path and query of the request.
651    pub path_with_query: Option<String>,
652    /// The request headers.
653    pub headers: FieldMap,
654    /// The request body.
655    pub body: Option<HyperOutgoingBody>,
656}
657
658/// The concrete type behind a `wasi:http/types.request-options` resource.
659#[derive(Debug, Default)]
660pub struct HostRequestOptions {
661    /// How long to wait for a connection to be established.
662    pub connect_timeout: Option<std::time::Duration>,
663    /// How long to wait for the first byte of the response body.
664    pub first_byte_timeout: Option<std::time::Duration>,
665    /// How long to wait between frames of the response body.
666    pub between_bytes_timeout: Option<std::time::Duration>,
667}
668
669/// The concrete type behind a `wasi:http/types.incoming-response` resource.
670#[derive(Debug)]
671pub struct HostIncomingResponse {
672    /// The response status
673    pub status: u16,
674    /// The response headers
675    pub headers: FieldMap,
676    /// The response body
677    pub body: Option<HostIncomingBody>,
678}
679
680/// The concrete type behind a `wasi:http/types.fields` resource.
681#[derive(Debug)]
682pub enum HostFields {
683    /// A reference to the fields of a parent entry.
684    Ref {
685        /// The parent resource rep.
686        parent: u32,
687
688        /// The function to get the fields from the parent.
689        // NOTE: there's not failure in the result here because we assume that HostFields will
690        // always be registered as a child of the entry with the `parent` id. This ensures that the
691        // entry will always exist while this `HostFields::Ref` entry exists in the table, thus we
692        // don't need to account for failure when fetching the fields ref from the parent.
693        get_fields: for<'a> fn(elem: &'a mut (dyn Any + 'static)) -> &'a mut FieldMap,
694    },
695    /// An owned version of the fields.
696    Owned {
697        /// The fields themselves.
698        fields: FieldMap,
699    },
700}
701
702/// An owned version of `HostFields`. A wrapper on http `HeaderMap` that
703/// keeps a running tally of memory consumed by header names and values.
704#[derive(Debug, Clone)]
705pub struct FieldMap {
706    map: HeaderMap,
707    limit: usize,
708    size: usize,
709}
710
711/// Error given when a `FieldMap` has exceeded the size limit.
712#[derive(Debug)]
713pub struct FieldSizeLimitError {
714    /// The erroring `FieldMap` operation would require this content size
715    pub(crate) size: usize,
716    /// The limit set on `FieldMap` content size
717    pub(crate) limit: usize,
718}
719impl fmt::Display for FieldSizeLimitError {
720    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
721        write!(f, "Field size limit {} exceeded: {}", self.limit, self.size)
722    }
723}
724impl std::error::Error for FieldSizeLimitError {}
725
726impl FieldMap {
727    /// Construct a `FieldMap` from a `HeaderMap` and a size limit.
728    ///
729    /// Construction with a `HeaderMap` which exceeds the size limit is
730    /// allowed, but subsequent operations to expand the resource use will
731    /// fail.
732    pub fn new(map: HeaderMap, limit: usize) -> Self {
733        let size = Self::content_size(&map);
734        Self { map, size, limit }
735    }
736    /// Construct an empty `FieldMap`
737    pub fn empty(limit: usize) -> Self {
738        Self {
739            map: HeaderMap::new(),
740            size: 0,
741            limit,
742        }
743    }
744    /// Get the `HeaderMap` out of the `FieldMap`
745    pub fn into_inner(self) -> HeaderMap {
746        self.map
747    }
748    /// Calculate the content size of a `HeaderMap`. This is a sum of the size
749    /// of all of the keys and all of the values.
750    pub(crate) fn content_size(map: &HeaderMap) -> usize {
751        let mut sum = 0;
752        for key in map.keys() {
753            sum += header_name_size(key);
754        }
755        for value in map.values() {
756            sum += header_value_size(value);
757        }
758        sum
759    }
760    /// Remove all values associated with a key in a map.
761    ///
762    /// Returns an empty list if the key is not already present within the map.
763    pub fn remove_all(&mut self, key: &HeaderName) -> Vec<HeaderValue> {
764        use http::header::Entry;
765        match self.map.try_entry(key) {
766            Ok(Entry::Vacant { .. }) | Err(_) => Vec::new(),
767            Ok(Entry::Occupied(e)) => {
768                let (name, value_drain) = e.remove_entry_mult();
769                let mut removed = header_name_size(&name);
770                let values = value_drain.collect::<Vec<_>>();
771                for v in values.iter() {
772                    removed += header_value_size(v);
773                }
774                self.size -= removed;
775                values
776            }
777        }
778    }
779    /// Add a value associated with a key to the map.
780    ///
781    /// If `key` is already present within the map then `value` is appended to
782    /// the list of values it already has.
783    pub fn append(&mut self, key: &HeaderName, value: HeaderValue) -> Result<bool> {
784        let key_size = header_name_size(key);
785        let val_size = header_value_size(&value);
786        let new_size = if !self.map.contains_key(key) {
787            self.size + key_size + val_size
788        } else {
789            self.size + val_size
790        };
791        if new_size > self.limit {
792            bail!(FieldSizeLimitError {
793                limit: self.limit,
794                size: new_size
795            })
796        }
797        self.size = new_size;
798        Ok(self.map.try_append(key, value)?)
799    }
800}
801
802/// Returns the size, in accounting cost, to consider for `name`.
803///
804/// This includes both the byte length of the `name` itself as well as the size
805/// of the data structure itself as it'll reside within a `HeaderMap`.
806fn header_name_size(name: &HeaderName) -> usize {
807    name.as_str().len() + size_of::<HeaderName>()
808}
809
810/// Same as `header_name_size`, but for values.
811///
812/// This notably includes the size of `HeaderValue` itself to ensure that all
813/// headers have a nonzero size as otherwise this would never limit addition of
814/// an empty header value.
815fn header_value_size(value: &HeaderValue) -> usize {
816    value.len() + size_of::<HeaderValue>()
817}
818
819// We impl AsRef, but not AsMut, because any modifications of the
820// underlying HeaderMap must account for changes in size
821impl AsRef<HeaderMap> for FieldMap {
822    fn as_ref(&self) -> &HeaderMap {
823        &self.map
824    }
825}
826
827/// A handle to a future incoming response.
828pub type FutureIncomingResponseHandle =
829    AbortOnDropJoinHandle<wasmtime::Result<Result<IncomingResponse, types::ErrorCode>>>;
830
831/// A response that is in the process of being received.
832#[derive(Debug)]
833pub struct IncomingResponse {
834    /// The response itself.
835    pub resp: hyper::Response<HyperIncomingBody>,
836    /// Optional worker task that continues to process the response.
837    pub worker: Option<AbortOnDropJoinHandle<()>>,
838    /// The timeout between chunks of the response.
839    pub between_bytes_timeout: std::time::Duration,
840}
841
842/// The concrete type behind a `wasi:http/types.future-incoming-response` resource.
843#[derive(Debug)]
844pub enum HostFutureIncomingResponse {
845    /// A pending response
846    Pending(FutureIncomingResponseHandle),
847    /// The response is ready.
848    ///
849    /// An outer error will trap while the inner error gets returned to the guest.
850    Ready(wasmtime::Result<Result<IncomingResponse, types::ErrorCode>>),
851    /// The response has been consumed.
852    Consumed,
853}
854
855impl HostFutureIncomingResponse {
856    /// Create a new `HostFutureIncomingResponse` that is pending on the provided task handle.
857    pub fn pending(handle: FutureIncomingResponseHandle) -> Self {
858        Self::Pending(handle)
859    }
860
861    /// Create a new `HostFutureIncomingResponse` that is ready.
862    pub fn ready(result: wasmtime::Result<Result<IncomingResponse, types::ErrorCode>>) -> Self {
863        Self::Ready(result)
864    }
865
866    /// Returns `true` if the response is ready.
867    pub fn is_ready(&self) -> bool {
868        matches!(self, Self::Ready(_))
869    }
870
871    /// Unwrap the response, panicking if it is not ready.
872    pub fn unwrap_ready(self) -> wasmtime::Result<Result<IncomingResponse, types::ErrorCode>> {
873        match self {
874            Self::Ready(res) => res,
875            Self::Pending(_) | Self::Consumed => {
876                panic!("unwrap_ready called on a pending HostFutureIncomingResponse")
877            }
878        }
879    }
880}
881
882#[async_trait::async_trait]
883impl Pollable for HostFutureIncomingResponse {
884    async fn ready(&mut self) {
885        if let Self::Pending(handle) = self {
886            *self = Self::Ready(handle.await);
887        }
888    }
889}