1use http::header::{HeaderMap, HeaderValue, ACCEPT, USER_AGENT};
2use http::{HeaderName, Method, StatusCode};
3use std::convert::{TryFrom, TryInto};
4use std::sync::Arc;
5use std::time::Duration;
6
7use super::conversions::{encode_method, failure_point};
8use super::request::{Request, RequestBuilder};
9use super::response::Response;
10use crate::error::Kind;
11use crate::{Body, IntoUrl};
12
13use wasi::clocks::*;
14use wasi::http::*;
15use wasi::io::*;
16
17#[derive(Debug)]
18struct Config {
19 headers: HeaderMap,
20 connect_timeout: Option<Duration>,
21 timeout: Option<Duration>,
22 error: Option<crate::Error>,
23}
24
25#[derive(Clone, Debug)]
34pub struct Client {
35 inner: Arc<ClientRef>,
36}
37
38#[derive(Debug)]
39struct ClientRef {
40 pub headers: HeaderMap,
41 pub connect_timeout: Option<Duration>,
42 pub first_byte_timeout: Option<Duration>,
43 pub between_bytes_timeout: Option<Duration>,
44}
45
46#[must_use]
48#[derive(Debug)]
49pub struct ClientBuilder {
50 config: Config,
51}
52
53impl Client {
54 pub fn new() -> Self {
56 Client::builder().build().expect("Client::new()")
57 }
58
59 pub fn builder() -> ClientBuilder {
63 ClientBuilder::new()
64 }
65
66 pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
72 self.request(Method::GET, url)
73 }
74
75 pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
81 self.request(Method::POST, url)
82 }
83
84 pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
90 self.request(Method::PUT, url)
91 }
92
93 pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
99 self.request(Method::PATCH, url)
100 }
101
102 pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
108 self.request(Method::DELETE, url)
109 }
110
111 pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
117 self.request(Method::HEAD, url)
118 }
119
120 pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
129 let req = url.into_url().map(move |url| Request::new(method, url));
130 RequestBuilder::new(self.clone(), req)
131 }
132
133 pub fn execute(&self, request: Request) -> Result<Response, crate::Error> {
146 let mut header_key_values: Vec<(String, Vec<u8>)> = vec![];
147 for (name, value) in self.inner.headers.iter() {
148 if let Ok(value) = value.to_str() {
149 header_key_values.push((name.as_str().to_string(), value.into()))
150 }
151 }
152
153 let (method, url, headers, body, timeout, _version) = request.pieces();
154 for (name, value) in headers.iter() {
155 if let Ok(value) = value.to_str() {
156 header_key_values.push((name.as_str().to_string(), value.into()))
157 }
158 }
159
160 let scheme = match url.scheme() {
161 "http" => types::Scheme::Http,
162 "https" => types::Scheme::Https,
163 other => types::Scheme::Other(other.to_string()),
164 };
165 let headers = types::Fields::from_list(&header_key_values)?;
166 let request = types::OutgoingRequest::new(headers);
167 let path_with_query = match url.query() {
168 Some(query) => format!("{}?{}", url.path(), query),
169 None => url.path().to_string(),
170 };
171 request
172 .set_method(&encode_method(method))
173 .map_err(|e| failure_point("set_method", e))?;
174 request
175 .set_path_with_query(Some(&path_with_query))
176 .map_err(|e| failure_point("set_path_with_query", e))?;
177 request
178 .set_scheme(Some(&scheme))
179 .map_err(|e| failure_point("set_scheme", e))?;
180 request
181 .set_authority(Some(url.authority()))
182 .map_err(|e| failure_point("set_authority", e))?;
183
184 let options = types::RequestOptions::new();
185 options
186 .set_connect_timeout(self.inner.connect_timeout.map(|d| d.as_nanos() as u64))
187 .map_err(|e| failure_point("set_connect_timeout", e))?;
188 options
189 .set_first_byte_timeout(
190 timeout
191 .or(self.inner.first_byte_timeout)
192 .map(|d| d.as_nanos() as u64),
193 )
194 .map_err(|e| failure_point("set_first_byte_timeout", e))?;
195 options
196 .set_between_bytes_timeout(
197 timeout
198 .or(self.inner.between_bytes_timeout)
199 .map(|d| d.as_nanos() as u64),
200 )
201 .map_err(|e| failure_point("set_between_bytes_timeout", e))?;
202
203 let maybe_outgoing_body = if let Some(body) = body {
204 let request_body = request.body().map_err(|e| failure_point("body", e))?;
205 Some((body, request_body))
206 } else {
207 None
208 };
209
210 let future_incoming_response = outgoing_handler::handle(request, Some(options))?;
211
212 if let Some((body, outgoing_body)) = maybe_outgoing_body {
213 let outgoing_body_stream = outgoing_body
214 .write()
215 .map_err(|e| failure_point("write", e))?;
216 body.write(|chunk| {
217 outgoing_body_stream.blocking_write_and_flush(chunk)?;
218 Ok(())
219 })?;
220 drop(outgoing_body_stream);
221 types::OutgoingBody::finish(outgoing_body, None)?;
222 }
223
224 let receive_timeout = timeout.or(self.inner.first_byte_timeout);
225 let incoming_response =
226 Self::get_incoming_response(&future_incoming_response, receive_timeout)?;
227
228 let status = incoming_response.status();
229 let status_code = StatusCode::from_u16(status)
230 .map_err(|e| crate::Error::new(crate::error::Kind::Decode, Some(e)))?;
231
232 let response_fields = incoming_response.headers();
233 let response_headers = Self::fields_to_header_map(&response_fields);
234
235 let response_body = incoming_response
236 .consume()
237 .map_err(|e| failure_point("consume", e))?;
238 let response_body_stream = response_body
239 .stream()
240 .map_err(|e| failure_point("stream", e))?;
241 let body: Body = Body::from_incoming(response_body_stream, response_body);
242
243 Ok(Response::new(
244 status_code,
245 response_headers,
246 body,
247 incoming_response,
248 url,
249 ))
250 }
251
252 fn get_incoming_response(
253 future_incoming_response: &types::FutureIncomingResponse,
254 timeout: Option<Duration>,
255 ) -> Result<types::IncomingResponse, crate::Error> {
256 let deadline_pollable = monotonic_clock::subscribe_duration(
257 timeout
258 .unwrap_or(Duration::from_secs(10000000000))
259 .as_nanos() as u64,
260 );
261 loop {
262 match future_incoming_response.get() {
263 Some(Ok(Ok(incoming_response))) => {
264 return Ok(incoming_response);
265 }
266 Some(Ok(Err(err))) => return Err(err.into()),
267 Some(Err(err)) => return Err(failure_point("get_incoming_response", err)),
268 None => {
269 let pollable = future_incoming_response.subscribe();
270 let bitmap = poll::poll(&[&pollable, &deadline_pollable]);
271 if timeout.is_none() || !bitmap.contains(&1) {
272 continue;
273 } else {
274 return Err(crate::Error::new(
275 Kind::Request,
276 Some(crate::error::TimedOut),
277 ));
278 }
279 }
280 };
281 }
282 }
283
284 fn fields_to_header_map(fields: &types::Fields) -> HeaderMap {
285 let mut headers = HeaderMap::new();
286 let entries = fields.entries();
287 for (name, value) in entries {
288 headers.insert(
289 HeaderName::try_from(&name).expect("Invalid header name"),
290 HeaderValue::from_bytes(&value).expect("Invalid header value"),
291 );
292 }
293 headers
294 }
295}
296
297impl Default for Client {
298 fn default() -> Self {
299 Self::new()
300 }
301}
302
303impl Default for ClientBuilder {
304 fn default() -> Self {
305 Self::new()
306 }
307}
308
309impl ClientBuilder {
310 pub fn new() -> Self {
314 let mut headers: HeaderMap<HeaderValue> = HeaderMap::with_capacity(2);
315 headers.insert(ACCEPT, HeaderValue::from_static("*/*"));
316
317 Self {
318 config: Config {
319 headers,
320 connect_timeout: None,
321 timeout: None,
322 error: None,
323 },
324 }
325 }
326
327 pub fn build(self) -> Result<Client, crate::Error> {
334 if let Some(err) = self.config.error {
335 return Err(err);
336 }
337
338 Ok(Client {
339 inner: Arc::new(ClientRef {
340 headers: self.config.headers,
341 connect_timeout: self.config.connect_timeout,
342 first_byte_timeout: self.config.timeout,
343 between_bytes_timeout: self.config.timeout,
344 }),
345 })
346 }
347
348 pub fn user_agent<V>(mut self, value: V) -> ClientBuilder
350 where
351 V: TryInto<HeaderValue>,
352 V::Error: Into<http::Error>,
353 {
354 match value.try_into() {
355 Ok(value) => {
356 self.config.headers.insert(USER_AGENT, value);
357 }
358 Err(e) => {
359 self.config.error = Some(crate::error::builder(e.into()));
360 }
361 };
362 self
363 }
364
365 pub fn default_headers(mut self, headers: HeaderMap) -> ClientBuilder {
367 for (key, value) in headers.iter() {
368 self.config.headers.insert(key, value.clone());
369 }
370 self
371 }
372
373 pub fn timeout<T>(mut self, timeout: T) -> ClientBuilder
389 where
390 T: Into<Option<Duration>>,
391 {
392 self.config.timeout = timeout.into();
393 self
394 }
395
396 pub fn connect_timeout<T>(mut self, timeout: T) -> ClientBuilder
400 where
401 T: Into<Option<Duration>>,
402 {
403 self.config.connect_timeout = timeout.into();
404 self
405 }
406}