rocket_community/local/asynchronous/
client.rs

1use std::fmt;
2
3use parking_lot::RwLock;
4
5use crate::http::{uri::Origin, Method};
6use crate::listener::Endpoint;
7use crate::local::asynchronous::{LocalRequest, LocalResponse};
8use crate::{Error, Ignite, Orbit, Phase, Rocket};
9
10/// An `async` client to construct and dispatch local requests.
11///
12/// For details, see [the top-level documentation](../index.html#client).
13/// For the `blocking` version, see
14/// [`blocking::Client`](crate::local::blocking::Client).
15///
16/// ## Multithreaded Tracking Synchronization Pitfalls
17///
18/// Unlike its [`blocking`](crate::local::blocking) variant, this `async`
19/// `Client` implements `Sync`. However, using it in a multithreaded environment
20/// while tracking cookies can result in surprising, non-deterministic behavior.
21/// This is because while cookie modifications are serialized, the ordering
22/// depends on the ordering of request dispatch.
23///
24/// If possible, refrain from sharing a single instance of a tracking `Client`
25/// across multiple threads. Instead, prefer to create a unique instance of
26/// `Client` per thread. If this is not possible, ensure that you are not
27/// depending on the ordering of cookie modifications or have arranged for
28/// request dispatch to occur in a deterministic manner.
29///
30/// Alternatively, use an untracked client, which does not suffer from these
31/// pitfalls.
32///
33/// ## Example
34///
35/// The following snippet creates a `Client` from a `Rocket` instance and
36/// dispatches a local `POST /` request with a body of `Hello, world!`.
37///
38/// ```rust,no_run
39/// # extern crate rocket_community as rocket;
40/// use rocket::local::asynchronous::Client;
41///
42/// # rocket::async_test(async {
43/// let rocket = rocket::build();
44/// let client = Client::tracked(rocket).await.expect("valid rocket");
45/// let response = client.post("/")
46///     .body("Hello, world!")
47///     .dispatch()
48///     .await;
49/// # });
50/// ```
51pub struct Client {
52    rocket: Rocket<Orbit>,
53    cookies: RwLock<cookie::CookieJar>,
54    pub(super) tracked: bool,
55}
56
57impl Client {
58    pub(crate) async fn _new<P: Phase>(
59        rocket: Rocket<P>,
60        tracked: bool,
61        secure: bool,
62    ) -> Result<Client, Error> {
63        let mut endpoint = Endpoint::new("local client");
64        if secure {
65            endpoint = endpoint.assume_tls();
66        }
67
68        let rocket = rocket.local_launch(endpoint).await?;
69        let cookies = RwLock::new(cookie::CookieJar::new());
70        Ok(Client {
71            rocket,
72            cookies,
73            tracked,
74        })
75    }
76
77    // WARNING: This is unstable! Do not use this method outside of Rocket!
78    // This is used by the `Client` doctests.
79    #[doc(hidden)]
80    pub fn _test<T, F>(f: F) -> T
81    where
82        F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send,
83    {
84        crate::async_test(async {
85            let client = Client::debug(crate::build()).await.unwrap();
86            let request = client.get("/");
87            let response = request.clone().dispatch().await;
88            f(&client, request, response)
89        })
90    }
91
92    #[inline(always)]
93    pub(crate) fn _rocket(&self) -> &Rocket<Orbit> {
94        &self.rocket
95    }
96
97    #[inline(always)]
98    pub(crate) fn _with_raw_cookies<F, T>(&self, f: F) -> T
99    where
100        F: FnOnce(&cookie::CookieJar) -> T,
101    {
102        f(&self.cookies.read())
103    }
104
105    #[inline(always)]
106    pub(crate) fn _with_raw_cookies_mut<F, T>(&self, f: F) -> T
107    where
108        F: FnOnce(&mut cookie::CookieJar) -> T,
109    {
110        f(&mut self.cookies.write())
111    }
112
113    #[inline(always)]
114    fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
115    where
116        U: TryInto<Origin<'u>> + fmt::Display,
117    {
118        LocalRequest::new(self, method, uri)
119    }
120
121    pub(crate) async fn _terminate(self) -> Rocket<Ignite> {
122        let rocket = self.rocket;
123        rocket.shutdown().notify();
124        rocket.fairings.handle_shutdown(&rocket).await;
125        rocket.deorbit()
126    }
127
128    // Generates the public API methods, which call the private methods above.
129    pub_client_impl!("use rocket::local::asynchronous::Client;" @async await);
130}
131
132impl std::fmt::Debug for Client {
133    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134        self._rocket().fmt(f)
135    }
136}
137
138#[cfg(test)]
139mod test {
140    #[test]
141    fn test_local_client_impl_send_sync() {
142        fn assert_sync_send<T: Sync + Send>() {}
143        assert_sync_send::<super::Client>();
144    }
145}