Skip to main content

wasmtime_wasi_http/p2/
http_impl.rs

1//! Implementation of the `wasi:http/outgoing-handler` interface.
2
3use crate::WasiHttpCtxView;
4use crate::p2::{
5    HttpResult,
6    bindings::http::{
7        outgoing_handler,
8        types::{self, Scheme},
9    },
10    error::internal_error,
11    http_request_error,
12    types::{HostFutureIncomingResponse, HostOutgoingRequest},
13};
14use bytes::Bytes;
15use http_body_util::{BodyExt, Empty};
16use hyper::Method;
17use std::pin::Pin;
18use wasmtime::component::Resource;
19
20impl outgoing_handler::Host for WasiHttpCtxView<'_> {
21    fn handle(
22        &mut self,
23        request_id: Resource<HostOutgoingRequest>,
24        options: Option<Resource<types::RequestOptions>>,
25    ) -> HttpResult<Resource<HostFutureIncomingResponse>> {
26        let opts = options.and_then(|opts| self.table.get(&opts).ok()).cloned();
27
28        let req = self.table.delete(request_id)?;
29        let mut builder = hyper::Request::builder();
30
31        builder = builder.method(match req.method {
32            types::Method::Get => Method::GET,
33            types::Method::Head => Method::HEAD,
34            types::Method::Post => Method::POST,
35            types::Method::Put => Method::PUT,
36            types::Method::Delete => Method::DELETE,
37            types::Method::Connect => Method::CONNECT,
38            types::Method::Options => Method::OPTIONS,
39            types::Method::Trace => Method::TRACE,
40            types::Method::Patch => Method::PATCH,
41            types::Method::Other(m) => match hyper::Method::from_bytes(m.as_bytes()) {
42                Ok(method) => method,
43                Err(_) => return Err(types::ErrorCode::HttpRequestMethodInvalid.into()),
44            },
45        });
46
47        let scheme = match req.scheme {
48            Some(scheme) => {
49                let scheme = match scheme {
50                    Scheme::Http => http::uri::Scheme::HTTP,
51                    Scheme::Https => http::uri::Scheme::HTTPS,
52                    Scheme::Other(scheme) => http::uri::Scheme::try_from(scheme.as_str())
53                        .map_err(|_| types::ErrorCode::HttpProtocolError)?,
54                };
55                if !self.hooks.is_supported_scheme(&scheme) {
56                    return Err(types::ErrorCode::HttpProtocolError.into());
57                }
58                scheme
59            }
60            // Note that a hook returning `None` here means that guests are
61            // required to specify a scheme themselves.
62            None => self
63                .hooks
64                .default_scheme()
65                .ok_or(types::ErrorCode::HttpProtocolError)?,
66        };
67
68        let authority = req.authority.unwrap_or_else(String::new);
69
70        let mut uri = http::Uri::builder()
71            .scheme(scheme)
72            .authority(authority.clone());
73
74        if let Some(path) = req.path_with_query {
75            uri = uri.path_and_query(path);
76        }
77
78        builder = builder.uri(uri.build().map_err(http_request_error)?);
79
80        if self.hooks.set_host_header() {
81            builder = builder.header(http::header::HOST, authority.as_str());
82        }
83
84        for (k, v) in req.headers.iter() {
85            builder = builder.header(k, v);
86        }
87
88        let body = req.body.unwrap_or_else(|| {
89            Empty::<Bytes>::new()
90                .map_err(|_| unreachable!("Infallible error"))
91                .boxed_unsync()
92        });
93        let body = body.map_err(Into::into).boxed_unsync();
94
95        let request = builder
96            .body(body)
97            .map_err(|err| internal_error(err.to_string()))?;
98
99        let future = self
100            .hooks
101            .send_request(request, opts, Box::new(async { Ok(()) }));
102        let future = wasmtime_wasi::runtime::spawn(async move {
103            let (res, io) = Pin::from(future).await?;
104            let io = wasmtime_wasi::runtime::spawn(async move {
105                match Pin::from(io).await {
106                    Ok(()) => {}
107                    // TODO: shouldn't throw away this error and ideally should
108                    // surface somewhere.
109                    Err(e) => tracing::warn!("dropping error {e}"),
110                }
111            });
112            let res = res.map(|b| b.boxed_unsync());
113            Ok((res, io))
114        });
115
116        Ok(self
117            .table
118            .push(HostFutureIncomingResponse::Pending(future))?)
119    }
120}