Skip to main content

wasmtime_wasi_http/p2/
types.rs

1//! Implements the base structure that will provide the implementation of the
2//! wasi-http API.
3
4use crate::p2::{
5    bindings::http::types::{self, Method, Scheme},
6    body::{HostIncomingBody, HyperIncomingBody, HyperOutgoingBody},
7};
8use crate::{Error, ErrorResponse, FieldMap, WasiHttpCtxView};
9use bytes::Bytes;
10use http_body_util::BodyExt;
11use hyper::body::Body;
12use wasmtime::Result;
13use wasmtime::component::Resource;
14use wasmtime_wasi::p2::Pollable;
15use wasmtime_wasi::runtime::AbortOnDropJoinHandle;
16
17impl From<http::Method> for types::Method {
18    fn from(method: http::Method) -> Self {
19        if method == http::Method::GET {
20            types::Method::Get
21        } else if method == hyper::Method::HEAD {
22            types::Method::Head
23        } else if method == hyper::Method::POST {
24            types::Method::Post
25        } else if method == hyper::Method::PUT {
26            types::Method::Put
27        } else if method == hyper::Method::DELETE {
28            types::Method::Delete
29        } else if method == hyper::Method::CONNECT {
30            types::Method::Connect
31        } else if method == hyper::Method::OPTIONS {
32            types::Method::Options
33        } else if method == hyper::Method::TRACE {
34            types::Method::Trace
35        } else if method == hyper::Method::PATCH {
36            types::Method::Patch
37        } else {
38            types::Method::Other(method.to_string())
39        }
40    }
41}
42
43impl TryInto<http::Method> for types::Method {
44    type Error = http::method::InvalidMethod;
45
46    fn try_into(self) -> Result<http::Method, Self::Error> {
47        match self {
48            Method::Get => Ok(http::Method::GET),
49            Method::Head => Ok(http::Method::HEAD),
50            Method::Post => Ok(http::Method::POST),
51            Method::Put => Ok(http::Method::PUT),
52            Method::Delete => Ok(http::Method::DELETE),
53            Method::Connect => Ok(http::Method::CONNECT),
54            Method::Options => Ok(http::Method::OPTIONS),
55            Method::Trace => Ok(http::Method::TRACE),
56            Method::Patch => Ok(http::Method::PATCH),
57            Method::Other(s) => http::Method::from_bytes(s.as_bytes()),
58        }
59    }
60}
61
62/// The concrete type behind a `wasi:http/types.incoming-request` resource.
63#[derive(Debug)]
64pub struct HostIncomingRequest {
65    pub(crate) method: http::method::Method,
66    pub(crate) uri: http::uri::Uri,
67    pub(crate) headers: FieldMap,
68    pub(crate) scheme: Scheme,
69    pub(crate) authority: String,
70    /// The body of the incoming request.
71    pub body: Option<HostIncomingBody>,
72}
73
74impl WasiHttpCtxView<'_> {
75    /// Create a new incoming request resource.
76    pub fn new_incoming_request<B>(
77        &mut self,
78        scheme: Scheme,
79        req: hyper::Request<B>,
80    ) -> wasmtime::Result<Resource<HostIncomingRequest>>
81    where
82        B: Body<Data = Bytes> + Send + 'static,
83        B::Error: Into<Error>,
84    {
85        let (parts, body) = req.into_parts();
86        let body = body.map_err(Into::into).boxed_unsync();
87        let body = HostIncomingBody::new(body);
88        let authority = match parts.uri.authority() {
89            Some(authority) => authority.to_string(),
90            None => match parts.headers.get(http::header::HOST) {
91                Some(host) => host.to_str()?.to_string(),
92                None => {
93                    return Err(wasmtime::Error::msg(
94                        "invalid HTTP request missing authority in URI and host header",
95                    )
96                    .context(ErrorResponse::new(http::StatusCode::BAD_REQUEST)));
97                }
98            },
99        };
100
101        let headers = FieldMap::new_immutable(self.hooks, parts.headers);
102
103        let req = HostIncomingRequest {
104            method: parts.method,
105            uri: parts.uri,
106            headers,
107            authority,
108            scheme,
109            body: Some(body),
110        };
111        Ok(self.table.push(req)?)
112    }
113}
114
115/// The concrete type behind a `wasi:http/types.response-outparam` resource.
116pub struct HostResponseOutparam {
117    /// The callback sending a response.
118    pub send:
119        Box<dyn FnOnce(Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>) + Send + Sync>,
120}
121
122impl WasiHttpCtxView<'_> {
123    /// Create a new outgoing response resource.
124    pub fn new_response_outparam(
125        &mut self,
126        result: tokio::sync::oneshot::Sender<
127            Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>,
128        >,
129    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
130        let id = self.table.push(HostResponseOutparam {
131            send: Box::new(move |value| {
132                // Giving the API doesn't return any error, it's probably
133                // better to ignore the error than trap the guest, in case of
134                // host timeout and dropped the receiver side of the channel.
135                // See also: #10784
136                _ = result.send(value)
137            }),
138        })?;
139        Ok(id)
140    }
141
142    /// Create a new outgoing response from an `FnOnce`.
143    pub fn new_response_outparam_from_callback(
144        &mut self,
145        callback: impl FnOnce(Result<hyper::Response<HyperOutgoingBody>, types::ErrorCode>)
146        + Send
147        + Sync
148        + 'static,
149    ) -> wasmtime::Result<Resource<HostResponseOutparam>> {
150        let id = self.table.push(HostResponseOutparam {
151            send: Box::new(callback),
152        })?;
153        Ok(id)
154    }
155}
156
157/// The concrete type behind a `wasi:http/types.outgoing-response` resource.
158pub struct HostOutgoingResponse {
159    /// The status of the response.
160    pub status: http::StatusCode,
161    /// The headers of the response.
162    pub headers: FieldMap,
163    /// The body of the response.
164    pub body: Option<HyperOutgoingBody>,
165}
166
167impl TryFrom<HostOutgoingResponse> for hyper::Response<HyperOutgoingBody> {
168    type Error = http::Error;
169
170    fn try_from(
171        resp: HostOutgoingResponse,
172    ) -> Result<hyper::Response<HyperOutgoingBody>, Self::Error> {
173        use http_body_util::Empty;
174
175        let mut builder = hyper::Response::builder().status(resp.status);
176
177        *builder.headers_mut().unwrap() = resp.headers.into();
178
179        match resp.body {
180            Some(body) => builder.body(body),
181            None => builder.body(
182                Empty::<bytes::Bytes>::new()
183                    .map_err(|_| unreachable!("Infallible error"))
184                    .boxed_unsync(),
185            ),
186        }
187    }
188}
189
190/// The concrete type behind a `wasi:http/types.outgoing-request` resource.
191#[derive(Debug)]
192pub struct HostOutgoingRequest {
193    /// The method of the request.
194    pub method: Method,
195    /// The scheme of the request.
196    pub scheme: Option<Scheme>,
197    /// The authority of the request.
198    pub authority: Option<String>,
199    /// The path and query of the request.
200    pub path_with_query: Option<String>,
201    /// The request headers.
202    pub headers: FieldMap,
203    /// The request body.
204    pub body: Option<HyperOutgoingBody>,
205}
206
207/// The concrete type behind a `wasi:http/types.incoming-response` resource.
208#[derive(Debug)]
209pub struct HostIncomingResponse {
210    /// The response status
211    pub status: u16,
212    /// The response headers
213    pub headers: FieldMap,
214    /// The response body
215    pub body: Option<HostIncomingBody>,
216}
217
218/// A handle to a future incoming response.
219pub type FutureIncomingResponseHandle = AbortOnDropJoinHandle<SendRequestResult>;
220
221/// A response that is in the process of being received.
222#[derive(Debug)]
223pub struct IncomingResponse {
224    /// The response itself.
225    pub resp: hyper::Response<HyperIncomingBody>,
226    /// Optional worker task that continues to process the response.
227    pub worker: Option<AbortOnDropJoinHandle<()>>,
228}
229
230type SendRequestResult =
231    crate::Result<(http::Response<HyperIncomingBody>, AbortOnDropJoinHandle<()>)>;
232
233/// The concrete type behind a `wasi:http/types.future-incoming-response` resource.
234pub enum HostFutureIncomingResponse {
235    /// A pending response
236    Pending(FutureIncomingResponseHandle),
237    /// The response is ready.
238    ///
239    /// An outer error will trap while the inner error gets returned to the guest.
240    Ready(SendRequestResult),
241    /// The response has been consumed.
242    Consumed,
243}
244
245impl HostFutureIncomingResponse {
246    /// Unwrap the response, panicking if it is not ready.
247    pub(crate) fn unwrap_ready(self) -> SendRequestResult {
248        match self {
249            Self::Ready(res) => res,
250            Self::Pending(_) | Self::Consumed => {
251                panic!("unwrap_ready called on a pending HostFutureIncomingResponse")
252            }
253        }
254    }
255}
256
257#[async_trait::async_trait]
258impl Pollable for HostFutureIncomingResponse {
259    async fn ready(&mut self) {
260        if let Self::Pending(handle) = self {
261            *self = Self::Ready(handle.await);
262        }
263    }
264}