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 130 131 132 133 134 135 136
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};
use crate::listener::Endpoint;
/// 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,
secure: bool,
) -> Result<Client, Error> {
let mut endpoint = Endpoint::new("local client");
if secure {
endpoint = endpoint.assume_tls();
}
let rocket = rocket.local_launch(endpoint).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.deorbit()
}
// 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>();
}
}