1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use crate::bindings::http::{
    outgoing_handler,
    types::{RequestOptions, Scheme},
};
use crate::types::{self, HostFutureIncomingResponse, OutgoingRequest};
use crate::WasiHttpView;
use bytes::Bytes;
use http_body_util::{BodyExt, Empty};
use hyper::Method;
use std::time::Duration;
use types::HostOutgoingRequest;
use wasmtime::component::Resource;

impl<T: WasiHttpView> outgoing_handler::Host for T {
    fn handle(
        &mut self,
        request_id: Resource<HostOutgoingRequest>,
        options: Option<RequestOptions>,
    ) -> wasmtime::Result<Result<Resource<HostFutureIncomingResponse>, outgoing_handler::Error>>
    {
        let connect_timeout = Duration::from_millis(
            options
                .and_then(|opts| opts.connect_timeout_ms)
                .unwrap_or(600 * 1000) as u64,
        );

        let first_byte_timeout = Duration::from_millis(
            options
                .and_then(|opts| opts.first_byte_timeout_ms)
                .unwrap_or(600 * 1000) as u64,
        );

        let between_bytes_timeout = Duration::from_millis(
            options
                .and_then(|opts| opts.between_bytes_timeout_ms)
                .unwrap_or(600 * 1000) as u64,
        );

        let req = self.table().delete_resource(request_id)?;

        let method = match req.method {
            crate::bindings::http::types::Method::Get => Method::GET,
            crate::bindings::http::types::Method::Head => Method::HEAD,
            crate::bindings::http::types::Method::Post => Method::POST,
            crate::bindings::http::types::Method::Put => Method::PUT,
            crate::bindings::http::types::Method::Delete => Method::DELETE,
            crate::bindings::http::types::Method::Connect => Method::CONNECT,
            crate::bindings::http::types::Method::Options => Method::OPTIONS,
            crate::bindings::http::types::Method::Trace => Method::TRACE,
            crate::bindings::http::types::Method::Patch => Method::PATCH,
            crate::bindings::http::types::Method::Other(method) => {
                return Ok(Err(outgoing_handler::Error::InvalidUrl(format!(
                    "unknown method {method}"
                ))));
            }
        };

        let (use_tls, scheme, port) = match req.scheme.unwrap_or(Scheme::Https) {
            Scheme::Http => (false, "http://", 80),
            Scheme::Https => (true, "https://", 443),
            Scheme::Other(scheme) => {
                return Ok(Err(outgoing_handler::Error::InvalidUrl(format!(
                    "unsupported scheme {scheme}"
                ))))
            }
        };

        let authority = if req.authority.find(':').is_some() {
            req.authority.clone()
        } else {
            format!("{}:{port}", req.authority)
        };

        let mut builder = hyper::Request::builder()
            .method(method)
            .uri(format!("{scheme}{authority}{}", req.path_with_query))
            .header(hyper::header::HOST, &authority);

        for (k, v) in req.headers.iter() {
            builder = builder.header(k, v);
        }

        let body = req.body.unwrap_or_else(|| {
            Empty::<Bytes>::new()
                .map_err(|_| anyhow::anyhow!("empty error"))
                .boxed()
        });

        let request = builder.body(body).map_err(types::http_protocol_error)?;
        Ok(Ok(self.send_request(OutgoingRequest {
            use_tls,
            authority,
            request,
            connect_timeout,
            first_byte_timeout,
            between_bytes_timeout,
        })?))
    }
}