1f4be3606SAlex Crichton use crate::wasi::http::{outgoing_handler, types as http_types};
2f4be3606SAlex Crichton use crate::wasi::io::streams;
3*90ac295eSAlex Crichton use anyhow::{Result, anyhow};
4f4be3606SAlex Crichton use std::fmt;
5f4be3606SAlex Crichton 
6f4be3606SAlex Crichton pub struct Response {
7f4be3606SAlex Crichton     pub status: http_types::StatusCode,
8f4be3606SAlex Crichton     pub headers: Vec<(String, Vec<u8>)>,
9f4be3606SAlex Crichton     pub body: Vec<u8>,
10f4be3606SAlex Crichton }
11f4be3606SAlex Crichton impl fmt::Debug for Response {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result12f4be3606SAlex Crichton     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
13f4be3606SAlex Crichton         let mut out = f.debug_struct("Response");
14f4be3606SAlex Crichton         out.field("status", &self.status)
15f4be3606SAlex Crichton             .field("headers", &self.headers);
16f4be3606SAlex Crichton         if let Ok(body) = std::str::from_utf8(&self.body) {
17f4be3606SAlex Crichton             out.field("body", &body);
18f4be3606SAlex Crichton         } else {
19f4be3606SAlex Crichton             out.field("body", &self.body);
20f4be3606SAlex Crichton         }
21f4be3606SAlex Crichton         out.finish()
22f4be3606SAlex Crichton     }
23f4be3606SAlex Crichton }
24f4be3606SAlex Crichton 
25f4be3606SAlex Crichton impl Response {
header(&self, name: &str) -> Option<&Vec<u8>>26f4be3606SAlex Crichton     pub fn header(&self, name: &str) -> Option<&Vec<u8>> {
27f4be3606SAlex Crichton         self.headers
28f4be3606SAlex Crichton             .iter()
29f4be3606SAlex Crichton             .find_map(|(k, v)| if k == name { Some(v) } else { None })
30f4be3606SAlex Crichton     }
31f4be3606SAlex Crichton }
32f4be3606SAlex Crichton 
request( method: http_types::Method, scheme: http_types::Scheme, authority: &str, path_with_query: &str, body: Option<&[u8]>, additional_headers: Option<&[(String, Vec<u8>)]>, connect_timeout: Option<u64>, first_by_timeout: Option<u64>, between_bytes_timeout: Option<u64>, ) -> Result<Response>33f4be3606SAlex Crichton pub fn request(
34f4be3606SAlex Crichton     method: http_types::Method,
35f4be3606SAlex Crichton     scheme: http_types::Scheme,
36f4be3606SAlex Crichton     authority: &str,
37f4be3606SAlex Crichton     path_with_query: &str,
38f4be3606SAlex Crichton     body: Option<&[u8]>,
39f4be3606SAlex Crichton     additional_headers: Option<&[(String, Vec<u8>)]>,
40d29b863aSRik Huijzer     connect_timeout: Option<u64>,
41d29b863aSRik Huijzer     first_by_timeout: Option<u64>,
42d29b863aSRik Huijzer     between_bytes_timeout: Option<u64>,
43f4be3606SAlex Crichton ) -> Result<Response> {
44f4be3606SAlex Crichton     fn header_val(v: &str) -> Vec<u8> {
45f4be3606SAlex Crichton         v.to_string().into_bytes()
46f4be3606SAlex Crichton     }
47c97f5d68STrevor Elliott     let headers = http_types::Headers::from_list(
48f4be3606SAlex Crichton         &[
49f4be3606SAlex Crichton             &[
50f4be3606SAlex Crichton                 ("User-agent".to_string(), header_val("WASI-HTTP/0.0.1")),
51f4be3606SAlex Crichton                 ("Content-type".to_string(), header_val("application/json")),
52f4be3606SAlex Crichton             ],
53f4be3606SAlex Crichton             additional_headers.unwrap_or(&[]),
54f4be3606SAlex Crichton         ]
55f4be3606SAlex Crichton         .concat(),
56c97f5d68STrevor Elliott     )?;
57f4be3606SAlex Crichton 
580e50d50dSTrevor Elliott     let request = http_types::OutgoingRequest::new(headers);
590e50d50dSTrevor Elliott 
600e50d50dSTrevor Elliott     request
610e50d50dSTrevor Elliott         .set_method(&method)
620e50d50dSTrevor Elliott         .map_err(|()| anyhow!("failed to set method"))?;
630e50d50dSTrevor Elliott     request
640e50d50dSTrevor Elliott         .set_scheme(Some(&scheme))
650e50d50dSTrevor Elliott         .map_err(|()| anyhow!("failed to set scheme"))?;
660e50d50dSTrevor Elliott     request
670e50d50dSTrevor Elliott         .set_authority(Some(authority))
680e50d50dSTrevor Elliott         .map_err(|()| anyhow!("failed to set authority"))?;
690e50d50dSTrevor Elliott     request
700e50d50dSTrevor Elliott         .set_path_with_query(Some(&path_with_query))
710e50d50dSTrevor Elliott         .map_err(|()| anyhow!("failed to set path_with_query"))?;
72f4be3606SAlex Crichton 
73f4be3606SAlex Crichton     let outgoing_body = request
74f2fe75f7STrevor Elliott         .body()
75f4be3606SAlex Crichton         .map_err(|_| anyhow!("outgoing request write failed"))?;
76f4be3606SAlex Crichton 
77d1ff945eSXinzhao Xu     let options = http_types::RequestOptions::new();
78d1ff945eSXinzhao Xu     options
79d1ff945eSXinzhao Xu         .set_connect_timeout(connect_timeout)
80d1ff945eSXinzhao Xu         .map_err(|()| anyhow!("failed to set connect_timeout"))?;
81d1ff945eSXinzhao Xu     options
82d1ff945eSXinzhao Xu         .set_first_byte_timeout(first_by_timeout)
83d1ff945eSXinzhao Xu         .map_err(|()| anyhow!("failed to set first_byte_timeout"))?;
84d1ff945eSXinzhao Xu     options
85d1ff945eSXinzhao Xu         .set_between_bytes_timeout(between_bytes_timeout)
86d1ff945eSXinzhao Xu         .map_err(|()| anyhow!("failed to set between_bytes_timeout"))?;
87d1ff945eSXinzhao Xu     let options = Some(options);
88d1ff945eSXinzhao Xu 
89d1ff945eSXinzhao Xu     let future_response = outgoing_handler::handle(request, options)?;
90d1ff945eSXinzhao Xu 
91f4be3606SAlex Crichton     if let Some(mut buf) = body {
92f4be3606SAlex Crichton         let request_body = outgoing_body
93f4be3606SAlex Crichton             .write()
94f4be3606SAlex Crichton             .map_err(|_| anyhow!("outgoing request write failed"))?;
95f4be3606SAlex Crichton 
96f4be3606SAlex Crichton         let pollable = request_body.subscribe();
97f4be3606SAlex Crichton         while !buf.is_empty() {
98ddffc7e9SPat Hickey             pollable.block();
99f4be3606SAlex Crichton 
100f4be3606SAlex Crichton             let permit = match request_body.check_write() {
101f4be3606SAlex Crichton                 Ok(n) => n,
102f4be3606SAlex Crichton                 Err(_) => anyhow::bail!("output stream error"),
103f4be3606SAlex Crichton             };
104f4be3606SAlex Crichton 
105f4be3606SAlex Crichton             let len = buf.len().min(permit as usize);
106f4be3606SAlex Crichton             let (chunk, rest) = buf.split_at(len);
107f4be3606SAlex Crichton             buf = rest;
108f4be3606SAlex Crichton 
109f4be3606SAlex Crichton             match request_body.write(chunk) {
110f4be3606SAlex Crichton                 Err(_) => anyhow::bail!("output stream error"),
111f4be3606SAlex Crichton                 _ => {}
112f4be3606SAlex Crichton             }
113f4be3606SAlex Crichton         }
114f4be3606SAlex Crichton 
115f4be3606SAlex Crichton         match request_body.flush() {
116f4be3606SAlex Crichton             Err(_) => anyhow::bail!("output stream error"),
117f4be3606SAlex Crichton             _ => {}
118f4be3606SAlex Crichton         }
119f4be3606SAlex Crichton 
120ddffc7e9SPat Hickey         pollable.block();
121f4be3606SAlex Crichton 
122f4be3606SAlex Crichton         match request_body.check_write() {
123f4be3606SAlex Crichton             Ok(_) => {}
124f4be3606SAlex Crichton             Err(_) => anyhow::bail!("output stream error"),
125f4be3606SAlex Crichton         };
126f4be3606SAlex Crichton     }
12724d945f3STrevor Elliott     http_types::OutgoingBody::finish(outgoing_body, None)?;
128f4be3606SAlex Crichton 
129f4be3606SAlex Crichton     let incoming_response = match future_response.get() {
1308b523e78STrevor Elliott         Some(result) => result.map_err(|()| anyhow!("response already taken"))?,
131f4be3606SAlex Crichton         None => {
132f4be3606SAlex Crichton             let pollable = future_response.subscribe();
133ddffc7e9SPat Hickey             pollable.block();
134f4be3606SAlex Crichton             future_response
135f4be3606SAlex Crichton                 .get()
136f4be3606SAlex Crichton                 .expect("incoming response available")
1378b523e78STrevor Elliott                 .map_err(|()| anyhow!("response already taken"))?
138f4be3606SAlex Crichton         }
1398b523e78STrevor Elliott     }?;
140f4be3606SAlex Crichton 
141f4be3606SAlex Crichton     drop(future_response);
142f4be3606SAlex Crichton 
143f4be3606SAlex Crichton     let status = incoming_response.status();
144f4be3606SAlex Crichton 
145f4be3606SAlex Crichton     let headers_handle = incoming_response.headers();
146f4be3606SAlex Crichton     let headers = headers_handle.entries();
147f4be3606SAlex Crichton     drop(headers_handle);
148f4be3606SAlex Crichton 
149f4be3606SAlex Crichton     let incoming_body = incoming_response
150f4be3606SAlex Crichton         .consume()
151f4be3606SAlex Crichton         .map_err(|()| anyhow!("incoming response has no body stream"))?;
152f4be3606SAlex Crichton 
153f4be3606SAlex Crichton     drop(incoming_response);
154f4be3606SAlex Crichton 
155f4be3606SAlex Crichton     let input_stream = incoming_body.stream().unwrap();
156f4be3606SAlex Crichton     let input_stream_pollable = input_stream.subscribe();
157f4be3606SAlex Crichton 
158f4be3606SAlex Crichton     let mut body = Vec::new();
159f4be3606SAlex Crichton     loop {
160ddffc7e9SPat Hickey         input_stream_pollable.block();
161f4be3606SAlex Crichton 
162f4be3606SAlex Crichton         let mut body_chunk = match input_stream.read(1024 * 1024) {
163f4be3606SAlex Crichton             Ok(c) => c,
164f4be3606SAlex Crichton             Err(streams::StreamError::Closed) => break,
165f4be3606SAlex Crichton             Err(e) => Err(anyhow!("input_stream read failed: {e:?}"))?,
166f4be3606SAlex Crichton         };
167f4be3606SAlex Crichton 
168f4be3606SAlex Crichton         if !body_chunk.is_empty() {
169f4be3606SAlex Crichton             body.append(&mut body_chunk);
170f4be3606SAlex Crichton         }
171f4be3606SAlex Crichton     }
172f4be3606SAlex Crichton 
173f4be3606SAlex Crichton     Ok(Response {
174f4be3606SAlex Crichton         status,
175f4be3606SAlex Crichton         headers,
176f4be3606SAlex Crichton         body,
177f4be3606SAlex Crichton     })
178f4be3606SAlex Crichton }
179