Path: blob/main/crates/test-programs/src/p3/http.rs
1693 views
use anyhow::{Context as _, Result, anyhow};1use core::fmt;2use futures::join;34use crate::p3::wasi::http::{handler, types};5use crate::p3::{wit_future, wit_stream};67pub struct Response {8pub status: types::StatusCode,9pub headers: Vec<(String, Vec<u8>)>,10pub body: Vec<u8>,11pub trailers: Option<Vec<(String, Vec<u8>)>>,12}13impl fmt::Debug for Response {14fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {15let mut out = f.debug_struct("Response");16out.field("status", &self.status)17.field("headers", &self.headers);18if let Ok(body) = std::str::from_utf8(&self.body) {19out.field("body", &body);20} else {21out.field("body", &self.body);22}23out.field("trailers", &self.trailers);24out.finish()25}26}2728impl Response {29pub fn header(&self, name: &str) -> Option<&Vec<u8>> {30self.headers31.iter()32.find_map(|(k, v)| if k == name { Some(v) } else { None })33}34}3536pub async fn request(37method: types::Method,38scheme: types::Scheme,39authority: &str,40path_with_query: &str,41body: Option<&[u8]>,42additional_headers: Option<&[(String, Vec<u8>)]>,43connect_timeout: Option<u64>,44first_by_timeout: Option<u64>,45between_bytes_timeout: Option<u64>,46) -> Result<Response> {47fn header_val(v: &str) -> Vec<u8> {48v.to_string().into_bytes()49}50let headers = types::Headers::from_list(51&[52&[53("User-agent".to_string(), header_val("WASI-HTTP/0.0.1")),54("Content-type".to_string(), header_val("application/json")),55],56additional_headers.unwrap_or(&[]),57]58.concat(),59)?;6061let options = types::RequestOptions::new();62options63.set_connect_timeout(connect_timeout)64.map_err(|_err| anyhow!("failed to set connect_timeout"))?;65options66.set_first_byte_timeout(first_by_timeout)67.map_err(|_err| anyhow!("failed to set first_byte_timeout"))?;68options69.set_between_bytes_timeout(between_bytes_timeout)70.map_err(|_err| anyhow!("failed to set between_bytes_timeout"))?;7172let (mut contents_tx, contents_rx) = wit_stream::new();73let (trailers_tx, trailers_rx) = wit_future::new(|| Ok(None));74let (request, transmit) =75types::Request::new(headers, Some(contents_rx), trailers_rx, Some(options));7677request78.set_method(&method)79.map_err(|()| anyhow!("failed to set method"))?;80request81.set_scheme(Some(&scheme))82.map_err(|()| anyhow!("failed to set scheme"))?;83request84.set_authority(Some(authority))85.map_err(|()| anyhow!("failed to set authority"))?;86request87.set_path_with_query(Some(&path_with_query))88.map_err(|()| anyhow!("failed to set path_with_query"))?;8990let (transmit, handle) = join!(91async { transmit.await.context("failed to transmit request") },92async {93let response = handler::handle(request).await?;94let status = response.get_status_code();95let headers = response.get_headers().copy_all();96let (body_rx, trailers_rx) = response97.consume_body()98.expect("failed to get response body");99let ((), rx) = join!(100async {101if let Some(buf) = body {102let remaining = contents_tx.write_all(buf.into()).await;103assert!(remaining.is_empty());104}105drop(contents_tx);106// This can fail in HTTP/1.1, since the connection might already be closed107_ = trailers_tx.write(Ok(None)).await;108},109async {110let body = body_rx.collect().await;111let trailers = trailers_rx.await.context("failed to read body")?;112let trailers = trailers.map(|trailers| trailers.copy_all());113anyhow::Ok(Response {114status,115headers,116body,117trailers,118})119}120);121rx122},123);124let response = handle?;125transmit?;126Ok(response)127}128129130