1use std::fmt;
23use parking_lot::RwLock;
45use crate::{Rocket, Phase, Orbit, Ignite, Error};
6use crate::local::asynchronous::{LocalRequest, LocalResponse};
7use crate::http::{Method, uri::Origin, private::cookie};
89/// 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>,
52pub(in super) tracked: bool,
53}
5455impl Client {
56pub(crate) async fn _new<P: Phase>(
57 rocket: Rocket<P>,
58 tracked: bool59 ) -> Result<Client, Error> {
60let rocket = rocket.local_launch().await?;
61let cookies = RwLock::new(cookie::CookieJar::new());
62Ok(Client { rocket, cookies, tracked })
63 }
6465// WARNING: This is unstable! Do not use this method outside of Rocket!
66 // This is used by the `Client` doctests.
67#[doc(hidden)]
68pub fn _test<T, F>(f: F) -> T
69where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send70 {
71crate::async_test(async {
72let client = Client::debug(crate::build()).await.unwrap();
73let request = client.get("/");
74let response = request.clone().dispatch().await;
75f(&client, request, response)
76 })
77 }
7879#[inline(always)]
80pub(crate) fn _rocket(&self) -> &Rocket<Orbit> {
81&self.rocket
82 }
8384#[inline(always)]
85pub(crate) fn _with_raw_cookies<F, T>(&self, f: F) -> T
86where F: FnOnce(&cookie::CookieJar) -> T
87 {
88f(&*self.cookies.read())
89 }
9091#[inline(always)]
92pub(crate) fn _with_raw_cookies_mut<F, T>(&self, f: F) -> T
93where F: FnOnce(&mut cookie::CookieJar) -> T
94 {
95f(&mut *self.cookies.write())
96 }
9798#[inline(always)]
99fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
100where U: TryInto<Origin<'u>> + fmt::Display101 {
102LocalRequest::new(self, method, uri)
103 }
104105pub(crate) async fn _terminate(self) -> Rocket<Ignite> {
106let rocket = self.rocket;
107 rocket.shutdown().notify();
108 rocket.fairings.handle_shutdown(&rocket).await;
109 rocket.into_ignite()
110 }
111112// Generates the public API methods, which call the private methods above.
113pub_client_impl!("use rocket::local::asynchronous::Client;" @async await);
114}
115116impl std::fmt::Debugfor Client {
117fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118self._rocket().fmt(f)
119 }
120}
121122#[cfg(test)]
123mod test {
124#[test]
125fn test_local_client_impl_send_sync() {
126fn assert_sync_send<T: Sync + Send>() {}
127 assert_sync_send::<super::Client>();
128 }
129}