rocket_community/local/blocking/
client.rs

1use std::cell::RefCell;
2use std::fmt;
3
4use crate::http::{uri::Origin, Method};
5use crate::local::{
6    asynchronous,
7    blocking::{LocalRequest, LocalResponse},
8};
9use crate::{Error, Ignite, Orbit, Phase, Rocket};
10
11/// A `blocking` client to construct and dispatch local requests.
12///
13/// For details, see [the top-level documentation](../index.html#client). For
14/// the `async` version, see [`asynchronous::Client`].
15///
16/// ## Example
17///
18/// The following snippet creates a `Client` from a `Rocket` instance and
19/// dispatches a local `POST /` request with a body of `Hello, world!`.
20///
21/// ```rust,no_run
22/// # extern crate rocket_community as rocket;
23/// use rocket::local::blocking::Client;
24///
25/// let rocket = rocket::build();
26/// let client = Client::tracked(rocket).expect("valid rocket");
27/// let response = client.post("/")
28///     .body("Hello, world!")
29///     .dispatch();
30/// ```
31pub struct Client {
32    pub(crate) inner: Option<asynchronous::Client>,
33    runtime: RefCell<tokio::runtime::Runtime>,
34}
35
36impl Client {
37    fn _new<P: Phase>(rocket: Rocket<P>, tracked: bool, secure: bool) -> Result<Client, Error> {
38        let runtime = tokio::runtime::Builder::new_multi_thread()
39            .thread_name("rocket-local-client-worker-thread")
40            .worker_threads(1)
41            .enable_all()
42            .build()
43            .expect("create tokio runtime");
44
45        // Initialize the Rocket instance
46        let inner = Some(runtime.block_on(asynchronous::Client::_new(rocket, tracked, secure))?);
47        Ok(Self {
48            inner,
49            runtime: RefCell::new(runtime),
50        })
51    }
52
53    // WARNING: This is unstable! Do not use this method outside of Rocket!
54    #[doc(hidden)]
55    pub fn _test<T, F>(f: F) -> T
56    where
57        F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send,
58    {
59        let client = Client::debug(crate::build()).unwrap();
60        let request = client.get("/");
61        let response = request.clone().dispatch();
62        f(&client, request, response)
63    }
64
65    #[inline(always)]
66    pub(crate) fn inner(&self) -> &asynchronous::Client {
67        self.inner
68            .as_ref()
69            .expect("internal invariant broken: self.inner is Some")
70    }
71
72    #[inline(always)]
73    pub(crate) fn block_on<F, R>(&self, fut: F) -> R
74    where
75        F: std::future::Future<Output = R>,
76    {
77        self.runtime.borrow_mut().block_on(fut)
78    }
79
80    #[inline(always)]
81    fn _rocket(&self) -> &Rocket<Orbit> {
82        self.inner()._rocket()
83    }
84
85    #[inline(always)]
86    pub(crate) fn _with_raw_cookies<F, T>(&self, f: F) -> T
87    where
88        F: FnOnce(&cookie::CookieJar) -> T,
89    {
90        self.inner()._with_raw_cookies(f)
91    }
92
93    pub(crate) fn _terminate(mut self) -> Rocket<Ignite> {
94        let runtime = tokio::runtime::Builder::new_current_thread()
95            .build()
96            .unwrap();
97        let runtime = self.runtime.replace(runtime);
98        let inner = self
99            .inner
100            .take()
101            .expect("invariant broken: self.inner is Some");
102        let rocket = runtime.block_on(inner._terminate());
103        runtime.shutdown_timeout(std::time::Duration::from_secs(1));
104        rocket
105    }
106
107    #[inline(always)]
108    fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
109    where
110        U: TryInto<Origin<'u>> + fmt::Display,
111    {
112        LocalRequest::new(self, method, uri)
113    }
114
115    // Generates the public API methods, which call the private methods above.
116    pub_client_impl!("use rocket::local::blocking::Client;");
117}
118
119impl std::fmt::Debug for Client {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        self._rocket().fmt(f)
122    }
123}
124
125impl Drop for Client {
126    fn drop(&mut self) {
127        if let Some(client) = self.inner.take() {
128            self.block_on(async { drop(client) });
129        }
130    }
131}
132
133#[cfg(doctest)]
134mod doctest {
135    /// ```compile_fail
136    /// use rocket::local::blocking::Client;
137    ///
138    /// fn not_sync<T: Sync>() {};
139    /// not_sync::<Client>();
140    /// ```
141    #[allow(dead_code)]
142    fn test_not_sync() {}
143}