worker/
request.rs

1use std::convert::TryFrom;
2
3use crate::{
4    cf::Cf, error::Error, headers::Headers, http::Method, ByteStream, FormData, RequestInit, Result,
5};
6
7use serde::de::DeserializeOwned;
8#[cfg(test)]
9use std::borrow::Cow;
10#[cfg(test)]
11use url::form_urlencoded::Parse;
12use url::Url;
13use wasm_bindgen::JsCast;
14use wasm_bindgen_futures::JsFuture;
15use worker_sys::ext::RequestExt;
16
17/// A [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) representation for
18/// handling incoming and creating outbound HTTP requests.
19#[derive(Debug)]
20pub struct Request {
21    method: Method,
22    path: String,
23    headers: Headers,
24    cf: Option<Cf>,
25    edge_request: web_sys::Request,
26    body_used: bool,
27    immutable: bool,
28}
29
30unsafe impl Send for Request {}
31unsafe impl Sync for Request {}
32
33#[cfg(feature = "http")]
34impl<B: http_body::Body<Data = bytes::Bytes> + 'static> TryFrom<http::Request<B>> for Request {
35    type Error = crate::Error;
36    fn try_from(req: http::Request<B>) -> Result<Self> {
37        let web_request: web_sys::Request = crate::http::request::to_wasm(req)?;
38        Ok(Request::from(web_request))
39    }
40}
41
42#[cfg(feature = "http")]
43impl TryFrom<Request> for crate::HttpRequest {
44    type Error = crate::Error;
45    fn try_from(req: Request) -> Result<Self> {
46        crate::http::request::from_wasm(req.edge_request)
47    }
48}
49
50impl From<web_sys::Request> for Request {
51    fn from(req: web_sys::Request) -> Self {
52        Self {
53            method: req.method().into(),
54            path: Url::parse(&req.url())
55                .map(|u| u.path().into())
56                .unwrap_or_else(|_| {
57                    let u = req.url();
58                    if !u.starts_with('/') {
59                        return "/".to_string() + &u;
60                    }
61                    u
62                }),
63            headers: Headers(req.headers()),
64            cf: req.cf().map(Into::into),
65            edge_request: req,
66            body_used: false,
67            immutable: true,
68        }
69    }
70}
71
72impl TryFrom<Request> for web_sys::Request {
73    type Error = Error;
74    fn try_from(req: Request) -> Result<Self> {
75        req.inner().clone().map_err(Error::from)
76    }
77}
78
79impl TryFrom<&Request> for web_sys::Request {
80    type Error = Error;
81    fn try_from(req: &Request) -> Result<Self> {
82        req.inner().clone().map_err(Error::from)
83    }
84}
85
86impl Request {
87    /// Construct a new `Request` with an HTTP Method.
88    pub fn new(uri: &str, method: Method) -> Result<Self> {
89        let init = web_sys::RequestInit::new();
90        init.set_method(method.as_ref());
91        web_sys::Request::new_with_str_and_init(uri, &init)
92            .map(|req| {
93                let mut req: Request = req.into();
94                req.immutable = false;
95                req
96            })
97            .map_err(|e| {
98                Error::JsError(
99                    e.as_string()
100                        .unwrap_or_else(|| "invalid URL or method for Request".to_string()),
101                )
102            })
103    }
104
105    /// Construct a new `Request` with a `RequestInit` configuration.
106    pub fn new_with_init(uri: &str, init: &RequestInit) -> Result<Self> {
107        web_sys::Request::new_with_str_and_init(uri, &init.into())
108            .map(|req| {
109                let mut req: Request = req.into();
110                req.immutable = false;
111                req
112            })
113            .map_err(|e| {
114                Error::JsError(
115                    e.as_string()
116                        .unwrap_or_else(|| "invalid URL or options for Request".to_string()),
117                )
118            })
119    }
120
121    /// Access this request's body encoded as JSON.
122    pub async fn json<B: DeserializeOwned>(&mut self) -> Result<B> {
123        if !self.body_used {
124            self.body_used = true;
125            return JsFuture::from(self.edge_request.json()?)
126                .await
127                .map_err(|e| {
128                    Error::JsError(
129                        e.as_string()
130                            .unwrap_or_else(|| "failed to get JSON for body value".into()),
131                    )
132                })
133                .and_then(|val| serde_wasm_bindgen::from_value(val).map_err(Error::from));
134        }
135
136        Err(Error::BodyUsed)
137    }
138
139    /// Access this request's body as plaintext.
140    pub async fn text(&mut self) -> Result<String> {
141        if !self.body_used {
142            self.body_used = true;
143            return JsFuture::from(self.edge_request.text()?)
144                .await
145                .map(|val| val.as_string().unwrap())
146                .map_err(|e| {
147                    Error::JsError(
148                        e.as_string()
149                            .unwrap_or_else(|| "failed to get text for body value".into()),
150                    )
151                });
152        }
153
154        Err(Error::BodyUsed)
155    }
156
157    /// Access this request's body as raw bytes.
158    pub async fn bytes(&mut self) -> Result<Vec<u8>> {
159        if !self.body_used {
160            self.body_used = true;
161            return JsFuture::from(self.edge_request.array_buffer()?)
162                .await
163                .map(|val| js_sys::Uint8Array::new(&val).to_vec())
164                .map_err(|e| {
165                    Error::JsError(
166                        e.as_string()
167                            .unwrap_or_else(|| "failed to read array buffer from request".into()),
168                    )
169                });
170        }
171
172        Err(Error::BodyUsed)
173    }
174
175    /// Access this request's body as a form-encoded payload and pull out fields and files.
176    pub async fn form_data(&mut self) -> Result<FormData> {
177        if !self.body_used {
178            self.body_used = true;
179            return JsFuture::from(self.edge_request.form_data()?)
180                .await
181                .map(|val| val.into())
182                .map_err(|e| {
183                    Error::JsError(
184                        e.as_string()
185                            .unwrap_or_else(|| "failed to get form data from request".into()),
186                    )
187                });
188        }
189
190        Err(Error::BodyUsed)
191    }
192
193    /// Access this request's body as a [`Stream`](futures::stream::Stream) of bytes.
194    pub fn stream(&mut self) -> Result<ByteStream> {
195        if self.body_used {
196            return Err(Error::BodyUsed);
197        }
198
199        self.body_used = true;
200
201        let stream = self
202            .edge_request
203            .body()
204            .ok_or_else(|| Error::RustError("no body for request".into()))?;
205
206        let stream = wasm_streams::ReadableStream::from_raw(stream.dyn_into().unwrap());
207        Ok(ByteStream {
208            inner: stream.into_stream(),
209        })
210    }
211
212    /// Get the `Headers` for this request.
213    pub fn headers(&self) -> &Headers {
214        &self.headers
215    }
216
217    /// Get a mutable reference to this request's `Headers`.
218    /// **Note:** they can only be modified if the request was created from scratch or cloned.
219    pub fn headers_mut(&mut self) -> Result<&mut Headers> {
220        if self.immutable {
221            return Err(Error::JsError(
222                "Cannot get a mutable reference to an immutable headers object.".into(),
223            ));
224        }
225        Ok(&mut self.headers)
226    }
227
228    /// Access this request's Cloudflare-specific properties.
229    ///
230    /// # Note
231    ///
232    /// Request objects constructed by the user and not the runtime will not have a [Cf] associated.
233    ///
234    /// See [workerd#825](https://github.com/cloudflare/workerd/issues/825)
235    pub fn cf(&self) -> Option<&Cf> {
236        self.cf.as_ref()
237    }
238
239    /// The HTTP Method associated with this `Request`.
240    pub fn method(&self) -> Method {
241        self.method.clone()
242    }
243
244    /// The URL Path of this `Request`.
245    pub fn path(&self) -> String {
246        self.path.clone()
247    }
248
249    /// Get a mutable reference to this request's path.
250    /// **Note:** they can only be modified if the request was created from scratch or cloned.
251    pub fn path_mut(&mut self) -> Result<&mut String> {
252        if self.immutable {
253            return Err(Error::JsError(
254                "Cannot get a mutable reference to an immutable path.".into(),
255            ));
256        }
257        Ok(&mut self.path)
258    }
259
260    /// The parsed [`url::Url`] of this `Request`.
261    pub fn url(&self) -> Result<Url> {
262        let url = self.edge_request.url();
263        url.parse()
264            .map_err(|e| Error::RustError(format!("failed to parse Url from {e}: {url}")))
265    }
266
267    /// Deserialize the url query
268    pub fn query<Q: DeserializeOwned>(&self) -> Result<Q> {
269        let url = self.url()?;
270        let pairs = url.query_pairs();
271        let deserializer = serde_urlencoded::Deserializer::new(pairs);
272
273        Q::deserialize(deserializer).map_err(Error::from)
274    }
275
276    #[allow(clippy::should_implement_trait)]
277    pub fn clone(&self) -> Result<Self> {
278        self.edge_request
279            .clone()
280            .map(|req| req.into())
281            .map_err(Error::from)
282    }
283
284    pub fn clone_mut(&self) -> Result<Self> {
285        let mut req: Request = web_sys::Request::new_with_request(&self.edge_request)?.into();
286        req.immutable = false;
287        Ok(req)
288    }
289
290    pub fn inner(&self) -> &web_sys::Request {
291        &self.edge_request
292    }
293}
294
295#[cfg(test)]
296pub struct ParamIter<'a> {
297    inner: Parse<'a>,
298    key: &'a str,
299}
300
301#[cfg(test)]
302impl<'a> Iterator for ParamIter<'a> {
303    type Item = Cow<'a, str>;
304
305    fn next(&mut self) -> Option<Self::Item> {
306        let key = self.key;
307        Some(self.inner.find(|(k, _)| k == key)?.1)
308    }
309}
310
311/// A trait used to represent any viable Request type that can be used in the Worker.
312/// The only requirement is that it be convertible from a web_sys::Request.
313pub trait FromRequest: std::marker::Sized {
314    fn from_raw(
315        request: web_sys::Request,
316    ) -> std::result::Result<Self, impl Into<Box<dyn std::error::Error>>>;
317}
318
319impl FromRequest for web_sys::Request {
320    fn from_raw(
321        request: web_sys::Request,
322    ) -> std::result::Result<Self, impl Into<Box<dyn std::error::Error>>> {
323        Ok::<web_sys::Request, Error>(request)
324    }
325}
326
327impl FromRequest for Request {
328    fn from_raw(
329        request: web_sys::Request,
330    ) -> std::result::Result<Self, impl Into<Box<dyn std::error::Error>>> {
331        Ok::<Request, Error>(request.into())
332    }
333}
334
335#[cfg(feature = "http")]
336impl FromRequest for crate::HttpRequest {
337    fn from_raw(
338        request: web_sys::Request,
339    ) -> std::result::Result<Self, impl Into<Box<dyn std::error::Error>>> {
340        crate::http::request::from_wasm(request)
341    }
342}
343
344#[cfg(test)]
345mod test {
346    use super::*;
347
348    /// Used to add additional helper functions to url::Url
349    pub trait UrlExt {
350        /// Given a query parameter, returns the value of the first occurrence of that parameter if it
351        /// exists
352        fn param<'a>(&'a self, key: &'a str) -> Option<Cow<'a, str>> {
353            self.param_iter(key).next()
354        }
355        /// Given a query parameter, returns an Iterator of values for that parameter in the url's
356        /// query string
357        fn param_iter<'a>(&'a self, key: &'a str) -> ParamIter<'a>;
358    }
359
360    impl UrlExt for Url {
361        fn param_iter<'a>(&'a self, key: &'a str) -> ParamIter<'a> {
362            ParamIter {
363                inner: self.query_pairs(),
364                key,
365            }
366        }
367    }
368
369    #[test]
370    fn url_param_works() {
371        let url = Url::parse("https://example.com/foo.html?a=foo&b=bar&a=baz").unwrap();
372        assert_eq!(url.param("a").as_deref(), Some("foo"));
373        assert_eq!(url.param("b").as_deref(), Some("bar"));
374        assert_eq!(url.param("c").as_deref(), None);
375        let mut a_values = url.param_iter("a");
376        assert_eq!(a_values.next().as_deref(), Some("foo"));
377        assert_eq!(a_values.next().as_deref(), Some("baz"));
378        assert_eq!(a_values.next(), None);
379    }
380
381    #[test]
382    fn clone_mut_works() {
383        let req = Request::new(
384            "https://example.com/foo.html?a=foo&b=bar&a=baz",
385            crate::Method::Get,
386        )
387        .unwrap();
388        assert!(!req.immutable);
389        let mut_req = req.clone_mut().unwrap();
390        assert!(mut_req.immutable);
391    }
392}