Skip to main content

wasmtime_wasi_http/p2/
http_impl.rs

1//! Implementation of the `wasi:http/outgoing-handler` interface.
2
3use crate::p2::{
4    HttpResult, WasiHttpCtxView,
5    bindings::http::{
6        outgoing_handler,
7        types::{self, Scheme},
8    },
9    error::internal_error,
10    http_request_error,
11    types::{HostFutureIncomingResponse, HostOutgoingRequest, OutgoingRequestConfig},
12};
13use bytes::Bytes;
14use http_body_util::{BodyExt, Empty};
15use hyper::Method;
16use wasmtime::component::Resource;
17
18impl outgoing_handler::Host for WasiHttpCtxView<'_> {
19    fn handle(
20        &mut self,
21        request_id: Resource<HostOutgoingRequest>,
22        options: Option<Resource<types::RequestOptions>>,
23    ) -> HttpResult<Resource<HostFutureIncomingResponse>> {
24        let opts = options.and_then(|opts| self.table.get(&opts).ok());
25
26        let connect_timeout = opts
27            .and_then(|opts| opts.connect_timeout)
28            .unwrap_or(std::time::Duration::from_secs(600));
29
30        let first_byte_timeout = opts
31            .and_then(|opts| opts.first_byte_timeout)
32            .unwrap_or(std::time::Duration::from_secs(600));
33
34        let between_bytes_timeout = opts
35            .and_then(|opts| opts.between_bytes_timeout)
36            .unwrap_or(std::time::Duration::from_secs(600));
37
38        let req = self.table.delete(request_id)?;
39        let mut builder = hyper::Request::builder();
40
41        builder = builder.method(match req.method {
42            types::Method::Get => Method::GET,
43            types::Method::Head => Method::HEAD,
44            types::Method::Post => Method::POST,
45            types::Method::Put => Method::PUT,
46            types::Method::Delete => Method::DELETE,
47            types::Method::Connect => Method::CONNECT,
48            types::Method::Options => Method::OPTIONS,
49            types::Method::Trace => Method::TRACE,
50            types::Method::Patch => Method::PATCH,
51            types::Method::Other(m) => match hyper::Method::from_bytes(m.as_bytes()) {
52                Ok(method) => method,
53                Err(_) => return Err(types::ErrorCode::HttpRequestMethodInvalid.into()),
54            },
55        });
56
57        let (use_tls, scheme) = match req.scheme.unwrap_or(Scheme::Https) {
58            Scheme::Http => (false, http::uri::Scheme::HTTP),
59            Scheme::Https => (true, http::uri::Scheme::HTTPS),
60
61            // We can only support http/https
62            Scheme::Other(_) => return Err(types::ErrorCode::HttpProtocolError.into()),
63        };
64
65        let authority = req.authority.unwrap_or_else(String::new);
66
67        builder = builder.header(hyper::header::HOST, &authority);
68
69        let mut uri = http::Uri::builder()
70            .scheme(scheme)
71            .authority(authority.clone());
72
73        if let Some(path) = req.path_with_query {
74            uri = uri.path_and_query(path);
75        }
76
77        builder = builder.uri(uri.build().map_err(http_request_error)?);
78
79        for (k, v) in req.headers.iter() {
80            builder = builder.header(k, v);
81        }
82
83        let body = req.body.unwrap_or_else(|| {
84            Empty::<Bytes>::new()
85                .map_err(|_| unreachable!("Infallible error"))
86                .boxed_unsync()
87        });
88
89        let request = builder
90            .body(body)
91            .map_err(|err| internal_error(err.to_string()))?;
92
93        let future = self.hooks.send_request(
94            request,
95            OutgoingRequestConfig {
96                use_tls,
97                connect_timeout,
98                first_byte_timeout,
99                between_bytes_timeout,
100            },
101        )?;
102
103        Ok(self.table.push(future)?)
104    }
105}