1 use {
2 flate2::{
3 Compression,
4 write::{DeflateDecoder, DeflateEncoder},
5 },
6 std::{io::Write, mem},
7 test_programs::p3::{
8 wasi::http::{
9 handler,
10 types::{ErrorCode, Headers, Request, Response},
11 },
12 wit_future, wit_stream,
13 },
14 wit_bindgen::StreamResult,
15 };
16
17 wit_bindgen::generate!({
18 path: "../wasi-http/src/p3/wit",
19 world: "wasi:http/middleware",
20 with: {
21 "wasi:http/handler@0.3.0-rc-2026-03-15": test_programs::p3::wasi::http::handler,
22 "wasi:http/types@0.3.0-rc-2026-03-15": test_programs::p3::wasi::http::types,
23 "wasi:http/client@0.3.0-rc-2026-03-15": test_programs::p3::wasi::http::client,
24 "wasi:random/random@0.3.0-rc-2026-03-15": test_programs::p3::wasi::random::random,
25 "wasi:random/insecure@0.3.0-rc-2026-03-15": test_programs::p3::wasi::random::insecure,
26 "wasi:random/insecure-seed@0.3.0-rc-2026-03-15": test_programs::p3::wasi::random::insecure_seed,
27 "wasi:cli/stdout@0.3.0-rc-2026-03-15": test_programs::p3::wasi::cli::stdout,
28 "wasi:cli/stderr@0.3.0-rc-2026-03-15": test_programs::p3::wasi::cli::stderr,
29 "wasi:cli/stdin@0.3.0-rc-2026-03-15": test_programs::p3::wasi::cli::stdin,
30 "wasi:cli/types@0.3.0-rc-2026-03-15": test_programs::p3::wasi::cli::types,
31 "wasi:clocks/monotonic-clock@0.3.0-rc-2026-03-15": test_programs::p3::wasi::clocks::monotonic_clock,
32 "wasi:clocks/system-clock@0.3.0-rc-2026-03-15": test_programs::p3::wasi::clocks::system_clock,
33 "wasi:clocks/types@0.3.0-rc-2026-03-15": test_programs::p3::wasi::clocks::types,
34 },
35 });
36
37 struct Component;
38
39 export!(Component);
40
41 impl exports::wasi::http::handler::Guest for Component {
42 /// Forward the specified request to the imported `wasi:http/handler`, transparently decoding the request body
43 /// if it is `deflate`d and then encoding the response body if the client has provided an `accept-encoding:
44 /// deflate` header.
handle(request: Request) -> Result<Response, ErrorCode>45 async fn handle(request: Request) -> Result<Response, ErrorCode> {
46 // First, extract the parts of the request and check for (and remove) headers pertaining to body encodings.
47 let method = request.get_method();
48 let scheme = request.get_scheme();
49 let path_with_query = request.get_path_with_query();
50 let authority = request.get_authority();
51 let mut accept_deflated = false;
52 let mut content_deflated = false;
53 let headers = request.get_headers();
54 let mut headers = headers.copy_all();
55 headers.retain(|(k, v)| match (k.as_str(), v.as_slice()) {
56 ("accept-encoding", value)
57 if std::str::from_utf8(value)
58 .map(|v| v.contains("deflate"))
59 .unwrap_or(false) =>
60 {
61 accept_deflated = true;
62 false
63 }
64 ("content-encoding", b"deflate") => {
65 content_deflated = true;
66 false
67 }
68 _ => true,
69 });
70 let (_, result_rx) = wit_future::new(|| Ok(()));
71 let (mut body, trailers) = Request::consume_body(request, result_rx);
72
73 let (body, trailers) = if content_deflated {
74 // Next, spawn a task to pipe and decode the original request body and trailers into a new request
75 // we'll create below. This will run concurrently with any code in the imported `wasi:http/handler`.
76 let (trailers_tx, trailers_rx) = wit_future::new(|| todo!());
77 let (mut pipe_tx, pipe_rx) = wit_stream::new();
78
79 wit_bindgen::spawn(async move {
80 {
81 let mut decoder = DeflateDecoder::new(Vec::new());
82 let mut status = StreamResult::Complete(0);
83 let mut chunk = Vec::with_capacity(64 * 1024);
84
85 while let StreamResult::Complete(_) = status {
86 (status, chunk) = body.read(chunk).await;
87 decoder.write_all(&chunk).unwrap();
88 let remaining = pipe_tx.write_all(mem::take(decoder.get_mut())).await;
89 assert!(remaining.is_empty());
90 *decoder.get_mut() = remaining;
91 chunk.clear();
92 }
93
94 let remaining = pipe_tx.write_all(decoder.finish().unwrap()).await;
95 assert!(remaining.is_empty());
96
97 drop(pipe_tx);
98 }
99
100 trailers_tx.write(trailers.await).await.unwrap();
101 });
102
103 (pipe_rx, trailers_rx)
104 } else {
105 (body, trailers)
106 };
107
108 // While the above task (if any) is running, synthesize a request from the parts collected above and pass
109 // it to the imported `wasi:http/handler`.
110 let (my_request, _request_complete) = Request::new(
111 Headers::from_list(&headers).unwrap(),
112 Some(body),
113 trailers,
114 None,
115 );
116 my_request.set_method(&method).unwrap();
117 my_request.set_scheme(scheme.as_ref()).unwrap();
118 my_request
119 .set_path_with_query(path_with_query.as_deref())
120 .unwrap();
121 my_request.set_authority(authority.as_deref()).unwrap();
122
123 let response = handler::handle(my_request).await?;
124
125 // Now that we have the response, extract the parts, adding an extra header if we'll be encoding the body.
126 let status_code = response.get_status_code();
127 let mut headers = response.get_headers().copy_all();
128 if accept_deflated {
129 headers.push(("content-encoding".into(), b"deflate".into()));
130 }
131
132 let (_, result_rx) = wit_future::new(|| Ok(()));
133 let (mut body, trailers) = Response::consume_body(response, result_rx);
134 let (body, trailers) = if accept_deflated {
135 headers.retain(|(name, _value)| name != "content-length");
136
137 // Spawn another task; this one is to pipe and encode the original response body and trailers into a
138 // new response we'll create below. This will run concurrently with the caller's code (i.e. it won't
139 // necessarily complete before we return a value).
140 let (trailers_tx, trailers_rx) = wit_future::new(|| todo!());
141 let (mut pipe_tx, pipe_rx) = wit_stream::new();
142
143 wit_bindgen::spawn(async move {
144 {
145 let mut encoder = DeflateEncoder::new(Vec::new(), Compression::fast());
146 let mut status = StreamResult::Complete(0);
147 let mut chunk = Vec::with_capacity(64 * 1024);
148
149 while let StreamResult::Complete(_) = status {
150 (status, chunk) = body.read(chunk).await;
151 encoder.write_all(&chunk).unwrap();
152 let remaining = pipe_tx.write_all(mem::take(encoder.get_mut())).await;
153 assert!(remaining.is_empty());
154 *encoder.get_mut() = remaining;
155 chunk.clear();
156 }
157
158 let remaining = pipe_tx.write_all(encoder.finish().unwrap()).await;
159 assert!(remaining.is_empty());
160
161 drop(pipe_tx);
162 }
163
164 trailers_tx.write(trailers.await).await.unwrap();
165 });
166
167 (pipe_rx, trailers_rx)
168 } else {
169 (body, trailers)
170 };
171
172 // While the above tasks (if any) are running, synthesize a response from the parts collected above and
173 // return it.
174 let (my_response, _response_complete) =
175 Response::new(Headers::from_list(&headers).unwrap(), Some(body), trailers);
176 my_response.set_status_code(status_code).unwrap();
177
178 Ok(my_response)
179 }
180 }
181
182 // Unused function; required since this file is built as a `bin`:
main()183 fn main() {}
184