rocket/local/asynchronous/
request.rs

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