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
use std::fmt;
use std::cell::RefCell;
use std::convert::TryInto;

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

/// A `blocking` client to construct and dispatch local requests.
///
/// For details, see [the top-level documentation](../index.html#client). For
/// the `async` version, see [`asynchronous::Client`].
///
/// ## 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::blocking::Client;
///
/// let rocket = rocket::build();
/// let client = Client::tracked(rocket).expect("valid rocket");
/// let response = client.post("/")
///     .body("Hello, world!")
///     .dispatch();
/// ```
pub struct Client {
    pub(crate) inner: Option<asynchronous::Client>,
    runtime: RefCell<tokio::runtime::Runtime>,
}

impl Client {
    fn _new<P: Phase>(rocket: Rocket<P>, tracked: bool) -> Result<Client, Error> {
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .thread_name("rocket-local-client-worker-thread")
            .worker_threads(1)
            .enable_all()
            .build()
            .expect("create tokio runtime");

        // Initialize the Rocket instance
        let inner = Some(runtime.block_on(asynchronous::Client::_new(rocket, tracked))?);
        Ok(Self { inner, runtime: RefCell::new(runtime) })
    }

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

    #[inline(always)]
    pub(crate) fn inner(&self) -> &asynchronous::Client {
        self.inner.as_ref().expect("internal invariant broken: self.inner is Some")
    }

    #[inline(always)]
    pub(crate) fn block_on<F, R>(&self, fut: F) -> R
        where F: std::future::Future<Output=R>,
    {
        self.runtime.borrow_mut().block_on(fut)
    }

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

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

    #[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)
    }

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

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

impl Drop for Client {
    fn drop(&mut self) {
        let client = self.inner.take();
        self.block_on(async { drop(client) });
    }
}

#[cfg(doctest)]
mod doctest {
    /// ```compile_fail
    /// use rocket::local::blocking::Client;
    ///
    /// fn not_sync<T: Sync>() {};
    /// not_sync::<Client>();
    /// ```
    #[allow(dead_code)]
    fn test_not_sync() {}
}