wasmtime_wasi_http/p3/
response.rs1use crate::p3::bindings::http::types::ErrorCode;
2use crate::p3::body::{Body, GuestBody};
3use crate::{Error, FieldMap, WasiHttpCtxView, WasiHttpHooks, WasiHttpView, get_content_length};
4use bytes::Bytes;
5use http::StatusCode;
6use http_body_util::BodyExt as _;
7use http_body_util::combinators::UnsyncBoxBody;
8use wasmtime::AsContextMut;
9use wasmtime::error::Context as _;
10
11pub struct Response {
13 pub status: StatusCode,
15 pub headers: FieldMap,
17 pub(crate) body: Body,
19}
20
21impl TryFrom<Response> for http::Response<Body> {
22 type Error = http::Error;
23
24 fn try_from(
25 Response {
26 status,
27 headers,
28 body,
29 }: Response,
30 ) -> Result<Self, Self::Error> {
31 let mut res = http::Response::builder().status(status);
32 *res.headers_mut().unwrap() = headers.into();
33 res.body(body)
34 }
35}
36
37impl Response {
38 pub fn into_http<T: WasiHttpView + 'static>(
45 self,
46 store: impl AsContextMut<Data = T>,
47 fut: impl Future<Output = Result<(), Error>> + Send + 'static,
48 ) -> wasmtime::Result<http::Response<UnsyncBoxBody<Bytes, Error>>> {
49 self.into_http_with_getter(store, fut, T::http)
50 }
51
52 pub fn into_http_with_getter<T: 'static>(
55 self,
56 store: impl AsContextMut<Data = T>,
57 fut: impl Future<Output = Result<(), Error>> + Send + 'static,
58 getter: fn(&mut T) -> WasiHttpCtxView<'_>,
59 ) -> wasmtime::Result<http::Response<UnsyncBoxBody<Bytes, Error>>> {
60 let res = http::Response::try_from(self)?;
61 let (res, body) = res.into_parts();
62 let body = match body {
63 Body::Guest {
64 contents_rx,
65 trailers_rx,
66 result_tx,
67 } => {
68 let content_length =
70 get_content_length(&res.headers).context("failed to parse `content-length`")?;
71 GuestBody::new(
72 store,
73 contents_rx,
74 trailers_rx,
75 result_tx,
76 fut,
77 content_length,
78 ErrorCode::HttpResponseBodySize,
79 getter,
80 )?
81 .boxed_unsync()
82 }
83 Body::Host { body, result_tx } => {
84 _ = result_tx.send(Box::new(fut));
85 body
86 }
87 };
88 Ok(http::Response::from_parts(res, body))
89 }
90
91 pub fn from_http<T>(
93 hooks: &mut dyn WasiHttpHooks,
94 res: http::Response<T>,
95 ) -> (
96 Self,
97 impl Future<Output = Result<(), Error>> + Send + 'static,
98 )
99 where
100 T: http_body::Body<Data = Bytes> + Send + 'static,
101 T::Error: Into<Error>,
102 {
103 let (parts, body) = res.into_parts();
104 let (result_tx, result_rx) = tokio::sync::oneshot::channel();
105
106 let wasi_response = Response {
107 status: parts.status,
108 headers: FieldMap::new_immutable(hooks, parts.headers),
109 body: Body::Host {
110 body: body.map_err(Into::into).boxed_unsync(),
111 result_tx,
112 },
113 };
114
115 let io_future = async {
116 let Ok(fut) = result_rx.await else {
117 return Ok(());
118 };
119 Box::into_pin(fut).await
120 };
121
122 (wasi_response, io_future)
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use crate::default_hooks;
130 use core::future::Future;
131 use core::pin::pin;
132 use core::task::{Context, Poll, Waker};
133 use http_body_util::Full;
134
135 #[tokio::test]
136 async fn test_response_from_http() {
137 let http_response = http::Response::builder()
138 .status(StatusCode::OK)
139 .header("x-custom-header", "value123")
140 .body(Full::new(Bytes::from_static(b"hello wasm")))
141 .unwrap();
142
143 let (wasi_resp, io_future) = Response::from_http(default_hooks(), http_response);
144 assert_eq!(wasi_resp.status, StatusCode::OK);
145 assert_eq!(
146 wasi_resp.headers.get("x-custom-header").unwrap(),
147 "value123"
148 );
149 match wasi_resp.body {
150 Body::Host { body, result_tx } => {
151 let collected = body.collect().await;
152 assert!(collected.is_ok(), "Body stream failed unexpectedly");
153 let chunks = collected.unwrap().to_bytes();
154 assert_eq!(chunks, &b"hello wasm"[..]);
155 _ = result_tx.send(Box::new(async { Ok(()) }));
156 }
157 _ => panic!("Response body should be of type Host"),
158 }
159
160 let mut cx = Context::from_waker(Waker::noop());
161 let result = pin!(io_future).poll(&mut cx);
162 assert!(matches!(result, Poll::Ready(Ok(_))));
163 }
164}