rocket/local/asynchronous/
request.rs

1use std::fmt;
2
3use crate::{Request, Data};
4use crate::http::{Status, Method};
5use crate::http::uri::Origin;
6
7use super::{Client, LocalResponse};
8
9/// An `async` local request as returned by [`Client`](super::Client).
10///
11/// For details, see [the top-level documentation](../index.html#localrequest).
12///
13/// ## Example
14///
15/// The following snippet uses the available builder methods to construct and
16/// dispatch a `POST` request to `/` with a JSON body:
17///
18/// ```rust,no_run
19/// use rocket::local::asynchronous::{Client, LocalRequest};
20/// use rocket::http::{ContentType, Cookie};
21///
22/// # rocket::async_test(async {
23/// let client = Client::tracked(rocket::build()).await.expect("valid rocket");
24/// let req = client.post("/")
25///     .header(ContentType::JSON)
26///     .remote("127.0.0.1:8000".parse().unwrap())
27///     .cookie(("name", "value"))
28///     .body(r#"{ "value": 42 }"#);
29///
30/// let response = req.dispatch().await;
31/// # });
32/// ```
33pub struct LocalRequest<'c> {
34    pub(in super) client: &'c Client,
35    pub(in super) request: Request<'c>,
36    data: Vec<u8>,
37    // The `Origin` on the right is INVALID! It should _not_ be used!
38    uri: Result<Origin<'c>, Origin<'static>>,
39}
40
41impl<'c> LocalRequest<'c> {
42    pub(crate) fn new<'u: 'c, U>(client: &'c Client, method: Method, uri: U) -> Self
43        where U: TryInto<Origin<'u>> + fmt::Display
44    {
45        // Try to parse `uri` into an `Origin`, storing whether it's good.
46        let uri_str = uri.to_string();
47        let try_origin = uri.try_into().map_err(|_| Origin::path_only(uri_str));
48
49        // Create a request. We'll handle bad URIs later, in `_dispatch`.
50        let origin = try_origin.clone().unwrap_or_else(|bad| bad);
51        let mut request = Request::new(client.rocket(), method, origin);
52
53        // Add any cookies we know about.
54        if client.tracked {
55            client._with_raw_cookies(|jar| {
56                for cookie in jar.iter() {
57                    request.cookies_mut().add_original(cookie.clone());
58                }
59            })
60        }
61
62        LocalRequest { client, request, uri: try_origin, data: vec![] }
63    }
64
65    pub(crate) fn _request(&self) -> &Request<'c> {
66        &self.request
67    }
68
69    pub(crate) fn _request_mut(&mut self) -> &mut Request<'c> {
70        &mut self.request
71    }
72
73    pub(crate) fn _body_mut(&mut self) -> &mut Vec<u8> {
74        &mut self.data
75    }
76
77    // Performs the actual dispatch.
78    async fn _dispatch(mut self) -> LocalResponse<'c> {
79        // First, revalidate the URI, returning an error response (generated
80        // from an error catcher) immediately if it's invalid. If it's valid,
81        // then `request` already contains a correct URI.
82        let rocket = self.client.rocket();
83        if let Err(ref invalid) = self.uri {
84            // The user may have changed the URI in the request in which case we
85            // _shouldn't_ error. Check that now and error only if not.
86            if self.inner().uri() == invalid {
87                error!("invalid request URI: {:?}", invalid.path());
88                return LocalResponse::new(self.request, move |req| {
89                    rocket.handle_error(Status::BadRequest, req)
90                }).await
91            }
92        }
93
94        // Actually dispatch the request.
95        let mut data = Data::local(self.data);
96        let token = rocket.preprocess_request(&mut self.request, &mut data).await;
97        let response = LocalResponse::new(self.request, move |req| {
98            rocket.dispatch(token, req, data)
99        }).await;
100
101        // If the client is tracking cookies, updates the internal cookie jar
102        // with the changes reflected by `response`.
103        if self.client.tracked {
104            self.client._with_raw_cookies_mut(|jar| {
105                let current_time = time::OffsetDateTime::now_utc();
106                for cookie in response.cookies().iter() {
107                    if let Some(expires) = cookie.expires_datetime() {
108                        if expires <= current_time {
109                            jar.force_remove(cookie.name());
110                            continue;
111                        }
112                    }
113
114                    jar.add_original(cookie.clone());
115                }
116            })
117        }
118
119        response
120    }
121
122    pub_request_impl!("# use rocket::local::asynchronous::Client;\n\
123        use rocket::local::asynchronous::LocalRequest;" async await);
124}
125
126impl<'c> Clone for LocalRequest<'c> {
127    fn clone(&self) -> Self {
128        LocalRequest {
129            client: self.client,
130            request: self.request.clone(),
131            data: self.data.clone(),
132            uri: self.uri.clone(),
133        }
134    }
135}
136
137impl std::fmt::Debug for LocalRequest<'_> {
138    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139        self._request().fmt(f)
140    }
141}
142
143impl<'c> std::ops::Deref for LocalRequest<'c> {
144    type Target = Request<'c>;
145
146    fn deref(&self) -> &Self::Target {
147        self.inner()
148    }
149}
150
151impl<'c> std::ops::DerefMut for LocalRequest<'c> {
152    fn deref_mut(&mut self) -> &mut Self::Target {
153        self.inner_mut()
154    }
155}