golem_wasi_http/wasi/
sync_client.rs

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/// A `Client` to make Requests with.
26///
27/// The Client has various configuration values to tweak, but the defaults
28/// are set to what is usually the most commonly desired value. To configure a
29/// `Client`, use `Client::builder()`.
30///
31/// The `Client` holds a connection pool internally, so it is advised that
32/// you create one and **reuse** it.
33#[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/// A `ClientBuilder` can be used to create a `Client` with custom configuration.
47#[must_use]
48#[derive(Debug)]
49pub struct ClientBuilder {
50    config: Config,
51}
52
53impl Client {
54    /// Constructs a new `Client`.
55    pub fn new() -> Self {
56        Client::builder().build().expect("Client::new()")
57    }
58
59    /// Creates a `ClientBuilder` to configure a `Client`.
60    ///
61    /// This is the same as `ClientBuilder::new()`.
62    pub fn builder() -> ClientBuilder {
63        ClientBuilder::new()
64    }
65
66    /// Convenience method to make a `GET` request to a URL.
67    ///
68    /// # Errors
69    ///
70    /// This method fails whenever supplied `Url` cannot be parsed.
71    pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
72        self.request(Method::GET, url)
73    }
74
75    /// Convenience method to make a `POST` request to a URL.
76    ///
77    /// # Errors
78    ///
79    /// This method fails whenever supplied `Url` cannot be parsed.
80    pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
81        self.request(Method::POST, url)
82    }
83
84    /// Convenience method to make a `PUT` request to a URL.
85    ///
86    /// # Errors
87    ///
88    /// This method fails whenever supplied `Url` cannot be parsed.
89    pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
90        self.request(Method::PUT, url)
91    }
92
93    /// Convenience method to make a `PATCH` request to a URL.
94    ///
95    /// # Errors
96    ///
97    /// This method fails whenever supplied `Url` cannot be parsed.
98    pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
99        self.request(Method::PATCH, url)
100    }
101
102    /// Convenience method to make a `DELETE` request to a URL.
103    ///
104    /// # Errors
105    ///
106    /// This method fails whenever supplied `Url` cannot be parsed.
107    pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
108        self.request(Method::DELETE, url)
109    }
110
111    /// Convenience method to make a `HEAD` request to a URL.
112    ///
113    /// # Errors
114    ///
115    /// This method fails whenever supplied `Url` cannot be parsed.
116    pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
117        self.request(Method::HEAD, url)
118    }
119
120    /// Start building a `Request` with the `Method` and `Url`.
121    ///
122    /// Returns a `RequestBuilder`, which will allow setting headers and
123    /// request body before sending.
124    ///
125    /// # Errors
126    ///
127    /// This method fails whenever supplied `Url` cannot be parsed.
128    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    /// Executes a `Request`.
134    ///
135    /// A `Request` can be built manually with `Request::new()` or obtained
136    /// from a RequestBuilder with `RequestBuilder::build()`.
137    ///
138    /// You should prefer to use the `RequestBuilder` and
139    /// `RequestBuilder::send()`.
140    ///
141    /// # Errors
142    ///
143    /// This method fails if there was an error while sending request,
144    /// redirect loop was detected or redirect limit was exhausted.
145    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    /// Constructs a new `ClientBuilder`.
311    ///
312    /// This is the same as `Client::builder()`.
313    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    /// Returns a `Client` that uses this `ClientBuilder` configuration.
328    ///
329    /// # Errors
330    ///
331    /// This method fails if TLS backend cannot be initialized, or the resolver
332    /// cannot load the system configuration.
333    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    /// Sets the `User-Agent` header to be used by this client.
349    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    /// Sets the default headers for every request
366    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    // TODO: cookie support
374    // TODO: gzip support
375    // TODO: brotli support
376    // TODO: deflate support
377    // TODO: redirect support
378    // TODO: proxy support
379    // TODO: TLS support
380
381    // Timeout options
382
383    /// Set a timeout for connect, read and write operations of a `Client`.
384    ///
385    /// Default is 30 seconds.
386    ///
387    /// Pass `None` to disable timeout.
388    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    /// Set a timeout for only the connect phase of a `Client`.
397    ///
398    /// Default is `None`.
399    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}