logo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
use std::fmt;

use parking_lot::RwLock;

use crate::{Rocket, Phase, Orbit, Ignite, Error};
use crate::local::asynchronous::{LocalRequest, LocalResponse};
use crate::http::{Method, uri::Origin, private::cookie};

/// An `async` client to construct and dispatch local requests.
///
/// For details, see [the top-level documentation](../index.html#client).
/// For the `blocking` version, see
/// [`blocking::Client`](crate::local::blocking::Client).
///
/// ## Multithreaded Tracking Synchronization Pitfalls
///
/// Unlike its [`blocking`](crate::local::blocking) variant, this `async`
/// `Client` implements `Sync`. However, using it in a multithreaded environment
/// while tracking cookies can result in surprising, non-deterministic behavior.
/// This is because while cookie modifications are serialized, the ordering
/// depends on the ordering of request dispatch.
///
/// If possible, refrain from sharing a single instance of a tracking `Client`
/// across multiple threads. Instead, prefer to create a unique instance of
/// `Client` per thread. If this is not possible, ensure that you are not
/// depending on the ordering of cookie modifications or have arranged for
/// request dispatch to occur in a deterministic manner.
///
/// Alternatively, use an untracked client, which does not suffer from these
/// pitfalls.
///
/// ## Example
///
/// The following snippet creates a `Client` from a `Rocket` instance and
/// dispatches a local `POST /` request with a body of `Hello, world!`.
///
/// ```rust,no_run
/// use rocket::local::asynchronous::Client;
///
/// # rocket::async_test(async {
/// let rocket = rocket::build();
/// let client = Client::tracked(rocket).await.expect("valid rocket");
/// let response = client.post("/")
///     .body("Hello, world!")
///     .dispatch()
///     .await;
/// # });
/// ```
pub struct Client {
    rocket: Rocket<Orbit>,
    cookies: RwLock<cookie::CookieJar>,
    pub(in super) tracked: bool,
}

impl Client {
    pub(crate) async fn _new<P: Phase>(
        rocket: Rocket<P>,
        tracked: bool
    ) -> Result<Client, Error> {
        let rocket = rocket.local_launch().await?;
        let cookies = RwLock::new(cookie::CookieJar::new());
        Ok(Client { rocket, cookies, tracked })
    }

    // WARNING: This is unstable! Do not use this method outside of Rocket!
    // This is used by the `Client` doctests.
    #[doc(hidden)]
    pub fn _test<T, F>(f: F) -> T
        where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
    {
        crate::async_test(async {
            let client = Client::debug(crate::build()).await.unwrap();
            let request = client.get("/");
            let response = request.clone().dispatch().await;
            f(&client, request, response)
        })
    }

    #[inline(always)]
    pub(crate) fn _rocket(&self) -> &Rocket<Orbit> {
        &self.rocket
    }

    #[inline(always)]
    pub(crate) fn _with_raw_cookies<F, T>(&self, f: F) -> T
        where F: FnOnce(&cookie::CookieJar) -> T
    {
        f(&*self.cookies.read())
    }

    #[inline(always)]
    pub(crate) fn _with_raw_cookies_mut<F, T>(&self, f: F) -> T
        where F: FnOnce(&mut cookie::CookieJar) -> T
    {
        f(&mut *self.cookies.write())
    }

    #[inline(always)]
    fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
        where U: TryInto<Origin<'u>> + fmt::Display
    {
        LocalRequest::new(self, method, uri)
    }

    pub(crate) async fn _terminate(self) -> Rocket<Ignite> {
        let rocket = self.rocket;
        rocket.shutdown().notify();
        rocket.fairings.handle_shutdown(&rocket).await;
        rocket.into_ignite()
    }

    // Generates the public API methods, which call the private methods above.
    pub_client_impl!("use rocket::local::asynchronous::Client;" @async await);
}

impl std::fmt::Debug for Client {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self._rocket().fmt(f)
    }
}

#[cfg(test)]
mod test {
    #[test]
    fn test_local_client_impl_send_sync() {
        fn assert_sync_send<T: Sync + Send>() {}
        assert_sync_send::<super::Client>();
    }
}