Replace 'Manifest' with 'Cargo'.

This is largely an internal change. Prior to this commit, the 'Manifest'
type, now replaced with the 'Cargo' type, robbed responsibility from the
core 'Rocket' type. This new construction restores the previous
responsibility and makes it clear that 'Cargo' is _only_ for freezing,
and representing the stability of, Rocket's internal state.
This commit is contained in:
Sergio Benitez 2020-06-27 22:59:40 -07:00
parent dd5b518cc2
commit d89c7024ed
28 changed files with 534 additions and 404 deletions

View File

@ -118,8 +118,8 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
/// Retrieves a connection of type `Self` from the `rocket` /// Retrieves a connection of type `Self` from the `rocket`
/// instance. Returns `Some` as long as `Self::fairing()` has been /// instance. Returns `Some` as long as `Self::fairing()` has been
/// attached and there is at least one connection in the pool. /// attached and there is at least one connection in the pool.
pub fn get_one(manifest: &::rocket::Manifest) -> Option<Self> { pub fn get_one(cargo: &::rocket::Cargo) -> Option<Self> {
manifest.state::<#pool_type>() cargo.state::<#pool_type>()
.and_then(|pool| pool.0.get().ok()) .and_then(|pool| pool.0.get().ok())
.map(#guard_type) .map(#guard_type)
} }

View File

@ -239,7 +239,7 @@
//! Returns a fairing that initializes the associated database connection //! Returns a fairing that initializes the associated database connection
//! pool. //! pool.
//! //!
//! * `fn get_one(&Manifest) -> Option<Self>` //! * `fn get_one(&Cargo) -> Option<Self>`
//! //!
//! Retrieves a connection from the configured pool. Returns `Some` as long //! Retrieves a connection from the configured pool. Returns `Some` as long
//! as `Self::fairing()` has been attached and there is at least one //! as `Self::fairing()` has been attached and there is at least one

View File

@ -3,7 +3,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
use rocket::http::uncased::UncasedStr; use rocket::http::uncased::UncasedStr;
use rocket::fairing::{Fairing, Info, Kind}; use rocket::fairing::{Fairing, Info, Kind};
use rocket::{Manifest, Request, Response}; use rocket::{Cargo, Request, Response};
use crate::helmet::*; use crate::helmet::*;
@ -201,9 +201,9 @@ impl Fairing for SpaceHelmet {
self.apply(res); self.apply(res);
} }
fn on_launch(&self, manifest: &Manifest) { fn on_launch(&self, cargo: &Cargo) {
if manifest.config().tls_enabled() if cargo.config().tls_enabled()
&& !manifest.config().environment.is_dev() && !cargo.config().environment.is_dev()
&& !self.is_enabled::<Hsts>() && !self.is_enabled::<Hsts>()
{ {
warn_!("Space Helmet: deploying with TLS without enabling HSTS."); warn_!("Space Helmet: deploying with TLS without enabling HSTS.");

View File

@ -141,7 +141,7 @@ use serde_json::{Value, to_value};
use std::borrow::Cow; use std::borrow::Cow;
use std::path::PathBuf; use std::path::PathBuf;
use rocket::Manifest; use rocket::Cargo;
use rocket::request::Request; use rocket::request::Request;
use rocket::fairing::Fairing; use rocket::fairing::Fairing;
use rocket::response::{self, Content, Responder}; use rocket::response::{self, Content, Responder};
@ -340,15 +340,15 @@ impl Template {
/// ///
/// # context.insert("test", "test"); /// # context.insert("test", "test");
/// # #[allow(unused_variables)] /// # #[allow(unused_variables)]
/// let template = Template::show(client.manifest(), "index", context); /// let template = Template::show(client.cargo(), "index", context);
/// # }); /// # });
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn show<S, C>(manifest: &Manifest, name: S, context: C) -> Option<String> pub fn show<S, C>(cargo: &Cargo, name: S, context: C) -> Option<String>
where S: Into<Cow<'static, str>>, C: Serialize where S: Into<Cow<'static, str>>, C: Serialize
{ {
let ctxt = manifest.state::<ContextManager>().map(ContextManager::context).or_else(|| { let ctxt = cargo.state::<ContextManager>().map(ContextManager::context).or_else(|| {
warn!("Uninitialized template context: missing fairing."); warn!("Uninitialized template context: missing fairing.");
info!("To use templates, you must attach `Template::fairing()`."); info!("To use templates, you must attach `Template::fairing()`.");
info!("See the `Template` documentation for more information."); info!("See the `Template` documentation for more information.");

View File

@ -52,17 +52,17 @@ mod templates_tests {
#[rocket::async_test] #[rocket::async_test]
async fn test_tera_templates() { async fn test_tera_templates() {
let mut rocket = rocket(); let mut rocket = rocket();
let manifest = rocket.inspect().await; let cargo = rocket.inspect().await;
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("title", "_test_"); map.insert("title", "_test_");
map.insert("content", "<script />"); map.insert("content", "<script />");
// Test with a txt file, which shouldn't escape. // Test with a txt file, which shouldn't escape.
let template = Template::show(manifest, "tera/txt_test", &map); let template = Template::show(cargo, "tera/txt_test", &map);
assert_eq!(template, Some(UNESCAPED_EXPECTED.into())); assert_eq!(template, Some(UNESCAPED_EXPECTED.into()));
// Now with an HTML file, which should. // Now with an HTML file, which should.
let template = Template::show(manifest, "tera/html_test", &map); let template = Template::show(cargo, "tera/html_test", &map);
assert_eq!(template, Some(ESCAPED_EXPECTED.into())); assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
} }
@ -97,13 +97,13 @@ mod templates_tests {
#[rocket::async_test] #[rocket::async_test]
async fn test_handlebars_templates() { async fn test_handlebars_templates() {
let mut rocket = rocket(); let mut rocket = rocket();
let manifest = rocket.inspect().await; let cargo = rocket.inspect().await;
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("title", "_test_"); map.insert("title", "_test_");
map.insert("content", "<script /> hi"); map.insert("content", "<script /> hi");
// Test with a txt file, which shouldn't escape. // Test with a txt file, which shouldn't escape.
let template = Template::show(manifest, "hbs/test", &map); let template = Template::show(cargo, "hbs/test", &map);
assert_eq!(template, Some(EXPECTED.into())); assert_eq!(template, Some(EXPECTED.into()));
} }
@ -153,7 +153,7 @@ mod templates_tests {
} }
// verify that the initial content is correct // verify that the initial content is correct
let initial_rendered = Template::show(client.manifest(), RELOAD_TEMPLATE, ()); let initial_rendered = Template::show(client.cargo(), RELOAD_TEMPLATE, ());
assert_eq!(initial_rendered, Some(INITIAL_TEXT.into())); assert_eq!(initial_rendered, Some(INITIAL_TEXT.into()));
// write a change to the file // write a change to the file
@ -164,7 +164,7 @@ mod templates_tests {
client.get("/").dispatch().await; client.get("/").dispatch().await;
// if the new content is correct, we are done // if the new content is correct, we are done
let new_rendered = Template::show(client.manifest(), RELOAD_TEMPLATE, ()); let new_rendered = Template::show(client.cargo(), RELOAD_TEMPLATE, ());
if new_rendered == Some(NEW_TEXT.into()) { if new_rendered == Some(NEW_TEXT.into()) {
write_file(&reload_path, INITIAL_TEXT); write_file(&reload_path, INITIAL_TEXT);
return; return;

View File

@ -73,6 +73,7 @@ impl EntryAttr for Launch {
___rocket ___rocket
}); });
// FIXME: Don't duplicate the `#block` here!
let (vis, mut sig) = (&f.vis, f.sig.clone()); let (vis, mut sig) = (&f.vis, f.sig.clone());
sig.ident = syn::Ident::new("main", sig.ident.span()); sig.ident = syn::Ident::new("main", sig.ident.span());
sig.output = syn::ReturnType::Default; sig.output = syn::ReturnType::Default;

View File

@ -114,7 +114,7 @@ mod benches {
// Hold all of the requests we're going to make during the benchmark. // Hold all of the requests we're going to make during the benchmark.
let mut requests = vec![]; let mut requests = vec![];
for route in client.manifest().routes() { for route in client.cargo().routes() {
let request = client.req(route.method, route.uri.path()); let request = client.req(route.method, route.uri.path());
requests.push(request); requests.push(request);
} }

View File

@ -123,6 +123,15 @@ impl fmt::Display for Catcher {
} }
} }
impl fmt::Debug for Catcher {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Catcher")
.field("code", &self.code)
.field("default", &self.is_default)
.finish()
}
}
macro_rules! error_page_template { macro_rules! error_page_template {
($code:expr, $name:expr, $description:expr) => ( ($code:expr, $name:expr, $description:expr) => (
concat!(r#" concat!(r#"

View File

@ -2,7 +2,7 @@ use std::io::{self, Cursor};
use std::pin::Pin; use std::pin::Pin;
use std::task::{Poll, Context}; use std::task::{Poll, Context};
use futures::{ready, future::BoxFuture, stream::Stream}; use futures::{ready, Future, future::BoxFuture, stream::Stream};
use tokio::io::{AsyncRead, AsyncReadExt as _}; use tokio::io::{AsyncRead, AsyncReadExt as _};
use crate::http::hyper; use crate::http::hyper;
@ -111,3 +111,53 @@ impl AsyncRead for AsyncReadBody {
} }
} }
} }
// The code below was adapted from the `replace_with` crate and reproduced here
// under the rights granted by the MIT license. The code is copyright the
// `replace_with` developers. See LICENSE-MIT for the full text.
struct OnUnwind<F: FnOnce()>(std::mem::ManuallyDrop<F>);
impl<F: FnOnce()> Drop for OnUnwind<F> {
#[inline(always)]
fn drop(&mut self) {
(unsafe { std::ptr::read(&*self.0) })();
}
}
#[inline(always)]
pub async fn async_on_unwind<F, Fut, T, P: FnOnce()>(f: F, p: P) -> T
where F: FnOnce() -> Fut, Fut: Future<Output = T>,
{
let x = OnUnwind(std::mem::ManuallyDrop::new(p));
let t = f().await;
let _ = unsafe { std::ptr::read(&*x.0) };
std::mem::forget(x);
t
}
#[inline]
pub async fn async_replace_with_or_else<T, Fut, F, D>(dest: &mut T, d: D, f: F)
where Fut: Future<Output = T>,
F: FnOnce(T) -> Fut,
D: FnOnce() -> T,
{
unsafe {
let old = std::ptr::read(dest);
let new = async_on_unwind(
|| async move { f(old).await },
|| std::ptr::write(dest, d()),
).await;
std::ptr::write(dest, new);
}
}
#[inline]
pub async fn async_replace_with<T, Fut, F>(dest: &mut T, f: F)
where Fut: Future<Output = T>,
F: FnOnce(T) -> Fut,
{
async_replace_with_or_else(dest, || std::process::abort(), f).await
}

View File

@ -2,7 +2,7 @@ use std::sync::Mutex;
use futures::future::{Future, BoxFuture}; use futures::future::{Future, BoxFuture};
use crate::{Manifest, Rocket, Request, Response, Data}; use crate::{Cargo, Rocket, Request, Response, Data};
use crate::fairing::{Fairing, Kind, Info}; use crate::fairing::{Fairing, Kind, Info};
/// A ad-hoc fairing that can be created from a function or closure. /// A ad-hoc fairing that can be created from a function or closure.
@ -67,7 +67,7 @@ enum AdHocKind {
-> BoxFuture<'static, Result<Rocket, Rocket>> + Send + 'static>>>), -> BoxFuture<'static, Result<Rocket, Rocket>> + Send + 'static>>>),
/// An ad-hoc **launch** fairing. Called just before Rocket launches. /// An ad-hoc **launch** fairing. Called just before Rocket launches.
Launch(Mutex<Option<Box<dyn FnOnce(&Manifest) + Send + 'static>>>), Launch(Mutex<Option<Box<dyn FnOnce(&Cargo) + Send + 'static>>>),
/// An ad-hoc **request** fairing. Called when a request is received. /// An ad-hoc **request** fairing. Called when a request is received.
Request(Box<dyn for<'a> Fn(&'a mut Request<'_>, &'a Data) Request(Box<dyn for<'a> Fn(&'a mut Request<'_>, &'a Data)
@ -116,7 +116,7 @@ impl AdHoc {
/// }); /// });
/// ``` /// ```
pub fn on_launch<F: Send + 'static>(name: &'static str, f: F) -> AdHoc pub fn on_launch<F: Send + 'static>(name: &'static str, f: F) -> AdHoc
where F: FnOnce(&Manifest) where F: FnOnce(&Cargo)
{ {
AdHoc { name, kind: AdHocKind::Launch(Mutex::new(Some(Box::new(f)))) } AdHoc { name, kind: AdHocKind::Launch(Mutex::new(Some(Box::new(f)))) }
} }
@ -204,11 +204,11 @@ impl Fairing for AdHoc {
} }
} }
fn on_launch(&self, manifest: &Manifest) { fn on_launch(&self, state: &Cargo) {
if let AdHocKind::Launch(ref mutex) = self.kind { if let AdHocKind::Launch(ref mutex) = self.kind {
let mut opt = mutex.lock().expect("AdHoc::Launch lock"); let mut opt = mutex.lock().expect("AdHoc::Launch lock");
let f = opt.take().expect("internal error: `on_launch` one-call invariant broken"); let f = opt.take().expect("internal error: `on_launch` one-call invariant broken");
f(manifest) f(state)
} }
} }

View File

@ -1,4 +1,4 @@
use crate::{Manifest, Rocket, Request, Response, Data}; use crate::{Cargo, Rocket, Request, Response, Data};
use crate::fairing::{Fairing, Kind}; use crate::fairing::{Fairing, Kind};
use crate::logger::PaintExt; use crate::logger::PaintExt;
@ -52,9 +52,9 @@ impl Fairings {
} }
#[inline(always)] #[inline(always)]
pub fn handle_launch(&self, manifest: &Manifest) { pub fn handle_launch(&self, cargo: &Cargo) {
for &i in &self.launch { for &i in &self.launch {
self.all_fairings[i].on_launch(manifest); self.all_fairings[i].on_launch(cargo);
} }
} }

View File

@ -22,6 +22,7 @@ use std::ops::BitOr;
/// } /// }
/// # ; /// # ;
/// ``` /// ```
#[derive(Debug)]
pub struct Info { pub struct Info {
/// The name of the fairing. /// The name of the fairing.
pub name: &'static str, pub name: &'static str,

View File

@ -47,7 +47,7 @@
//! of other `Fairings` are not jeopardized. For instance, unless it is made //! of other `Fairings` are not jeopardized. For instance, unless it is made
//! abundantly clear, a fairing should not rewrite every request. //! abundantly clear, a fairing should not rewrite every request.
use crate::{Manifest, Rocket, Request, Response, Data}; use crate::{Cargo, Rocket, Request, Response, Data};
mod fairings; mod fairings;
mod ad_hoc; mod ad_hoc;
@ -196,7 +196,7 @@ pub use self::info_kind::{Info, Kind};
/// decorated with an attribute of `#[rocket::async_trait]`: /// decorated with an attribute of `#[rocket::async_trait]`:
/// ///
/// ```rust /// ```rust
/// use rocket::{Manifest, Rocket, Request, Data, Response}; /// use rocket::{Cargo, Rocket, Request, Data, Response};
/// use rocket::fairing::{Fairing, Info, Kind}; /// use rocket::fairing::{Fairing, Info, Kind};
/// ///
/// # struct MyType; /// # struct MyType;
@ -212,7 +212,7 @@ pub use self::info_kind::{Info, Kind};
/// # unimplemented!() /// # unimplemented!()
/// } /// }
/// ///
/// fn on_launch(&self, manifest: &Manifest) { /// fn on_launch(&self, cargo: &Cargo) {
/// /* ... */ /// /* ... */
/// # unimplemented!() /// # unimplemented!()
/// } /// }
@ -421,14 +421,14 @@ pub trait Fairing: Send + Sync + 'static {
/// ///
/// This method is called just prior to launching the application if /// This method is called just prior to launching the application if
/// `Kind::Launch` is in the `kind` field of the `Info` structure for this /// `Kind::Launch` is in the `kind` field of the `Info` structure for this
/// fairing. The `Manifest` parameter corresponds to the application that /// fairing. The `Cargo` parameter corresponds to the application that
/// will be launched. /// will be launched.
/// ///
/// ## Default Implementation /// ## Default Implementation
/// ///
/// The default implementation of this method does nothing. /// The default implementation of this method does nothing.
#[allow(unused_variables)] #[allow(unused_variables)]
fn on_launch(&self, manifest: &Manifest) {} fn on_launch(&self, cargo: &Cargo) {}
/// The request callback. /// The request callback.
/// ///
@ -470,8 +470,8 @@ impl<T: Fairing> Fairing for std::sync::Arc<T> {
} }
#[inline] #[inline]
fn on_launch(&self, manifest: &Manifest) { fn on_launch(&self, cargo: &Cargo) {
(self as &T).on_launch(manifest) (self as &T).on_launch(cargo)
} }
#[inline] #[inline]

View File

@ -139,7 +139,7 @@ mod ext;
pub use crate::router::Route; pub use crate::router::Route;
pub use crate::request::{Request, State}; pub use crate::request::{Request, State};
pub use crate::catcher::Catcher; pub use crate::catcher::Catcher;
pub use crate::rocket::{Manifest, Rocket}; pub use crate::rocket::{Cargo, Rocket};
/// Alias to [`Rocket::ignite()`] Creates a new instance of `Rocket`. /// Alias to [`Rocket::ignite()`] Creates a new instance of `Rocket`.
pub fn ignite() -> Rocket { pub fn ignite() -> Rocket {

View File

@ -1,7 +1,7 @@
use std::sync::RwLock; use std::sync::RwLock;
use std::borrow::Cow; use std::borrow::Cow;
use crate::rocket::{Rocket, Manifest}; use crate::rocket::{Rocket, Cargo};
use crate::local::LocalRequest; use crate::local::LocalRequest;
use crate::http::{Method, private::CookieJar}; use crate::http::{Method, private::CookieJar};
use crate::error::LaunchError; use crate::error::LaunchError;
@ -70,7 +70,7 @@ use crate::error::LaunchError;
/// [`put()`]: #method.put /// [`put()`]: #method.put
/// [`post()`]: #method.post /// [`post()`]: #method.post
pub struct Client { pub struct Client {
manifest: Manifest, cargo: Cargo,
pub(crate) cookies: Option<RwLock<CookieJar>>, pub(crate) cookies: Option<RwLock<CookieJar>>,
} }
@ -78,16 +78,14 @@ impl Client {
/// Constructs a new `Client`. If `tracked` is `true`, an empty `CookieJar` /// Constructs a new `Client`. If `tracked` is `true`, an empty `CookieJar`
/// is created for cookie tracking. Otherwise, the internal `CookieJar` is /// is created for cookie tracking. Otherwise, the internal `CookieJar` is
/// set to `None`. /// set to `None`.
async fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> { async fn _new(mut rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
let mut manifest = rocket.actualize_and_take_manifest().await; rocket.prelaunch_check().await?;
manifest.prelaunch_check()?;
let cookies = match tracked { let cookies = match tracked {
true => Some(RwLock::new(CookieJar::new())), true => Some(RwLock::new(CookieJar::new())),
false => None false => None
}; };
Ok(Client { manifest, cookies }) Ok(Client { cargo: rocket.into_cargo().await, cookies })
} }
/// Construct a new `Client` from an instance of `Rocket` with cookie /// Construct a new `Client` from an instance of `Rocket` with cookie
@ -153,7 +151,7 @@ impl Client {
Client::_new(rocket, false).await Client::_new(rocket, false).await
} }
/// Returns a reference to the `Manifest` of the `Rocket` this client is /// Returns a reference to the `Rocket` of the `Rocket` this client is
/// creating requests for. /// creating requests for.
/// ///
/// # Example /// # Example
@ -165,13 +163,32 @@ impl Client {
/// let my_rocket = rocket::ignite(); /// let my_rocket = rocket::ignite();
/// let client = Client::new(my_rocket).await.expect("valid rocket"); /// let client = Client::new(my_rocket).await.expect("valid rocket");
/// ///
/// // get access to the manifest within `client` /// let rocket = client.rocket();
/// let manifest = client.manifest();
/// # }); /// # });
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn manifest(&self) -> &Manifest { pub fn rocket(&self) -> &Rocket {
&self.manifest &*self.cargo
}
/// Returns a reference to the `Rocket` of the `Rocket` this client is
/// creating requests for.
///
/// # Example
///
/// ```rust
/// use rocket::local::Client;
///
/// # rocket::async_test(async {
/// let my_rocket = rocket::ignite();
/// let client = Client::new(my_rocket).await.expect("valid rocket");
///
/// let cargo = client.cargo();
/// # });
/// ```
#[inline(always)]
pub fn cargo(&self) -> &Cargo {
&self.cargo
} }
/// Create a local `GET` request to the URI `uri`. /// Create a local `GET` request to the URI `uri`.

View File

@ -105,7 +105,7 @@ impl<'c> LocalRequest<'c> {
uri: Cow<'c, str> uri: Cow<'c, str>
) -> LocalRequest<'c> { ) -> LocalRequest<'c> {
// We set a dummy string for now and check the user's URI on dispatch. // We set a dummy string for now and check the user's URI on dispatch.
let request = Request::new(client.manifest(), method, Origin::dummy()); let request = Request::new(client.rocket(), method, Origin::dummy());
// Set up any cookies we know about. // Set up any cookies we know about.
if let Some(ref jar) = client.cookies { if let Some(ref jar) = client.cookies {
@ -428,12 +428,12 @@ impl<'c> LocalRequest<'c> {
request.set_uri(uri.into_owned()); request.set_uri(uri.into_owned());
} else { } else {
error!("Malformed request URI: {}", uri); error!("Malformed request URI: {}", uri);
let res = client.manifest().handle_error(Status::BadRequest, request).await; let res = client.rocket().handle_error(Status::BadRequest, request).await;
return LocalResponse { _request: owned_request, response: res }; return LocalResponse { _request: owned_request, response: res };
} }
// Actually dispatch the request. // Actually dispatch the request.
let response = client.manifest().dispatch(request, Data::local(data)).await; let response = client.rocket().dispatch(request, Data::local(data)).await;
// If the client is tracking cookies, updates the internal cookie jar // If the client is tracking cookies, updates the internal cookie jar
// with the changes reflected by `response`. // with the changes reflected by `response`.

View File

@ -11,7 +11,7 @@ use futures::future::BoxFuture;
use crate::request::{FromParam, FromSegments, FromRequest, Outcome}; use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
use crate::request::{FromFormValue, FormItems, FormItem}; use crate::request::{FromFormValue, FormItems, FormItem};
use crate::rocket::{Rocket, Manifest}; use crate::rocket::Rocket;
use crate::router::Route; use crate::router::Route;
use crate::config::{Config, Limits}; use crate::config::{Config, Limits};
use crate::http::{hyper, uri::{Origin, Segments}}; use crate::http::{hyper, uri::{Origin, Segments}};
@ -60,7 +60,7 @@ impl<'r> Request<'r> {
/// Create a new `Request` with the given `method` and `uri`. /// Create a new `Request` with the given `method` and `uri`.
#[inline(always)] #[inline(always)]
pub(crate) fn new<'s: 'r>( pub(crate) fn new<'s: 'r>(
manifest: &'r Manifest, rocket: &'r Rocket,
method: Method, method: Method,
uri: Origin<'s> uri: Origin<'s>
) -> Request<'r> { ) -> Request<'r> {
@ -72,8 +72,8 @@ impl<'r> Request<'r> {
state: RequestState { state: RequestState {
path_segments: SmallVec::new(), path_segments: SmallVec::new(),
query_items: None, query_items: None,
config: &manifest.config, config: &rocket.config,
managed: &manifest.state, managed: &rocket.managed_state,
route: RwLock::new(None), route: RwLock::new(None),
cookies: Mutex::new(Some(CookieJar::new())), cookies: Mutex::new(Some(CookieJar::new())),
accept: Storage::new(), accept: Storage::new(),
@ -749,7 +749,7 @@ impl<'r> Request<'r> {
pub fn example<F: Fn(&mut Request<'_>)>(method: Method, uri: &str, f: F) { pub fn example<F: Fn(&mut Request<'_>)>(method: Method, uri: &str, f: F) {
let rocket = Rocket::custom(Config::development()); let rocket = Rocket::custom(Config::development());
let uri = Origin::parse(uri).expect("invalid URI in example"); let uri = Origin::parse(uri).expect("invalid URI in example");
let mut request = Request::new(rocket._manifest(), method, uri); let mut request = Request::new(&rocket, method, uri);
f(&mut request); f(&mut request);
} }
@ -832,7 +832,7 @@ impl<'r> Request<'r> {
/// Convert from Hyper types into a Rocket Request. /// Convert from Hyper types into a Rocket Request.
pub(crate) fn from_hyp( pub(crate) fn from_hyp(
manifest: &'r Manifest, rocket: &'r Rocket,
h_method: hyper::Method, h_method: hyper::Method,
h_headers: hyper::HeaderMap<hyper::HeaderValue>, h_headers: hyper::HeaderMap<hyper::HeaderValue>,
h_uri: &'r hyper::Uri, h_uri: &'r hyper::Uri,
@ -854,7 +854,7 @@ impl<'r> Request<'r> {
let uri = Origin::parse(uri).map_err(|e| e.to_string())?; let uri = Origin::parse(uri).map_err(|e| e.to_string())?;
// Construct the request object. // Construct the request object.
let mut request = Request::new(manifest, method, uri); let mut request = Request::new(rocket, method, uri);
request.set_remote(h_addr); request.set_remote(h_addr);
// Set the request cookies, if they exist. // Set the request cookies, if they exist.

View File

@ -1,6 +1,6 @@
use std::ops::Deref; use std::ops::Deref;
use crate::rocket::Manifest; use crate::rocket::Cargo;
use crate::request::{self, FromRequest, Request}; use crate::request::{self, FromRequest, Request};
use crate::outcome::Outcome; use crate::outcome::Outcome;
use crate::http::Status; use crate::http::Status;
@ -150,18 +150,18 @@ impl<'r, T: Send + Sync + 'static> State<'r, T> {
/// ///
/// # rocket::async_test(async { /// # rocket::async_test(async {
/// let mut rocket = rocket::ignite().manage(Managed(7)); /// let mut rocket = rocket::ignite().manage(Managed(7));
/// let manifest = rocket.inspect().await; /// let cargo = rocket.inspect().await;
/// ///
/// let state: Option<State<Managed>> = State::from(manifest); /// let state: Option<State<Managed>> = State::from(cargo);
/// assert_eq!(state.map(|s| s.inner()), Some(&Managed(7))); /// assert_eq!(state.map(|s| s.inner()), Some(&Managed(7)));
/// ///
/// let state: Option<State<Unmanaged>> = State::from(manifest); /// let state: Option<State<Unmanaged>> = State::from(cargo);
/// assert_eq!(state, None); /// assert_eq!(state, None);
/// # }); /// # });
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn from(manifest: &'r Manifest) -> Option<Self> { pub fn from(rocket: &'r Cargo) -> Option<Self> {
manifest.state().map(State) rocket.state().map(State)
} }
} }

View File

@ -22,7 +22,7 @@ macro_rules! assert_headers {
// Dispatch the request and check that the headers are what we expect. // Dispatch the request and check that the headers are what we expect.
let config = Config::development(); let config = Config::development();
let r = Rocket::custom(config); let r = Rocket::custom(config);
let req = Request::from_hyp(r._manifest(), h_method, h_headers, &h_uri, h_addr).unwrap(); let req = Request::from_hyp(&r, h_method, h_headers, &h_uri, h_addr).unwrap();
let actual_headers = req.headers(); let actual_headers = req.headers();
for (key, values) in expected.iter() { for (key, values) in expected.iter() {
let actual: Vec<_> = actual_headers.get(key).collect(); let actual: Vec<_> = actual_headers.get(key).collect();

View File

@ -23,7 +23,7 @@ use crate::outcome::Outcome;
use crate::error::{LaunchError, LaunchErrorKind}; use crate::error::{LaunchError, LaunchErrorKind};
use crate::fairing::{Fairing, Fairings}; use crate::fairing::{Fairing, Fairings};
use crate::logger::PaintExt; use crate::logger::PaintExt;
use crate::ext::AsyncReadExt; use crate::ext::{AsyncReadExt, async_replace_with};
use crate::shutdown::{ShutdownHandle, ShutdownHandleManaged}; use crate::shutdown::{ShutdownHandle, ShutdownHandleManaged};
use crate::http::{Method, Status, Header}; use crate::http::{Method, Status, Header};
@ -34,39 +34,135 @@ use crate::http::uri::Origin;
/// The main `Rocket` type: used to mount routes and catchers and launch the /// The main `Rocket` type: used to mount routes and catchers and launch the
/// application. /// application.
pub struct Rocket { pub struct Rocket {
pub(crate) manifest: Option<Manifest>, manifest: Vec<PreLaunchOp>,
pending: Vec<BuildOperation>,
}
enum BuildOperation {
Mount(Origin<'static>, Vec<Route>),
Register(Vec<Catcher>),
Manage(Box<dyn FnOnce(Manifest) -> Manifest + Send + Sync + 'static>),
Attach(Box<dyn Fairing>),
}
/// The state of an unlaunched [`Rocket`].
///
/// A `Manifest` includes configuration, managed state, and mounted routes and
/// can be accessed through [`Rocket::inspect()`] before launching.
pub struct Manifest {
pub(crate) config: Config, pub(crate) config: Config,
router: Router, router: Router,
default_catchers: HashMap<u16, Catcher>, default_catchers: HashMap<u16, Catcher>,
catchers: HashMap<u16, Catcher>, catchers: HashMap<u16, Catcher>,
pub(crate) state: Container, pub(crate) managed_state: Container,
fairings: Fairings, fairings: Fairings,
shutdown_handle: ShutdownHandle, shutdown_handle: ShutdownHandle,
shutdown_receiver: Option<mpsc::Receiver<()>>, shutdown_receiver: Option<mpsc::Receiver<()>>,
} }
// This function tries to hide all of the Hyper-ness from Rocket. It /// An operation that occurs prior to launching a Rocket instance.
// essentially converts Hyper types into Rocket types, then calls the enum PreLaunchOp {
// `dispatch` function, which knows nothing about Hyper. Because responding Mount(Origin<'static>, Vec<Route>),
// depends on the `HyperResponse` type, this function does the actual Register(Vec<Catcher>),
// response processing. Manage(&'static str, Box<dyn FnOnce(&mut Container) + Send + Sync + 'static>),
Attach(Box<dyn Fairing>),
}
/// A frozen view into the contents of an instance of `Rocket`.
///
/// Obtained via [`Rocket::inspect()`].
#[repr(transparent)]
pub struct Cargo(Rocket);
impl Rocket {
#[inline]
fn _mount(&mut self, base: Origin<'static>, routes: Vec<Route>) {
info!("{}{} {}{}",
Paint::emoji("🛰 "),
Paint::magenta("Mounting"),
Paint::blue(&base),
Paint::magenta(":"));
for mut route in routes {
let path = route.uri.clone();
if let Err(e) = route.set_uri(base.clone(), path) {
error_!("{}", e);
panic!("Invalid route URI.");
}
info_!("{}", route);
self.router.add(route);
}
}
#[inline]
fn _register(&mut self, catchers: Vec<Catcher>) {
info!("{}{}", Paint::emoji("👾 "), Paint::magenta("Catchers:"));
for c in catchers {
if self.catchers.get(&c.code).map_or(false, |e| !e.is_default) {
info_!("{} {}", c, Paint::yellow("(warning: duplicate catcher!)"));
} else {
info_!("{}", c);
}
self.catchers.insert(c.code, c);
}
}
#[inline]
async fn _attach(mut self, fairing: Box<dyn Fairing>) -> Self {
// Attach (and run attach-) fairings, which requires us to move `self`.
let mut fairings = mem::replace(&mut self.fairings, Fairings::new());
self = fairings.attach(fairing, self).await;
// Note that `self.fairings` may now be non-empty! Move them to the end.
fairings.append(self.fairings);
self.fairings = fairings;
self
}
// Instead of requiring the user to individually `await` each call to
// `attach()`, some operations are queued in `self.pending`. Functions that
// want to provide read access to any data from the Cargo, such as
// `inspect()`, need to apply those pending operations first.
//
// This function returns a future that executes those pending operations,
// requiring only a single `await` at the call site. After completion,
// `self.pending` will be empty and `self.manifest` will reflect all pending
// changes.
//
// Note that this returns a boxed future, because `_attach()` calls this
// function again creating a cycle.
fn actualize_manifest(&mut self) -> BoxFuture<'_, ()> {
Box::pin(async move {
// Note: attach fairings may add more ops to the `manifest`! We
// process them as a stack to maintain proper ordering.
let mut manifest = mem::replace(&mut self.manifest, vec![]);
while !manifest.is_empty() {
trace_!("[MANIEST PROGRESS]: {:?}", manifest);
match manifest.remove(0) {
PreLaunchOp::Manage(_, callback) => callback(&mut self.managed_state),
PreLaunchOp::Mount(base, routes) => self._mount(base, routes),
PreLaunchOp::Register(catchers) => self._register(catchers),
PreLaunchOp::Attach(fairing) => {
async_replace_with(self, |rocket| rocket._attach(fairing)).await;
self.manifest.append(&mut manifest);
manifest = mem::replace(&mut self.manifest, vec![]);
}
}
}
})
}
pub(crate) async fn into_cargo(mut self) -> Cargo {
self.actualize_manifest().await;
Cargo(self)
}
fn cargo(&self) -> &Cargo {
if !self.manifest.is_empty() {
panic!("internal error: immutable launch state with manifest");
}
// This is simply a `newtype`-like transformation. The
// `repr(transparent)` on `Cargo` ensures that this is safe and
// correct. Note that this exact pattern appears often in `std`.
unsafe { &*(self as *const Rocket as *const Cargo) }
}
}
// This function tries to hide all of the Hyper-ness from Rocket. It essentially
// converts Hyper types into Rocket types, then calls the `dispatch` function,
// which knows nothing about Hyper. Because responding depends on the
// `HyperResponse` type, this function does the actual response processing.
fn hyper_service_fn( fn hyper_service_fn(
rocket: Arc<Manifest>, rocket: Arc<Rocket>,
h_addr: std::net::SocketAddr, h_addr: std::net::SocketAddr,
hyp_req: hyper::Request<hyper::Body>, hyp_req: hyper::Request<hyper::Body>,
) -> impl Future<Output = Result<hyper::Response<hyper::Body>, io::Error>> { ) -> impl Future<Output = Result<hyper::Response<hyper::Body>, io::Error>> {
@ -109,7 +205,7 @@ fn hyper_service_fn(
} }
} }
impl Manifest { impl Rocket {
#[inline] #[inline]
async fn issue_response( async fn issue_response(
&self, &self,
@ -180,9 +276,7 @@ impl Manifest {
Ok(()) Ok(())
} }
}
impl Manifest {
/// Preprocess the request for Rocket things. Currently, this means: /// Preprocess the request for Rocket things. Currently, this means:
/// ///
/// * Rewriting the method in the request if _method form field exists. /// * Rewriting the method in the request if _method form field exists.
@ -209,45 +303,6 @@ impl Manifest {
} }
} }
#[inline]
pub(crate) fn dispatch<'s, 'r: 's>(
&'s self,
request: &'r mut Request<'s>,
data: Data
) -> impl Future<Output = Response<'r>> + 's {
async move {
info!("{}:", request);
// Do a bit of preprocessing before routing.
self.preprocess_request(request, &data);
// Run the request fairings.
self.fairings.handle_request(request, &data).await;
// Remember if the request is a `HEAD` request for later body stripping.
let was_head_request = request.method() == Method::Head;
// Route the request and run the user's handlers.
let mut response = self.route_and_process(request, data).await;
// Add a default 'Server' header if it isn't already there.
// TODO: If removing Hyper, write out `Date` header too.
if !response.headers().contains("Server") {
response.set_header(Header::new("Server", "Rocket"));
}
// Run the response fairings.
self.fairings.handle_response(request, &mut response).await;
// Strip the body if this is a `HEAD` request.
if was_head_request {
response.strip_body();
}
response
}
}
/// Route the request and process the outcome to eventually get a response. /// Route the request and process the outcome to eventually get a response.
fn route_and_process<'s, 'r: 's>( fn route_and_process<'s, 'r: 's>(
&'s self, &'s self,
@ -328,6 +383,45 @@ impl Manifest {
} }
} }
#[inline]
pub(crate) fn dispatch<'s, 'r: 's>(
&'s self,
request: &'r mut Request<'s>,
data: Data
) -> impl Future<Output = Response<'r>> + 's {
async move {
info!("{}:", request);
// Do a bit of preprocessing before routing.
self.preprocess_request(request, &data);
// Run the request fairings.
self.fairings.handle_request(request, &data).await;
// Remember if the request is `HEAD` for later body stripping.
let was_head_request = request.method() == Method::Head;
// Route the request and run the user's handlers.
let mut response = self.route_and_process(request, data).await;
// Add a default 'Server' header if it isn't already there.
// TODO: If removing Hyper, write out `Date` header too.
if !response.headers().contains("Server") {
response.set_header(Header::new("Server", "Rocket"));
}
// Run the response fairings.
self.fairings.handle_response(request, &mut response).await;
// Strip the body if this is a `HEAD` request.
if was_head_request {
response.strip_body();
}
response
}
}
// Finds the error catcher for the status `status` and executes it for the // Finds the error catcher for the status `status` and executes it for the
// given request `req`. If a user has registered a catcher for `status`, the // given request `req`. If a user has registered a catcher for `status`, the
// catcher is called. If the catcher fails to return a good response, the // catcher is called. If the catcher fails to return a good response, the
@ -364,104 +458,23 @@ impl Manifest {
} }
} }
} }
}
impl Manifest {
#[inline]
fn _mount(mut self, base: Origin<'static>, routes: Vec<Route>) -> Self {
info!("{}{} {}{}",
Paint::emoji("🛰 "),
Paint::magenta("Mounting"),
Paint::blue(&base),
Paint::magenta(":"));
for mut route in routes {
let path = route.uri.clone();
if let Err(e) = route.set_uri(base.clone(), path) {
error_!("{}", e);
panic!("Invalid route URI.");
}
info_!("{}", route);
self.router.add(route);
}
self
}
#[inline]
fn _register(mut self, catchers: Vec<Catcher>) -> Self {
info!("{}{}", Paint::emoji("👾 "), Paint::magenta("Catchers:"));
for c in catchers {
if self.catchers.get(&c.code).map_or(false, |e| !e.is_default) {
info_!("{} {}", c, Paint::yellow("(warning: duplicate catcher!)"));
} else {
info_!("{}", c);
}
self.catchers.insert(c.code, c);
}
self
}
#[inline]
fn _manage(self, callback: Box<dyn FnOnce(Manifest) -> Manifest>) -> Self {
callback(self)
}
#[inline]
async fn _attach(mut self, fairing: Box<dyn Fairing>) -> Self {
// Attach (and run attach) fairings, which requires us to move `self`.
let mut fairings = mem::replace(&mut self.fairings, Fairings::new());
let mut rocket = Rocket { manifest: Some(self), pending: vec![] };
rocket = fairings.attach(fairing, rocket).await;
self = rocket.actualize_and_take_manifest().await;
// Make sure we keep all fairings around: the old and newly added ones!
fairings.append(self.fairings);
self.fairings = fairings;
self
}
pub(crate) fn prelaunch_check(&mut self) -> Result<(), LaunchError> {
if let Err(e) = self.router.collisions() {
return Err(LaunchError::new(LaunchErrorKind::Collision(e)));
}
if let Some(failures) = self.fairings.failures() {
return Err(LaunchError::new(LaunchErrorKind::FailedFairings(failures.to_vec())))
}
Ok(())
}
// TODO.async: Solidify the Listener APIs and make this function public // TODO.async: Solidify the Listener APIs and make this function public
async fn listen_on<L>(mut self, listener: L) -> Result<(), crate::error::Error> async fn listen_on<L>(mut self, listener: L) -> Result<(), crate::error::Error>
where where L: Listener + Send + Unpin + 'static,
L: Listener + Send + Unpin + 'static,
<L as Listener>::Connection: Send + Unpin + 'static, <L as Listener>::Connection: Send + Unpin + 'static,
{ {
self.fairings.pretty_print_counts();
// Determine the address and port we actually binded to. // Determine the address and port we actually binded to.
self.config.port = listener.local_addr().map(|a| a.port()).unwrap_or(0); self.config.port = listener.local_addr().map(|a| a.port()).unwrap_or(0);
let proto = self.config.tls.as_ref().map_or("http://", |_| "https://"); let proto = self.config.tls.as_ref().map_or("http://", |_| "https://");
let full_addr = format!("{}:{}", self.config.address, self.config.port); let full_addr = format!("{}:{}", self.config.address, self.config.port);
// Set the keep-alive.
// TODO.async: implement keep-alive in Listener
// let timeout = self.config.keep_alive.map(|s| Duration::from_secs(s as u64));
// listener.set_keepalive(timeout);
// Freeze managed state for synchronization-free accesses later. // Freeze managed state for synchronization-free accesses later.
self.state.freeze(); self.managed_state.freeze();
// Run the launch fairings. // Run the launch fairings.
self.fairings.handle_launch(&self); self.fairings.pretty_print_counts();
self.fairings.handle_launch(self.cargo());
launch_info!("{}{} {}{}", launch_info!("{}{} {}{}",
Paint::emoji("🚀 "), Paint::emoji("🚀 "),
@ -472,6 +485,11 @@ impl Manifest {
// Restore the log level back to what it originally was. // Restore the log level back to what it originally was.
logger::pop_max_level(); logger::pop_max_level();
// Set the keep-alive.
// TODO.async: implement keep-alive in Listener
// let timeout = self.config.keep_alive.map(|s| Duration::from_secs(s as u64));
// listener.set_keepalive(timeout);
// We need to get this before moving `self` into an `Arc`. // We need to get this before moving `self` into an `Arc`.
let mut shutdown_receiver = self.shutdown_receiver let mut shutdown_receiver = self.shutdown_receiver
.take().expect("shutdown receiver has already been used"); .take().expect("shutdown receiver has already been used");
@ -614,22 +632,20 @@ impl Rocket {
Paint::default(LoggedValue(value)).bold()); Paint::default(LoggedValue(value)).bold());
} }
let managed_state = Container::new();
let (shutdown_sender, shutdown_receiver) = mpsc::channel(1); let (shutdown_sender, shutdown_receiver) = mpsc::channel(1);
let shutdown_handle = ShutdownHandle(shutdown_sender);
managed_state.set(ShutdownHandleManaged(shutdown_handle.clone()));
let manifest = Manifest { Rocket {
config, config, managed_state, shutdown_handle,
manifest: vec![],
router: Router::new(), router: Router::new(),
default_catchers: catcher::defaults::get(), default_catchers: catcher::defaults::get(),
catchers: catcher::defaults::get(), catchers: catcher::defaults::get(),
state: Container::new(),
fairings: Fairings::new(), fairings: Fairings::new(),
shutdown_handle: ShutdownHandle(shutdown_sender),
shutdown_receiver: Some(shutdown_receiver), shutdown_receiver: Some(shutdown_receiver),
}; }
manifest.state.set(ShutdownHandleManaged(manifest.shutdown_handle.clone()));
Rocket { manifest: Some(manifest), pending: vec![] }
} }
/// Mounts all of the routes in the supplied vector at the given `base` /// Mounts all of the routes in the supplied vector at the given `base`
@ -697,7 +713,7 @@ impl Rocket {
panic!("Invalid mount point."); panic!("Invalid mount point.");
} }
self.pending.push(BuildOperation::Mount(base_uri, routes.into())); self.manifest.push(PreLaunchOp::Mount(base_uri, routes.into()));
self self
} }
@ -727,7 +743,7 @@ impl Rocket {
/// ``` /// ```
#[inline] #[inline]
pub fn register(mut self, catchers: Vec<Catcher>) -> Self { pub fn register(mut self, catchers: Vec<Catcher>) -> Self {
self.pending.push(BuildOperation::Register(catchers)); self.manifest.push(PreLaunchOp::Register(catchers));
self self
} }
@ -768,13 +784,12 @@ impl Rocket {
/// ``` /// ```
#[inline] #[inline]
pub fn manage<T: Send + Sync + 'static>(mut self, state: T) -> Self { pub fn manage<T: Send + Sync + 'static>(mut self, state: T) -> Self {
self.pending.push(BuildOperation::Manage(Box::new(|rocket| { let type_name = std::any::type_name::<T>();
if !rocket.state.set::<T>(state) { self.manifest.push(PreLaunchOp::Manage(type_name, Box::new(move |managed| {
error!("State for this type is already being managed!"); if !managed.set::<T>(state) {
error!("State for type '{}' is already being managed!", type_name);
panic!("Aborting due to duplicately managed state."); panic!("Aborting due to duplicately managed state.");
} }
rocket
}))); })));
self self
@ -802,44 +817,115 @@ impl Rocket {
/// ``` /// ```
#[inline] #[inline]
pub fn attach<F: Fairing>(mut self, fairing: F) -> Self { pub fn attach<F: Fairing>(mut self, fairing: F) -> Self {
self.pending.push(BuildOperation::Attach(Box::new(fairing))); self.manifest.push(PreLaunchOp::Attach(Box::new(fairing)));
self self
} }
// Instead of requiring the user to individually `await` each call to /// Access the current state of this `Rocket` instance.
// `attach()`, some operations are queued in `self.pending`. Functions that ///
// want to provide read access to any data from the Manifest, such as /// The `Cargo` type provides methods such as [`Cargo::routes()`]
// `inspect()`, need to apply those pending operations first. /// and [`Cargo::state()`]. This method is called to get a `Cargo`
// /// instance.
// This function returns a future that executes those pending operations, ///
// requiring only a single `await` at the call site. After completion, /// # Example
// `self.pending` will be empty and `self.manifest` will reflect all pending ///
// changes. /// ```rust
// /// # rocket::async_test(async {
// Note that this returns a boxed future, because `_attach()` calls this /// let mut rocket = rocket::ignite();
// function again creating a cycle. /// let config = rocket.inspect().await.config();
pub(crate) fn actualize_manifest(&mut self) -> BoxFuture<'_, ()> { /// # let _ = config;
Box::pin(async move { /// # });
while !self.pending.is_empty() { /// ```
// We need to preserve insertion order here, pub async fn inspect(&mut self) -> &Cargo {
// so we can't use `self.pending.pop()` self.actualize_manifest().await;
let op = self.pending.remove(0); self.cargo()
let manifest = self.manifest.take()
.expect("internal error: manifest was taken and not replaced. \
Was `inspect()` called but not polled to completion?");
self.manifest = Some(match op {
BuildOperation::Mount(base, routes) => manifest._mount(base, routes),
BuildOperation::Register(catchers) => manifest._register(catchers),
BuildOperation::Manage(callback) => manifest._manage(callback),
BuildOperation::Attach(fairing) => manifest._attach(fairing).await,
});
}
})
} }
pub(crate) async fn actualize_and_take_manifest(mut self) -> Manifest { /// Returns `Some` of the managed state value for the type `T` if it is
/// being managed by `self`. Otherwise, returns `None`.
///
/// This function is equivalent to `.inspect().await.state()` and is
/// provided as a convenience.
///
/// # Example
///
/// ```rust
/// #[derive(PartialEq, Debug)]
/// struct MyState(&'static str);
///
/// # rocket::async_test(async {
/// let mut rocket = rocket::ignite().manage(MyState("hello!"));
/// assert_eq!(rocket.state::<MyState>().await, Some(&MyState("hello!")));
/// # });
/// ```
pub async fn state<T: Send + Sync + 'static>(&mut self) -> Option<&T> {
self.inspect().await.state()
}
/// Returns the active configuration.
///
/// This function is equivalent to `.inspect().await.config()` and is
/// provided as a convenience.
///
/// # Example
///
/// ```rust
/// use rocket::Rocket;
/// use rocket::fairing::AdHoc;
///
/// # rocket::async_test(async {
/// let mut rocket = rocket::ignite();
/// println!("Rocket config: {:?}", rocket.config().await);
/// # });
/// ```
pub async fn config(&mut self) -> &Config {
self.inspect().await.config()
}
/// Returns a [`ShutdownHandle`], which can be used to gracefully terminate
/// the instance of Rocket. In routes, you should use the [`ShutdownHandle`]
/// request guard.
///
/// # Example
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # use std::{thread, time::Duration};
/// #
/// # rocket::async_test(async {
/// let mut rocket = rocket::ignite();
/// let handle = rocket.inspect().await.shutdown_handle();
///
/// # if false {
/// thread::spawn(move || {
/// thread::sleep(Duration::from_secs(10));
/// handle.shutdown();
/// });
///
/// // Shuts down after 10 seconds
/// let shutdown_result = rocket.launch().await;
/// assert!(shutdown_result.is_ok());
/// # }
/// # });
/// ```
#[inline(always)]
pub fn shutdown_handle(&self) -> ShutdownHandle {
self.shutdown_handle.clone()
}
/// Perform "pre-launch" checks: verify that there are no routing colisions
/// and that there were no fairing failures.
pub(crate) async fn prelaunch_check(&mut self) -> Result<(), LaunchError> {
self.actualize_manifest().await; self.actualize_manifest().await;
self.manifest.take().expect("internal error: actualize_manifest() should have replaced self.manifest") if let Err(e) = self.router.collisions() {
return Err(LaunchError::new(LaunchErrorKind::Collision(e)));
}
if let Some(failures) = self.fairings.failures() {
return Err(LaunchError::new(LaunchErrorKind::FailedFairings(failures.to_vec())))
}
Ok(())
} }
/// Returns a `Future` that drives the server, listening for and dispatching /// Returns a `Future` that drives the server, listening for and dispatching
@ -866,28 +952,24 @@ impl Rocket {
/// # } /// # }
/// } /// }
/// ``` /// ```
pub async fn launch(self) -> Result<(), crate::error::Error> { pub async fn launch(mut self) -> Result<(), crate::error::Error> {
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use crate::error::Error::Launch; use crate::error::Error::Launch;
let mut manifest = self.actualize_and_take_manifest().await; self.prelaunch_check().await.map_err(crate::error::Error::Launch)?;
manifest.prelaunch_check().map_err(crate::error::Error::Launch)?;
let config = manifest.config(); let full_addr = format!("{}:{}", self.config.address, self.config.port);
let full_addr = format!("{}:{}", config.address, config.port); let addr = match full_addr.to_socket_addrs() {
let addrs = match full_addr.to_socket_addrs() { Ok(mut addrs) => addrs.next().expect(">= 1 socket addr"),
Ok(a) => a.collect::<Vec<_>>(), Err(e) => return Err(Launch(e.into())),
Err(e) => return Err(Launch(From::from(e))),
}; };
let addr = addrs[0];
#[cfg(feature = "ctrl_c_shutdown")] #[cfg(feature = "ctrl_c_shutdown")]
let ( let (
shutdown_handle, shutdown_handle,
(cancel_ctrl_c_listener_sender, cancel_ctrl_c_listener_receiver) (cancel_ctrl_c_listener_sender, cancel_ctrl_c_listener_receiver)
) = ( ) = (
manifest.get_shutdown_handle(), self.shutdown_handle.clone(),
oneshot::channel(), oneshot::channel(),
); );
@ -898,14 +980,13 @@ impl Rocket {
Ok(ok) => ok, Ok(ok) => ok,
Err(err) => return Err(Launch(LaunchError::new(LaunchErrorKind::Bind(err)))), Err(err) => return Err(Launch(LaunchError::new(LaunchErrorKind::Bind(err)))),
}; };
manifest.listen_on(listener) self.listen_on(listener)
}}; }};
} }
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
{ {
let config = manifest.config(); if let Some(tls) = self.config.tls.clone() {
if let Some(tls) = config.tls.clone() {
listen_on!(crate::http::tls::bind_tls(addr, tls.certs, tls.key).await).boxed() listen_on!(crate::http::tls::bind_tls(addr, tls.certs, tls.key).await).boxed()
} else { } else {
listen_on!(crate::http::private::bind_tcp(addr).await).boxed() listen_on!(crate::http::private::bind_tcp(addr).await).boxed()
@ -948,108 +1029,38 @@ impl Rocket {
server.await server.await
} }
}
pub(crate) fn _manifest(&self) -> &Manifest { impl std::fmt::Debug for PreLaunchOp {
self.manifest.as_ref().expect("internal error: manifest was taken and not replaced. \ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Was `inspect()` called but not polled to completion?") use PreLaunchOp::*;
match self {
Mount(origin, routes) => f.debug_tuple("PreLaunchOp::Mount")
.field(&origin)
.field(&routes)
.finish(),
Register(catchers) => f.debug_tuple("PreLaunchOp::Register")
.field(&catchers)
.finish(),
Manage(name, _) => f.debug_tuple("PreLaunchOp::Manage")
.field(&name)
.finish(),
Attach(fairing) => f.debug_tuple("PreLaunchOp::Attach")
.field(&fairing.info())
.finish()
} }
/// Access the current state of this `Rocket` instance.
///
/// The `Manifest` type provides methods such as [`Manifest::routes()`]
/// and [`Manifest::state()`]. This method is called to get a `Manifest`
/// instance.
///
/// # Example
///
/// ```rust
/// # rocket::async_test(async {
/// let mut rocket = rocket::ignite();
/// let config = rocket.inspect().await.config();
/// # let _ = config;
/// # });
/// ```
pub async fn inspect(&mut self) -> &Manifest {
self.actualize_manifest().await;
self._manifest()
}
/// Returns `Some` of the managed state value for the type `T` if it is
/// being managed by `self`. Otherwise, returns `None`.
///
/// This function is equivalent to `.inspect().await.state()` and is
/// provided as a convenience.
///
/// # Example
///
/// ```rust
/// #[derive(PartialEq, Debug)]
/// struct MyState(&'static str);
///
/// # rocket::async_test(async {
/// let mut rocket = rocket::ignite().manage(MyState("hello!"));
/// assert_eq!(rocket.state::<MyState>().await, Some(&MyState("hello!")));
/// # });
/// ```
pub async fn state<T: Send + Sync + 'static>(&mut self) -> Option<&T> {
self.inspect().await.state()
}
/// Returns the active configuration.
///
/// This function is equivalent to `.inspect().await.config()` and is
/// provided as a convenience.
///
/// # Example
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # #[macro_use] extern crate rocket;
/// use rocket::Rocket;
/// use rocket::fairing::AdHoc;
///
/// # rocket::async_test(async {
/// let mut rocket = rocket::ignite();
/// println!("Rocket config: {:?}", rocket.config().await);
/// # });
/// ```
pub async fn config(&mut self) -> &Config {
self.inspect().await.config()
} }
} }
impl Manifest { impl std::ops::Deref for Cargo {
/// Returns a [`ShutdownHandle`], which can be used to gracefully terminate type Target = Rocket;
/// the instance of Rocket. In routes, you should use the [`ShutdownHandle`]
/// request guard.
///
/// # Example
///
/// ```rust
/// # #![feature(proc_macro_hygiene)]
/// # use std::{thread, time::Duration};
/// #
/// # rocket::async_test(async {
/// let mut rocket = rocket::ignite();
/// let handle = rocket.inspect().await.get_shutdown_handle();
///
/// # if false {
/// thread::spawn(move || {
/// thread::sleep(Duration::from_secs(10));
/// handle.shutdown();
/// });
///
/// // Shuts down after 10 seconds
/// let shutdown_result = rocket.launch().await;
/// assert!(shutdown_result.is_ok());
/// # }
/// # });
/// ```
#[inline(always)]
pub fn get_shutdown_handle(&self) -> ShutdownHandle {
self.shutdown_handle.clone()
}
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Cargo {
/// Returns an iterator over all of the routes mounted on this instance of /// Returns an iterator over all of the routes mounted on this instance of
/// Rocket. /// Rocket.
/// ///
@ -1086,7 +1097,7 @@ impl Manifest {
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn routes(&self) -> impl Iterator<Item = &Route> + '_ { pub fn routes(&self) -> impl Iterator<Item = &Route> + '_ {
self.router.routes() self.0.router.routes()
} }
/// Returns `Some` of the managed state value for the type `T` if it is /// Returns `Some` of the managed state value for the type `T` if it is
@ -1100,15 +1111,13 @@ impl Manifest {
/// ///
/// # rocket::async_test(async { /// # rocket::async_test(async {
/// let mut rocket = rocket::ignite().manage(MyState("hello!")); /// let mut rocket = rocket::ignite().manage(MyState("hello!"));
/// assert_eq!(rocket.inspect().await.state::<MyState>(), Some(&MyState("hello!"))); /// let cargo = rocket.inspect().await;
/// /// assert_eq!(cargo.state::<MyState>(), Some(&MyState("hello!")));
/// let client = rocket::local::Client::new(rocket).await.expect("valid rocket");
/// assert_eq!(client.manifest().state::<MyState>(), Some(&MyState("hello!")));
/// # }); /// # });
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn state<T: Send + Sync + 'static>(&self) -> Option<&T> { pub fn state<T: Send + Sync + 'static>(&self) -> Option<&T> {
self.state.try_get() self.0.managed_state.try_get()
} }
/// Returns the active configuration. /// Returns the active configuration.
@ -1124,13 +1133,13 @@ impl Manifest {
/// #[rocket::launch] /// #[rocket::launch]
/// fn rocket() -> rocket::Rocket { /// fn rocket() -> rocket::Rocket {
/// rocket::ignite() /// rocket::ignite()
/// .attach(AdHoc::on_launch("Config Printer", |manifest| { /// .attach(AdHoc::on_launch("Config Printer", |cargo| {
/// println!("Rocket launch config: {:?}", manifest.config()); /// println!("Rocket launch config: {:?}", cargo.config());
/// })) /// }))
/// } /// }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn config(&self) -> &Config { pub fn config(&self) -> &Config {
&self.config &self.0.config
} }
} }

View File

@ -402,7 +402,7 @@ mod tests {
where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>> where S1: Into<Option<&'static str>>, S2: Into<Option<&'static str>>
{ {
let rocket = Rocket::custom(Config::development()); let rocket = Rocket::custom(Config::development());
let mut req = Request::new(rocket._manifest(), m, Origin::dummy()); let mut req = Request::new(&rocket, m, Origin::dummy());
if let Some(mt_str) = mt1.into() { if let Some(mt_str) = mt1.into() {
if m.supports_payload() { if m.supports_payload() {
req.replace_header(mt_str.parse::<ContentType>().unwrap()); req.replace_header(mt_str.parse::<ContentType>().unwrap());
@ -469,7 +469,7 @@ mod tests {
fn req_route_path_match(a: &'static str, b: &'static str) -> bool { fn req_route_path_match(a: &'static str, b: &'static str) -> bool {
let rocket = Rocket::custom(Config::development()); let rocket = Rocket::custom(Config::development());
let req = Request::new(&rocket._manifest(), Get, Origin::parse(a).expect("valid URI")); let req = Request::new(&rocket, Get, Origin::parse(a).expect("valid URI"));
let route = Route::ranked(0, Get, b.to_string(), dummy_handler); let route = Route::ranked(0, Get, b.to_string(), dummy_handler);
route.matches(&req) route.matches(&req)
} }

View File

@ -233,7 +233,7 @@ mod test {
fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> { fn route<'a>(router: &'a Router, method: Method, uri: &str) -> Option<&'a Route> {
let rocket = Rocket::custom(Config::development()); let rocket = Rocket::custom(Config::development());
let request = Request::new(rocket._manifest(), method, Origin::parse(uri).unwrap()); let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
let matches = router.route(&request); let matches = router.route(&request);
if matches.len() > 0 { if matches.len() > 0 {
Some(matches[0]) Some(matches[0])
@ -244,7 +244,7 @@ mod test {
fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> { fn matches<'a>(router: &'a Router, method: Method, uri: &str) -> Vec<&'a Route> {
let rocket = Rocket::custom(Config::development()); let rocket = Rocket::custom(Config::development());
let request = Request::new(rocket._manifest(), method, Origin::parse(uri).unwrap()); let request = Request::new(&rocket, method, Origin::parse(uri).unwrap());
router.route(&request) router.route(&request)
} }

View File

@ -0,0 +1,43 @@
use rocket::fairing::AdHoc;
#[rocket::async_test]
async fn test_inspectable_attach_state() {
let mut rocket = rocket::ignite()
.attach(AdHoc::on_attach("Add State", |rocket| async {
Ok(rocket.manage("Hi!"))
}));
let state = rocket.inspect().await;
assert_eq!(state.state::<&'static str>(), Some(&"Hi!"));
}
#[rocket::async_test]
async fn test_inspectable_attach_state_in_future_attach() {
let mut rocket = rocket::ignite()
.attach(AdHoc::on_attach("Add State", |rocket| async {
Ok(rocket.manage("Hi!"))
}))
.attach(AdHoc::on_attach("Inspect State", |mut rocket| async {
let state = rocket.inspect().await;
assert_eq!(state.state::<&'static str>(), Some(&"Hi!"));
Ok(rocket)
}));
let _ = rocket.inspect().await;
}
#[rocket::async_test]
async fn test_attach_state_is_well_ordered() {
let mut rocket = rocket::ignite()
.attach(AdHoc::on_attach("Inspect State Pre", |mut rocket| async {
let state = rocket.inspect().await;
assert_eq!(state.state::<&'static str>(), None);
Ok(rocket)
}))
.attach(AdHoc::on_attach("Add State", |rocket| async {
Ok(rocket.manage("Hi!"))
}));
let state = rocket.inspect().await;
assert_eq!(state.state::<&'static str>(), Some(&"Hi!"));
}

View File

@ -39,11 +39,11 @@ async fn test_index() {
// Render the template with an empty context. // Render the template with an empty context.
let mut context: HashMap<&str, &str> = HashMap::new(); let mut context: HashMap<&str, &str> = HashMap::new();
let template = Template::show(client.manifest(), "index", &context).unwrap(); let template = Template::show(client.cargo(), "index", &context).unwrap();
test_body(None, template).await; test_body(None, template).await;
// Render the template with a context that contains the message. // Render the template with a context that contains the message.
context.insert("message", "Hello from Rocket!"); context.insert("message", "Hello from Rocket!");
let template = Template::show(client.manifest(), "index", &context).unwrap(); let template = Template::show(client.cargo(), "index", &context).unwrap();
test_body(Some(Cookie::new("message", "Hello from Rocket!")), template).await; test_body(Some(Cookie::new("message", "Hello from Rocket!")), template).await;
} }

View File

@ -31,7 +31,7 @@ async fn test_root() {
dispatch!(*method, "/", |client, response| { dispatch!(*method, "/", |client, response| {
let mut map = std::collections::HashMap::new(); let mut map = std::collections::HashMap::new();
map.insert("path", "/"); map.insert("path", "/");
let expected = Template::show(client.manifest(), "error/404", &map).unwrap(); let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound); assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string().await, Some(expected)); assert_eq!(response.body_string().await, Some(expected));
@ -50,7 +50,7 @@ async fn test_name() {
parent: "layout", parent: "layout",
}; };
let expected = Template::show(client.manifest(), "index", &context).unwrap(); let expected = Template::show(client.cargo(), "index", &context).unwrap();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(expected)); assert_eq!(response.body_string().await, Some(expected));
}); });
@ -63,7 +63,7 @@ async fn test_404() {
let mut map = std::collections::HashMap::new(); let mut map = std::collections::HashMap::new();
map.insert("path", "/hello/"); map.insert("path", "/hello/");
let expected = Template::show(client.manifest(), "error/404", &map).unwrap(); let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound); assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string().await, Some(expected)); assert_eq!(response.body_string().await, Some(expected));
}); });

View File

@ -1,4 +1,4 @@
use std::sync::atomic::{Ordering}; use std::sync::atomic::Ordering;
use super::{rocket, Atomics}; use super::{rocket, Atomics};
use rocket::local::Client; use rocket::local::Client;
@ -8,13 +8,13 @@ async fn test() {
let client = Client::new(rocket()).await.unwrap(); let client = Client::new(rocket()).await.unwrap();
client.get("/sync").dispatch().await; client.get("/sync").dispatch().await;
let atomics = client.manifest().state::<Atomics>().unwrap(); let atomics = client.cargo().state::<Atomics>().unwrap();
assert_eq!(atomics.uncached.load(Ordering::Relaxed), 2); assert_eq!(atomics.uncached.load(Ordering::Relaxed), 2);
assert_eq!(atomics.cached.load(Ordering::Relaxed), 1); assert_eq!(atomics.cached.load(Ordering::Relaxed), 1);
client.get("/async").dispatch().await; client.get("/async").dispatch().await;
let atomics = client.manifest().state::<Atomics>().unwrap(); let atomics = client.cargo().state::<Atomics>().unwrap();
assert_eq!(atomics.uncached.load(Ordering::Relaxed), 4); assert_eq!(atomics.uncached.load(Ordering::Relaxed), 4);
assert_eq!(atomics.cached.load(Ordering::Relaxed), 2); assert_eq!(atomics.cached.load(Ordering::Relaxed), 2);
} }

View File

@ -31,11 +31,11 @@ async fn test_raw_state_count() {
use super::{count, index}; use super::{count, index};
let mut rocket = super::rocket(); let mut rocket = super::rocket();
let manifest = rocket.inspect().await; let cargo = rocket.inspect().await;
assert_eq!(count(State::from(manifest).unwrap()), "0"); assert_eq!(count(State::from(cargo).unwrap()), "0");
assert!(index(State::from(manifest).unwrap()).0.contains("Visits: 1")); assert!(index(State::from(cargo).unwrap()).0.contains("Visits: 1"));
assert_eq!(count(State::from(manifest).unwrap()), "1"); assert_eq!(count(State::from(cargo).unwrap()), "1");
} }
// Cargo runs each test in parallel on different threads. We use all of these // Cargo runs each test in parallel on different threads. We use all of these

View File

@ -30,7 +30,7 @@ async fn test_root() {
dispatch!(*method, "/", |client, response| { dispatch!(*method, "/", |client, response| {
let mut map = std::collections::HashMap::new(); let mut map = std::collections::HashMap::new();
map.insert("path", "/"); map.insert("path", "/");
let expected = Template::show(client.manifest(), "error/404", &map).unwrap(); let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound); assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string().await, Some(expected)); assert_eq!(response.body_string().await, Some(expected));
@ -47,7 +47,7 @@ async fn test_name() {
items: vec!["One", "Two", "Three"] items: vec!["One", "Two", "Three"]
}; };
let expected = Template::show(client.manifest(), "index", &context).unwrap(); let expected = Template::show(client.cargo(), "index", &context).unwrap();
assert_eq!(response.status(), Status::Ok); assert_eq!(response.status(), Status::Ok);
assert_eq!(response.body_string().await, Some(expected)); assert_eq!(response.body_string().await, Some(expected));
}); });
@ -60,7 +60,7 @@ async fn test_404() {
let mut map = std::collections::HashMap::new(); let mut map = std::collections::HashMap::new();
map.insert("path", "/hello/"); map.insert("path", "/hello/");
let expected = Template::show(client.manifest(), "error/404", &map).unwrap(); let expected = Template::show(client.cargo(), "error/404", &map).unwrap();
assert_eq!(response.status(), Status::NotFound); assert_eq!(response.status(), Status::NotFound);
assert_eq!(response.body_string().await, Some(expected)); assert_eq!(response.body_string().await, Some(expected));
}); });