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-02-09": test_programs::p3::wasi::http::handler, 22 "wasi:http/types@0.3.0-rc-2026-02-09": test_programs::p3::wasi::http::types, 23 "wasi:http/client@0.3.0-rc-2026-02-09": test_programs::p3::wasi::http::client, 24 "wasi:random/random@0.3.0-rc-2026-02-09": test_programs::p3::wasi::random::random, 25 "wasi:random/insecure@0.3.0-rc-2026-02-09": test_programs::p3::wasi::random::insecure, 26 "wasi:random/insecure-seed@0.3.0-rc-2026-02-09": test_programs::p3::wasi::random::insecure_seed, 27 "wasi:cli/stdout@0.3.0-rc-2026-02-09": test_programs::p3::wasi::cli::stdout, 28 "wasi:cli/stderr@0.3.0-rc-2026-02-09": test_programs::p3::wasi::cli::stderr, 29 "wasi:cli/stdin@0.3.0-rc-2026-02-09": test_programs::p3::wasi::cli::stdin, 30 "wasi:cli/types@0.3.0-rc-2026-02-09": test_programs::p3::wasi::cli::types, 31 "wasi:clocks/monotonic-clock@0.3.0-rc-2026-02-09": test_programs::p3::wasi::clocks::monotonic_clock, 32 "wasi:clocks/system-clock@0.3.0-rc-2026-02-09": test_programs::p3::wasi::clocks::system_clock, 33 "wasi:clocks/types@0.3.0-rc-2026-02-09": 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. 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`: 183 fn main() {} 184