rocket_community/local/asynchronous/
request.rs

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