golem_wasi_http/wasi/
request.rs

1use http::header::CONTENT_TYPE;
2use http::{HeaderMap, HeaderName, HeaderValue, Method, Version};
3use serde::Serialize;
4#[cfg(feature = "json")]
5use serde_json;
6use std::convert::TryFrom;
7use std::fmt;
8use std::time::Duration;
9use url::Url;
10
11use super::body::Body;
12use super::client::Client;
13
14/// A request which can be executed with `Client::execute()`.
15#[derive(Debug)]
16pub struct Request {
17    method: Method,
18    url: Url,
19    headers: HeaderMap,
20    body: Option<Body>,
21    timeout: Option<Duration>,
22    version: Version,
23}
24
25/// A builder to construct the properties of a `Request`.
26///
27/// To construct a `RequestBuilder`, refer to the `Client` documentation.
28#[must_use = "RequestBuilder does nothing until you 'send' it"]
29#[derive(Debug)]
30pub struct RequestBuilder {
31    client: Client,
32    request: crate::Result<Request>,
33}
34
35impl Request {
36    /// Constructs a new request.
37    #[inline]
38    pub fn new(method: Method, url: Url) -> Self {
39        Self {
40            method,
41            url,
42            headers: HeaderMap::new(),
43            version: Version::default(),
44            timeout: None,
45            body: None,
46        }
47    }
48
49    /// Get the method.
50    #[inline]
51    pub fn method(&self) -> &Method {
52        &self.method
53    }
54
55    /// Get a mutable reference to the method.
56    #[inline]
57    pub fn method_mut(&mut self) -> &mut Method {
58        &mut self.method
59    }
60
61    /// Get the url.
62    #[inline]
63    pub fn url(&self) -> &Url {
64        &self.url
65    }
66
67    /// Get a mutable reference to the url.
68    #[inline]
69    pub fn url_mut(&mut self) -> &mut Url {
70        &mut self.url
71    }
72
73    /// Get the headers.
74    #[inline]
75    pub fn headers(&self) -> &HeaderMap {
76        &self.headers
77    }
78
79    /// Get a mutable reference to the headers.
80    #[inline]
81    pub fn headers_mut(&mut self) -> &mut HeaderMap {
82        &mut self.headers
83    }
84
85    /// Get the body.
86    #[inline]
87    pub fn body(&self) -> Option<&Body> {
88        self.body.as_ref()
89    }
90
91    /// Get a mutable reference to the body.
92    #[inline]
93    pub fn body_mut(&mut self) -> &mut Option<Body> {
94        &mut self.body
95    }
96
97    /// Get the timeout.
98    #[inline]
99    pub fn timeout(&self) -> Option<&Duration> {
100        self.timeout.as_ref()
101    }
102
103    /// Get a mutable reference to the timeout.
104    #[inline]
105    pub fn timeout_mut(&mut self) -> &mut Option<Duration> {
106        &mut self.timeout
107    }
108
109    /// Get the http version.
110    #[inline]
111    pub fn version(&self) -> Version {
112        self.version
113    }
114
115    /// Get a mutable reference to the http version.
116    #[inline]
117    pub fn version_mut(&mut self) -> &mut Version {
118        &mut self.version
119    }
120
121    /// Attempt to clone the request.
122    ///
123    /// `None` is returned if the request can not be cloned, i.e. if the body is a stream.
124    pub fn try_clone(&self) -> Option<Request> {
125        let body = match self.body.as_ref() {
126            Some(body) => Some(body.try_clone()?),
127            None => None,
128        };
129        let mut req = Request::new(self.method().clone(), self.url().clone());
130        *req.timeout_mut() = self.timeout().copied();
131        *req.headers_mut() = self.headers().clone();
132        *req.version_mut() = self.version();
133        req.body = body;
134        Some(req)
135    }
136
137    pub(crate) fn pieces(
138        self,
139    ) -> (
140        Method,
141        Url,
142        HeaderMap,
143        Option<Body>,
144        Option<Duration>,
145        Version,
146    ) {
147        (
148            self.method,
149            self.url,
150            self.headers,
151            self.body,
152            self.timeout,
153            self.version,
154        )
155    }
156}
157
158impl RequestBuilder {
159    pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
160        let mut builder = Self { client, request };
161
162        let auth = builder
163            .request
164            .as_mut()
165            .ok()
166            .and_then(|req| extract_authority(&mut req.url));
167
168        if let Some((username, password)) = auth {
169            builder.basic_auth(username, password)
170        } else {
171            builder
172        }
173    }
174
175    /// Constructs the Request and sends it the target URL, returning a Response.
176    ///
177    /// # Errors
178    ///
179    /// This method fails if there was an error while sending request,
180    /// redirect loop was detected or redirect limit was exhausted.
181    #[cfg(not(feature = "async"))]
182    pub fn send(self) -> crate::Result<super::Response> {
183        self.client.execute(self.request?)
184    }
185
186    /// Constructs the Request and sends it the target URL, returning a Response.
187    ///
188    /// # Errors
189    ///
190    /// This method fails if there was an error while sending request,
191    /// redirect loop was detected or redirect limit was exhausted.
192    #[cfg(feature = "async")]
193    pub async fn send(self) -> crate::Result<super::Response> {
194        self.client.execute(self.request?).await
195    }
196
197    /// Assemble a builder starting from an existing `Client` and a `Request`.
198    pub fn from_parts(client: Client, request: Request) -> RequestBuilder {
199        RequestBuilder {
200            client,
201            request: Ok(request),
202        }
203    }
204
205    /// Add a `Header` to this Request.
206    pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
207    where
208        HeaderName: TryFrom<K>,
209        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
210        HeaderValue: TryFrom<V>,
211        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
212    {
213        self.header_sensitive(key, value, false)
214    }
215
216    /// Add a `Header` to this Request with ability to define if `header_value` is sensitive.
217    fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
218    where
219        HeaderName: TryFrom<K>,
220        <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
221        HeaderValue: TryFrom<V>,
222        <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
223    {
224        let mut error = None;
225        if let Ok(ref mut req) = self.request {
226            match <HeaderName as TryFrom<K>>::try_from(key) {
227                Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
228                    Ok(mut value) => {
229                        // We want to potentially make an unsensitive header
230                        // to be sensitive, not the reverse. So, don't turn off
231                        // a previously sensitive header.
232                        if sensitive {
233                            value.set_sensitive(true);
234                        }
235                        req.headers_mut().append(key, value);
236                    }
237                    Err(e) => error = Some(crate::error::builder(e.into())),
238                },
239                Err(e) => error = Some(crate::error::builder(e.into())),
240            };
241        }
242        if let Some(err) = error {
243            self.request = Err(err);
244        }
245        self
246    }
247
248    /// Add a set of Headers to the existing ones on this Request.
249    ///
250    /// The headers will be merged in to any already set.
251    pub fn headers(mut self, headers: HeaderMap) -> RequestBuilder {
252        if let Ok(ref mut req) = self.request {
253            crate::util::replace_headers(req.headers_mut(), headers);
254        }
255        self
256    }
257
258    /// Enable HTTP basic authentication.
259    pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
260    where
261        U: fmt::Display,
262        P: fmt::Display,
263    {
264        let header_value = crate::util::basic_auth(username, password);
265        self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
266    }
267
268    /// Enable HTTP bearer authentication.
269    pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
270    where
271        T: fmt::Display,
272    {
273        let header_value = format!("Bearer {}", token);
274        self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
275    }
276
277    /// Set the request body.
278    pub fn body<T: Into<Body>>(mut self, body: T) -> RequestBuilder {
279        if let Ok(ref mut req) = self.request {
280            *req.body_mut() = Some(body.into());
281        }
282        self
283    }
284
285    /// Enables a request timeout.
286    ///
287    /// The timeout is applied from when the request starts connecting until the
288    /// response body has finished. It affects only this request and overrides
289    /// the timeout configured using `ClientBuilder::timeout()`.
290    pub fn timeout(mut self, timeout: Duration) -> RequestBuilder {
291        if let Ok(ref mut req) = self.request {
292            *req.timeout_mut() = Some(timeout);
293        }
294        self
295    }
296
297    /// Sends a multipart/form-data body.
298    #[cfg(feature = "multipart")]
299    #[cfg_attr(docsrs, doc(cfg(feature = "multipart")))]
300    pub fn multipart(self, mut multipart: crate::multipart::Form) -> RequestBuilder {
301        let mut builder = self.header(
302            CONTENT_TYPE,
303            format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
304        );
305        if let Ok(ref mut req) = builder.request {
306            *req.body_mut() = Some(match multipart.compute_length() {
307                Some(length) => Body::sized(multipart.reader(), length),
308                None => Body::new(multipart.reader()),
309            })
310        }
311        builder
312    }
313
314    /// Modify the query string of the URL.
315    ///
316    /// Modifies the URL of this request, adding the parameters provided.
317    /// This method appends and does not overwrite. This means that it can
318    /// be called multiple times and that existing query parameters are not
319    /// overwritten if the same key is used. The key will simply show up
320    /// twice in the query string.
321    /// Calling `.query(&[("foo", "a"), ("foo", "b")])` gives `"foo=a&foo=b"`.
322    ///
323    /// # Note
324    /// This method does not support serializing a single key-value
325    /// pair. Instead of using `.query(("key", "val"))`, use a sequence, such
326    /// as `.query(&[("key", "val")])`. It's also possible to serialize structs
327    /// and maps into a key-value pair.
328    ///
329    /// # Errors
330    /// This method will fail if the object you provide cannot be serialized
331    /// into a query string.
332    pub fn query<T: Serialize + ?Sized>(mut self, query: &T) -> RequestBuilder {
333        let mut error = None;
334        if let Ok(ref mut req) = self.request {
335            let url = req.url_mut();
336            let mut pairs = url.query_pairs_mut();
337            let serializer = serde_urlencoded::Serializer::new(&mut pairs);
338
339            if let Err(err) = query.serialize(serializer) {
340                error = Some(crate::error::builder(err));
341            }
342        }
343        if let Ok(ref mut req) = self.request {
344            if let Some("") = req.url().query() {
345                req.url_mut().set_query(None);
346            }
347        }
348        if let Some(err) = error {
349            self.request = Err(err);
350        }
351        self
352    }
353
354    /// Set HTTP version
355    pub fn version(mut self, version: Version) -> RequestBuilder {
356        if let Ok(ref mut req) = self.request {
357            req.version = version;
358        }
359        self
360    }
361
362    /// Send a form body.
363    ///
364    /// Sets the body to the url encoded serialization of the passed value,
365    /// and also sets the `Content-Type: application/x-www-form-urlencoded`
366    /// header.
367    ///
368    /// # Errors
369    ///
370    /// This method fails if the passed value cannot be serialized into
371    /// url encoded format
372    pub fn form<T: Serialize + ?Sized>(mut self, form: &T) -> RequestBuilder {
373        let mut error = None;
374        if let Ok(ref mut req) = self.request {
375            match serde_urlencoded::to_string(form) {
376                Ok(body) => {
377                    req.headers_mut().insert(
378                        CONTENT_TYPE,
379                        HeaderValue::from_static("application/x-www-form-urlencoded"),
380                    );
381                    *req.body_mut() = Some(body.into());
382                }
383                Err(err) => error = Some(crate::error::builder(err)),
384            }
385        }
386        if let Some(err) = error {
387            self.request = Err(err);
388        }
389        self
390    }
391
392    /// Send a JSON body.
393    ///
394    /// # Optional
395    ///
396    /// This requires the optional `json` feature enabled.
397    ///
398    /// # Errors
399    ///
400    /// Serialization can fail if `T`'s implementation of `Serialize` decides to
401    /// fail, or if `T` contains a map with non-string keys.
402    #[cfg(feature = "json")]
403    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
404    pub fn json<T: Serialize + ?Sized>(mut self, json: &T) -> RequestBuilder {
405        let mut error = None;
406        if let Ok(ref mut req) = self.request {
407            match serde_json::to_vec(json) {
408                Ok(body) => {
409                    if !req.headers().contains_key(CONTENT_TYPE) {
410                        req.headers_mut()
411                            .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
412                    }
413                    *req.body_mut() = Some(body.into());
414                }
415                Err(err) => error = Some(crate::error::builder(err)),
416            }
417        }
418        if let Some(err) = error {
419            self.request = Err(err);
420        }
421        self
422    }
423
424    /// Disable CORS on fetching the request.
425    ///
426    /// # WASM
427    ///
428    /// This option is only effective with WebAssembly target.
429    ///
430    /// The [request mode][mdn] will be set to 'no-cors'.
431    ///
432    /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Request/mode
433    pub fn fetch_mode_no_cors(self) -> RequestBuilder {
434        self
435    }
436
437    /// Build a `Request`, which can be inspected, modified and executed with
438    /// `Client::execute()`.
439    pub fn build(self) -> crate::Result<Request> {
440        self.request
441    }
442
443    /// Build a `Request`, which can be inspected, modified and executed with
444    /// `Client::execute()`.
445    ///
446    /// This is similar to [`RequestBuilder::build()`], but also returns the
447    /// embedded `Client`.
448    pub fn build_split(self) -> (Client, crate::Result<Request>) {
449        (self.client, self.request)
450    }
451
452    /// Attempt to clone the RequestBuilder.
453    ///
454    /// `None` is returned if the RequestBuilder can not be cloned,
455    /// i.e. if the request body is a stream.
456    ///
457    pub fn try_clone(&self) -> Option<RequestBuilder> {
458        self.request
459            .as_ref()
460            .ok()
461            .and_then(|req| req.try_clone())
462            .map(|req| RequestBuilder {
463                client: self.client.clone(),
464                request: Ok(req),
465            })
466    }
467}
468
469/// Check the request URL for a "username:password" type authority, and if
470/// found, remove it from the URL and return it.
471pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option<String>)> {
472    use percent_encoding::percent_decode;
473
474    if url.has_authority() {
475        let username: String = percent_decode(url.username().as_bytes())
476            .decode_utf8()
477            .ok()?
478            .into();
479        let password = url.password().and_then(|pass| {
480            percent_decode(pass.as_bytes())
481                .decode_utf8()
482                .ok()
483                .map(String::from)
484        });
485        if !username.is_empty() || password.is_some() {
486            url.set_username("")
487                .expect("has_authority means set_username shouldn't fail");
488            url.set_password(None)
489                .expect("has_authority means set_password shouldn't fail");
490            return Some((username, password));
491        }
492    }
493
494    None
495}