rocket/local/asynchronous/
client.rs

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