Defer execution of operations on 'Rocket' until their effects will be

observed.

This is a prerequisite for async on_attach fairings. 'Rocket' is now a
builder wrapper around the 'Manifest' type, with operations being
applied when needed by 'launch()', 'Client::new()', or 'inspect()'.
'inspect()' returns an '&Manifest', which now provides the methods that
could be called on an '&Rocket'.
This commit is contained in:
Jeb Rosen 2020-06-14 08:57:51 -07:00 committed by Sergio Benitez
parent 3796673740
commit dea940c7a8
31 changed files with 368 additions and 257 deletions

View File

@ -93,8 +93,8 @@ pub fn database_attr(attr: TokenStream, input: TokenStream) -> Result<TokenStrea
pub fn fairing() -> impl ::rocket::fairing::Fairing { pub fn fairing() -> impl ::rocket::fairing::Fairing {
use #databases::Poolable; use #databases::Poolable;
::rocket::fairing::AdHoc::on_attach(#fairing_name, |rocket| { ::rocket::fairing::AdHoc::on_attach(#fairing_name, |mut rocket| {
let pool = #databases::database_config(#name, rocket.config()) let pool = #databases::database_config(#name, rocket.inspect().config())
.map(<#conn_type>::pool); .map(<#conn_type>::pool);
match pool { match pool {
@ -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(rocket: &::rocket::Rocket) -> Option<Self> { pub fn get_one(manifest: &::rocket::Manifest) -> Option<Self> {
rocket.state::<#pool_type>() manifest.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(&Rocket) -> Option<Self>` //! * `fn get_one(&Manifest) -> 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
@ -548,16 +548,17 @@ pub enum ConfigError {
/// # .extra("databases", databases) /// # .extra("databases", databases)
/// # .expect("custom config okay"); /// # .expect("custom config okay");
/// # /// #
/// # rocket::custom(config).attach(AdHoc::on_attach("Testing", |rocket| { /// # rocket::custom(config).attach(AdHoc::on_attach("Testing", |mut rocket| {
/// # { /// # {
/// let config = database_config("my_db", rocket.config()).unwrap(); /// let manifest = rocket.inspect();
/// let config = database_config("my_db", manifest.config()).unwrap();
/// assert_eq!(config.url, "db/db.sqlite"); /// assert_eq!(config.url, "db/db.sqlite");
/// assert_eq!(config.pool_size, 25); /// assert_eq!(config.pool_size, 25);
/// ///
/// let other_config = database_config("my_other_db", rocket.config()).unwrap(); /// let other_config = database_config("my_other_db", manifest.config()).unwrap();
/// assert_eq!(other_config.url, "mysql://root:root@localhost/database"); /// assert_eq!(other_config.url, "mysql://root:root@localhost/database");
/// ///
/// let error = database_config("invalid_db", rocket.config()).unwrap_err(); /// let error = database_config("invalid_db", manifest.config()).unwrap_err();
/// assert_eq!(error, ConfigError::MissingKey); /// assert_eq!(error, ConfigError::MissingKey);
/// # } /// # }
/// # /// #

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::{Request, Response, Rocket}; use rocket::{Manifest, 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, rocket: &Rocket) { fn on_launch(&self, manifest: &Manifest) {
if rocket.config().tls_enabled() if manifest.config().tls_enabled()
&& !rocket.config().environment.is_dev() && !manifest.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

@ -151,10 +151,12 @@ impl Fairing for TemplateFairing {
/// The user's callback, if any was supplied, is called to customize the /// The user's callback, if any was supplied, is called to customize the
/// template engines. In debug mode, the `ContextManager::new` method /// template engines. In debug mode, the `ContextManager::new` method
/// initializes a directory watcher for auto-reloading of templates. /// initializes a directory watcher for auto-reloading of templates.
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { fn on_attach(&self, mut rocket: Rocket) -> Result<Rocket, Rocket> {
let mut template_root = rocket.config().root_relative(DEFAULT_TEMPLATE_DIR); let manifest = rocket.inspect();
match rocket.config().get_str("template_dir") { let config = manifest.config();
Ok(dir) => template_root = rocket.config().root_relative(dir), let mut template_root = config.root_relative(DEFAULT_TEMPLATE_DIR);
match config.get_str("template_dir") {
Ok(dir) => template_root = config.root_relative(dir),
Err(ConfigError::Missing(_)) => { /* ignore missing */ } Err(ConfigError::Missing(_)) => { /* ignore missing */ }
Err(e) => { Err(e) => {
e.pretty_print(); e.pretty_print();

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::{Rocket, State}; use rocket::{Manifest, State};
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};
@ -339,14 +339,14 @@ impl Template {
/// ///
/// # context.insert("test", "test"); /// # context.insert("test", "test");
/// # #[allow(unused_variables)] /// # #[allow(unused_variables)]
/// let template = Template::show(client.rocket(), "index", context); /// let template = Template::show(client.manifest(), "index", context);
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn show<S, C>(rocket: &Rocket, name: S, context: C) -> Option<String> pub fn show<S, C>(manifest: &Manifest, name: S, context: C) -> Option<String>
where S: Into<Cow<'static, str>>, C: Serialize where S: Into<Cow<'static, str>>, C: Serialize
{ {
let ctxt = rocket.state::<ContextManager>().map(ContextManager::context).or_else(|| { let ctxt = manifest.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

@ -33,8 +33,8 @@ mod rusqlite_integration_test {
.finalize() .finalize()
.unwrap(); .unwrap();
let rocket = rocket::custom(config).attach(SqliteDb::fairing()); let mut rocket = rocket::custom(config).attach(SqliteDb::fairing());
let mut conn = SqliteDb::get_one(&rocket).expect("unable to get connection"); let mut conn = SqliteDb::get_one(rocket.inspect()).expect("unable to get connection");
// Rusqlite's `transaction()` method takes `&mut self`; this tests the // Rusqlite's `transaction()` method takes `&mut self`; this tests the
// presence of a `DerefMut` trait on the generated connection type. // presence of a `DerefMut` trait on the generated connection type.
@ -54,8 +54,8 @@ mod rusqlite_integration_test {
.finalize() .finalize()
.unwrap(); .unwrap();
let rocket = rocket::custom(config).attach(SqliteDb::fairing()); let mut rocket = rocket::custom(config).attach(SqliteDb::fairing());
let conn = SqliteDb::get_one(&rocket).expect("unable to get connection"); let conn = SqliteDb::get_one(rocket.inspect()).expect("unable to get connection");
let _: i32 = conn.query_row("SELECT 1", &[] as &[&dyn ToSql], |row| row.get(0)).expect("get row"); let _: i32 = conn.query_row("SELECT 1", &[] as &[&dyn ToSql], |row| row.get(0)).expect("get row");
} }
} }

View File

@ -51,17 +51,18 @@ mod templates_tests {
#[test] #[test]
fn test_tera_templates() { fn test_tera_templates() {
let rocket = rocket(); let mut rocket = rocket();
let manifest = rocket.inspect();
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(&rocket, "tera/txt_test", &map); let template = Template::show(manifest, "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(&rocket, "tera/html_test", &map); let template = Template::show(manifest, "tera/html_test", &map);
assert_eq!(template, Some(ESCAPED_EXPECTED.into())); assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
} }
@ -95,13 +96,14 @@ mod templates_tests {
#[test] #[test]
fn test_handlebars_templates() { fn test_handlebars_templates() {
let rocket = rocket(); let mut rocket = rocket();
let manifest = rocket.inspect();
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(&rocket, "hbs/test", &map); let template = Template::show(manifest, "hbs/test", &map);
assert_eq!(template, Some(EXPECTED.into())); assert_eq!(template, Some(EXPECTED.into()));
} }
@ -151,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.rocket(), RELOAD_TEMPLATE, ()); let initial_rendered = Template::show(client.manifest(), 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
@ -162,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.rocket(), RELOAD_TEMPLATE, ()); let new_rendered = Template::show(client.manifest(), 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

@ -109,7 +109,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.rocket().routes() { for route in client.manifest().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

@ -174,9 +174,9 @@
//! //!
//! fn main() { //! fn main() {
//! rocket::ignite() //! rocket::ignite()
//! .attach(AdHoc::on_attach("Token Config", |rocket| { //! .attach(AdHoc::on_attach("Token Config", |mut rocket| {
//! println!("Adding token managed state from config..."); //! println!("Adding token managed state from config...");
//! let token_val = rocket.config().get_int("token").unwrap_or(-1); //! let token_val = rocket.inspect().config().get_int("token").unwrap_or(-1);
//! Ok(rocket.manage(Token(token_val))) //! Ok(rocket.manage(Token(token_val)))
//! })) //! }))
//! # ; //! # ;

View File

@ -2,7 +2,7 @@ use std::sync::Mutex;
use futures_util::future::BoxFuture; use futures_util::future::BoxFuture;
use crate::{Rocket, Request, Response, Data}; use crate::{Manifest, 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.
@ -48,7 +48,7 @@ enum AdHocKind {
/// An ad-hoc **attach** fairing. Called when the fairing is attached. /// An ad-hoc **attach** fairing. Called when the fairing is attached.
Attach(Mutex<Option<Box<dyn FnOnce(Rocket) -> Result<Rocket, Rocket> + Send + 'static>>>), Attach(Mutex<Option<Box<dyn FnOnce(Rocket) -> 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(&Rocket) + Send + 'static>>>), Launch(Mutex<Option<Box<dyn FnOnce(&Manifest) + 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) -> BoxFuture<'a, ()> + Send + Sync + 'static>), Request(Box<dyn for<'a> Fn(&'a mut Request<'_>, &'a Data) -> BoxFuture<'a, ()> + Send + Sync + 'static>),
/// An ad-hoc **response** fairing. Called when a response is ready to be /// An ad-hoc **response** fairing. Called when a response is ready to be
@ -88,7 +88,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(&Rocket) where F: FnOnce(&Manifest)
{ {
AdHoc { name, kind: AdHocKind::Launch(Mutex::new(Some(Box::new(f)))) } AdHoc { name, kind: AdHocKind::Launch(Mutex::new(Some(Box::new(f)))) }
} }
@ -163,11 +163,11 @@ impl Fairing for AdHoc {
} }
} }
fn on_launch(&self, rocket: &Rocket) { fn on_launch(&self, manifest: &Manifest) {
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(rocket) f(manifest)
} }
} }

View File

@ -1,4 +1,4 @@
use crate::{Rocket, Request, Response, Data}; use crate::{Manifest, 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, rocket: &Rocket) { pub fn handle_launch(&self, manifest: &Manifest) {
for &i in &self.launch { for &i in &self.launch {
self.all_fairings[i].on_launch(rocket); self.all_fairings[i].on_launch(manifest);
} }
} }

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::{Rocket, Request, Response, Data}; use crate::{Manifest, 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::{Rocket, Request, Data, Response}; /// use rocket::{Manifest, 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, rocket: &Rocket) { /// fn on_launch(&self, manifest: &Manifest) {
/// /* ... */ /// /* ... */
/// # 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 `&Rocket` parameter corresponds to the application that /// fairing. The `Manifest` 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, rocket: &Rocket) {} fn on_launch(&self, manifest: &Manifest) {}
/// 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, rocket: &Rocket) { fn on_launch(&self, manifest: &Manifest) {
(self as &T).on_launch(rocket) (self as &T).on_launch(manifest)
} }
#[inline] #[inline]

View File

@ -140,7 +140,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::Rocket; pub use crate::rocket::{Manifest, 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; use crate::rocket::{Rocket, Manifest};
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 {
rocket: Rocket, manifest: Manifest,
pub(crate) cookies: Option<RwLock<CookieJar>>, pub(crate) cookies: Option<RwLock<CookieJar>>,
} }
@ -79,12 +79,15 @@ impl Client {
/// 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`.
fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> { fn _new(rocket: Rocket, tracked: bool) -> Result<Client, LaunchError> {
let mut manifest = rocket.actualize_and_take_manifest();
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 { rocket: rocket.prelaunch_check()?, cookies }) Ok(Client { manifest, cookies })
} }
/// Construct a new `Client` from an instance of `Rocket` with cookie /// Construct a new `Client` from an instance of `Rocket` with cookie
@ -146,7 +149,8 @@ impl Client {
Client::_new(rocket, false) Client::_new(rocket, false)
} }
/// Returns the instance of `Rocket` this client is creating requests for. /// Returns a reference to the `Manifest` of the `Rocket` this client is
/// creating requests for.
/// ///
/// # Example /// # Example
/// ///
@ -156,12 +160,12 @@ impl Client {
/// let my_rocket = rocket::ignite(); /// let my_rocket = rocket::ignite();
/// let client = Client::new(my_rocket).expect("valid rocket"); /// let client = Client::new(my_rocket).expect("valid rocket");
/// ///
/// // get the instance of `my_rocket` within `client` /// // get access to the manifest within `client`
/// let my_rocket = client.rocket(); /// let manifest = client.manifest();
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn rocket(&self) -> &Rocket { pub fn manifest(&self) -> &Manifest {
&self.rocket &self.manifest
} }
/// Create a local `GET` request to the URI `uri`. /// Create a local `GET` request to the URI `uri`.

View File

@ -103,7 +103,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.rocket(), method, Origin::dummy()); let request = Request::new(client.manifest(), 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 {
@ -406,12 +406,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.rocket().handle_error(Status::BadRequest, request).await; let res = client.manifest().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.rocket().dispatch(request, Data::local(data)).await; let response = client.manifest().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_util::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; use crate::rocket::{Rocket, Manifest};
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>(
rocket: &'r Rocket, manifest: &'r Manifest,
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: &rocket.config, config: &manifest.config,
managed: &rocket.state, managed: &manifest.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(),
@ -738,7 +738,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, method, uri); let mut request = Request::new(rocket._manifest(), method, uri);
f(&mut request); f(&mut request);
} }
@ -821,7 +821,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(
rocket: &'r Rocket, manifest: &'r Manifest,
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,
@ -843,7 +843,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(rocket, method, uri); let mut request = Request::new(manifest, 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; use crate::rocket::Manifest;
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;
@ -99,8 +99,8 @@ use crate::http::Status;
/// state.0.to_string() /// state.0.to_string()
/// } /// }
/// ///
/// let rocket = rocket::ignite().manage(MyManagedState(127)); /// let mut rocket = rocket::ignite().manage(MyManagedState(127));
/// let state = State::from(&rocket).expect("managing `MyManagedState`"); /// let state = State::from(rocket.inspect()).expect("managing `MyManagedState`");
/// assert_eq!(handler(state), "127"); /// assert_eq!(handler(state), "127");
/// ``` /// ```
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
@ -152,17 +152,18 @@ impl<'r, T: Send + Sync + 'static> State<'r, T> {
/// #[derive(Debug, PartialEq)] /// #[derive(Debug, PartialEq)]
/// struct Unmanaged(usize); /// struct Unmanaged(usize);
/// ///
/// let rocket = rocket::ignite().manage(Managed(7)); /// let mut rocket = rocket::ignite().manage(Managed(7));
/// let manifest = rocket.inspect();
/// ///
/// let state: Option<State<Managed>> = State::from(&rocket); /// let state: Option<State<Managed>> = State::from(manifest);
/// 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(&rocket); /// let state: Option<State<Unmanaged>> = State::from(manifest);
/// assert_eq!(state, None); /// assert_eq!(state, None);
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn from(rocket: &'r Rocket) -> Option<Self> { pub fn from(manifest: &'r Manifest) -> Option<Self> {
rocket.state.try_get::<T>().map(State) manifest.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, h_method, h_headers, &h_uri, h_addr).unwrap(); let req = Request::from_hyp(r._manifest(), 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

@ -34,6 +34,22 @@ 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>,
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>,
@ -50,7 +66,7 @@ pub struct Rocket {
// depends on the `HyperResponse` type, this function does the actual // depends on the `HyperResponse` type, this function does the actual
// response processing. // response processing.
fn hyper_service_fn( fn hyper_service_fn(
rocket: Arc<Rocket>, rocket: Arc<Manifest>,
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>> {
@ -93,7 +109,7 @@ fn hyper_service_fn(
} }
} }
impl Rocket { impl Manifest {
#[inline] #[inline]
async fn issue_response( async fn issue_response(
&self, &self,
@ -162,7 +178,7 @@ impl Rocket {
} }
} }
impl Rocket { 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.
@ -346,6 +362,145 @@ impl Rocket {
} }
} }
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]
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);
self = rocket.actualize_and_take_manifest();
// 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
async fn listen_on<L>(mut self, listener: L) -> Result<(), crate::error::Error>
where
L: Listener + Send + Unpin + 'static,
<L as Listener>::Connection: Send + Unpin + 'static,
{
self.fairings.pretty_print_counts();
// Determine the address and port we actually binded to.
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 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.
self.state.freeze();
// Run the launch fairings.
self.fairings.handle_launch(&self);
launch_info!("{}{} {}{}",
Paint::emoji("🚀 "),
Paint::default("Rocket has launched from").bold(),
Paint::default(proto).bold().underline(),
Paint::default(&full_addr).bold().underline());
// Restore the log level back to what it originally was.
logger::pop_max_level();
// We need to get this before moving `self` into an `Arc`.
let mut shutdown_receiver = self.shutdown_receiver
.take().expect("shutdown receiver has already been used");
let rocket = Arc::new(self);
let service = hyper::make_service_fn(move |connection: &<L as Listener>::Connection| {
let rocket = rocket.clone();
let remote_addr = connection.remote_addr().unwrap_or_else(|| ([0, 0, 0, 0], 0).into());
async move {
Ok::<_, std::convert::Infallible>(hyper::service_fn(move |req| {
hyper_service_fn(rocket.clone(), remote_addr, req)
}))
}
});
#[derive(Clone)]
struct TokioExecutor;
impl<Fut> hyper::Executor<Fut> for TokioExecutor where Fut: Future + Send + 'static, Fut::Output: Send {
fn execute(&self, fut: Fut) {
tokio::spawn(fut);
}
}
hyper::Server::builder(Incoming::from_listener(listener))
.executor(TokioExecutor)
.serve(service)
.with_graceful_shutdown(async move { shutdown_receiver.recv().await; })
.await
.map_err(|e| crate::error::Error::Run(Box::new(e)))
}
}
impl Rocket { impl Rocket {
/// Create a new `Rocket` application using the configuration information in /// Create a new `Rocket` application using the configuration information in
/// `Rocket.toml`. If the file does not exist or if there is an I/O error /// `Rocket.toml`. If the file does not exist or if there is an I/O error
@ -457,7 +612,7 @@ impl Rocket {
let (shutdown_sender, shutdown_receiver) = mpsc::channel(1); let (shutdown_sender, shutdown_receiver) = mpsc::channel(1);
let rocket = Rocket { let manifest = Manifest {
config, config,
router: Router::new(), router: Router::new(),
default_catchers: catcher::defaults::get(), default_catchers: catcher::defaults::get(),
@ -468,9 +623,9 @@ impl Rocket {
shutdown_receiver: Some(shutdown_receiver), shutdown_receiver: Some(shutdown_receiver),
}; };
rocket.state.set(ShutdownHandleManaged(rocket.shutdown_handle.clone())); manifest.state.set(ShutdownHandleManaged(manifest.shutdown_handle.clone()));
rocket 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`
@ -529,13 +684,7 @@ impl Rocket {
/// ``` /// ```
#[inline] #[inline]
pub fn mount<R: Into<Vec<Route>>>(mut self, base: &str, routes: R) -> Self { pub fn mount<R: Into<Vec<Route>>>(mut self, base: &str, routes: R) -> Self {
info!("{}{} {}{}", let base_uri = Origin::parse_owned(base.to_string())
Paint::emoji("🛰 "),
Paint::magenta("Mounting"),
Paint::blue(base),
Paint::magenta(":"));
let base_uri = Origin::parse(base)
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
error_!("Invalid origin URI '{}' used as mount point.", base); error_!("Invalid origin URI '{}' used as mount point.", base);
panic!("Error: {}", e); panic!("Error: {}", e);
@ -546,17 +695,7 @@ impl Rocket {
panic!("Invalid mount point."); panic!("Invalid mount point.");
} }
for mut route in routes.into() { self.pending.push(BuildOperation::Mount(base_uri, routes.into()));
let path = route.uri.clone();
if let Err(e) = route.set_uri(base_uri.clone(), path) {
error_!("{}", e);
panic!("Invalid route URI.");
}
info_!("{}", route);
self.router.add(route);
}
self self
} }
@ -589,18 +728,7 @@ impl Rocket {
/// ``` /// ```
#[inline] #[inline]
pub fn register(mut self, catchers: Vec<Catcher>) -> Self { pub fn register(mut self, catchers: Vec<Catcher>) -> Self {
info!("{}{}", Paint::emoji("👾 "), Paint::magenta("Catchers:")); self.pending.push(BuildOperation::Register(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 self
} }
@ -642,11 +770,15 @@ impl Rocket {
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn manage<T: Send + Sync + 'static>(self, state: T) -> Self { pub fn manage<T: Send + Sync + 'static>(mut self, state: T) -> Self {
if !self.state.set::<T>(state) { self.pending.push(BuildOperation::Manage(Box::new(|rocket| {
error!("State for this type is already being managed!"); if !rocket.state.set::<T>(state) {
panic!("Aborting due to duplicately managed state."); error!("State for this type is already being managed!");
} panic!("Aborting due to duplicately managed state.");
}
rocket
})));
self self
} }
@ -675,105 +807,41 @@ impl Rocket {
/// ``` /// ```
#[inline] #[inline]
pub fn attach<F: Fairing>(mut self, fairing: F) -> Self { pub fn attach<F: Fairing>(mut self, fairing: F) -> Self {
// Attach (and run attach) fairings, which requires us to move `self`. self.pending.push(BuildOperation::Attach(Box::new(fairing)));
let mut fairings = mem::replace(&mut self.fairings, Fairings::new());
self = fairings.attach(Box::new(fairing), self);
// Make sure we keep all fairings around: the old and newly added ones!
fairings.append(self.fairings);
self.fairings = fairings;
self self
} }
pub(crate) fn prelaunch_check(mut self) -> Result<Rocket, LaunchError> { pub(crate) fn actualize_manifest(&mut self) {
self.router = match self.router.collisions() { while !self.pending.is_empty() {
Ok(router) => router, // We need to preserve insertion order here,
Err(e) => return Err(LaunchError::new(LaunchErrorKind::Collision(e))) // so we can't use `self.pending.pop()`
}; let op = self.pending.remove(0);
let manifest = self.manifest.take().expect("TODO error message");
if let Some(failures) = self.fairings.failures() { self.manifest = Some(match op {
return Err(LaunchError::new(LaunchErrorKind::FailedFairings(failures.to_vec()))) 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),
});
} }
Ok(self)
} }
// TODO.async: Solidify the Listener APIs and make this function public pub(crate) fn actualize_and_take_manifest(mut self) -> Manifest {
async fn listen_on<L>(mut self, listener: L) -> Result<(), crate::error::Error> self.actualize_manifest();
where self.manifest.take().expect("internal error: actualize_manifest() should have replaced self.manifest")
L: Listener + Send + Unpin + 'static,
<L as Listener>::Connection: Send + Unpin + 'static,
{
self = self.prelaunch_check().map_err(crate::error::Error::Launch)?;
self.fairings.pretty_print_counts();
// Determine the address and port we actually binded to.
self.config.port = listener.local_addr().map(|a| a.port()).unwrap_or(0);
let proto = if self.config.tls.is_some() {
"https://"
} else {
"http://"
};
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.
self.state.freeze();
// Run the launch fairings.
self.fairings.handle_launch(&self);
launch_info!("{}{} {}{}",
Paint::emoji("🚀 "),
Paint::default("Rocket has launched from").bold(),
Paint::default(proto).bold().underline(),
Paint::default(&full_addr).bold().underline());
// Restore the log level back to what it originally was.
logger::pop_max_level();
// We need to get this before moving `self` into an `Arc`.
let mut shutdown_receiver = self.shutdown_receiver
.take().expect("shutdown receiver has already been used");
let rocket = Arc::new(self);
let service = hyper::make_service_fn(move |connection: &<L as Listener>::Connection| {
let rocket = rocket.clone();
let remote_addr = connection.remote_addr().unwrap_or_else(|| ([0, 0, 0, 0], 0).into());
async move {
Ok::<_, std::convert::Infallible>(hyper::service_fn(move |req| {
hyper_service_fn(rocket.clone(), remote_addr, req)
}))
}
});
#[derive(Clone)]
struct TokioExecutor;
impl<Fut> hyper::Executor<Fut> for TokioExecutor where Fut: Future + Send + 'static, Fut::Output: Send {
fn execute(&self, fut: Fut) {
tokio::spawn(fut);
}
}
hyper::Server::builder(Incoming::from_listener(listener))
.executor(TokioExecutor)
.serve(service)
.with_graceful_shutdown(async move { shutdown_receiver.recv().await; })
.await
.map_err(|e| crate::error::Error::Run(Box::new(e)))
} }
/// Returns a `Future` that drives the server and completes when the server /// Returns a `Future` that drives the server, listening for and dispathcing
/// is shut down or errors. If the `ctrl_c_shutdown` feature is enabled, /// requests to mounted routes and catchers. The `Future` completes when the
/// the server will shut down gracefully once `Ctrl-C` is pressed. /// server is shut down (via a [`ShutdownHandle`] or encounters a fatal
/// error. If the `ctrl_c_shutdown` feature is enabled, the server will
/// also shut down once `Ctrl-C` is pressed.
///
/// # Error
/// If there is a problem starting the application, an [`Error`] is
/// returned. Note that a value of type `Error` panics if dropped
/// without first being inspected. See the [`Error`] documentation for
/// more information.
/// ///
/// # Example /// # Example
/// ///
@ -781,17 +849,22 @@ impl Rocket {
/// #[tokio::main] /// #[tokio::main]
/// async fn main() { /// async fn main() {
/// # if false { /// # if false {
/// let result = rocket::ignite().serve().await; /// let result = rocket::ignite().launch();
/// assert!(result.is_ok()); /// assert!(result.is_ok());
/// # } /// # }
/// } /// }
/// ``` /// ```
pub async fn serve(self) -> Result<(), crate::error::Error> { async fn serve(self) -> Result<(), crate::error::Error> {
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use crate::error::Error::Launch; use crate::error::Error::Launch;
let full_addr = format!("{}:{}", self.config.address, self.config.port); let mut manifest = self.actualize_and_take_manifest();
manifest.prelaunch_check().map_err(crate::error::Error::Launch)?;
let config = manifest.config();
let full_addr = format!("{}:{}", config.address, config.port);
let addrs = match full_addr.to_socket_addrs() { let addrs = match full_addr.to_socket_addrs() {
Ok(a) => a.collect::<Vec<_>>(), Ok(a) => a.collect::<Vec<_>>(),
Err(e) => return Err(Launch(From::from(e))), Err(e) => return Err(Launch(From::from(e))),
@ -803,7 +876,7 @@ impl Rocket {
shutdown_handle, shutdown_handle,
(cancel_ctrl_c_listener_sender, cancel_ctrl_c_listener_receiver) (cancel_ctrl_c_listener_sender, cancel_ctrl_c_listener_receiver)
) = ( ) = (
self.get_shutdown_handle(), manifest.get_shutdown_handle(),
oneshot::channel(), oneshot::channel(),
); );
@ -814,13 +887,14 @@ 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)))),
}; };
self.listen_on(listener) manifest.listen_on(listener)
}}; }};
} }
#[cfg(feature = "tls")] #[cfg(feature = "tls")]
{ {
if let Some(tls) = self.config.tls.clone() { let config = manifest.config();
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()
@ -887,11 +961,13 @@ impl Rocket {
/// rocket::ignite().launch(); /// rocket::ignite().launch();
/// # } /// # }
/// ``` /// ```
pub fn launch(self) -> Result<(), crate::error::Error> { pub fn launch(mut self) -> Result<(), crate::error::Error> {
let workers = self.inspect().config().workers as usize;
// Initialize the tokio runtime // Initialize the tokio runtime
let mut runtime = tokio::runtime::Builder::new() let mut runtime = tokio::runtime::Builder::new()
.threaded_scheduler() .threaded_scheduler()
.core_threads(self.config.workers as usize) .core_threads(workers)
.enable_all() .enable_all()
.build() .build()
.expect("Cannot build runtime!"); .expect("Cannot build runtime!");
@ -899,6 +975,30 @@ impl Rocket {
runtime.block_on(async move { self.serve().await }) runtime.block_on(async move { self.serve().await })
} }
pub(crate) fn _manifest(&self) -> &Manifest {
self.manifest.as_ref().expect("TODO error message")
}
/// Access the current state of this `Rocket` instance.
///
/// The `Mnaifest` type provides methods such as [`Manifest::routes`]
/// and [`Manifest::state`]. This method is called to get an `Manifest`
/// instance.
///
/// # Example
///
/// ```rust
/// let mut rocket = rocket::ignite();
/// let config = rocket.inspect().config();
/// # let _ = config;
/// ```
pub fn inspect(&mut self) -> &Manifest {
self.actualize_manifest();
self._manifest()
}
}
impl Manifest {
/// Returns a [`ShutdownHandle`], which can be used to gracefully terminate /// Returns a [`ShutdownHandle`], which can be used to gracefully terminate
/// the instance of Rocket. In routes, you should use the [`ShutdownHandle`] /// the instance of Rocket. In routes, you should use the [`ShutdownHandle`]
/// request guard. /// request guard.
@ -909,8 +1009,8 @@ impl Rocket {
/// # #![feature(proc_macro_hygiene)] /// # #![feature(proc_macro_hygiene)]
/// # use std::{thread, time::Duration}; /// # use std::{thread, time::Duration};
/// # /// #
/// let rocket = rocket::ignite(); /// let mut rocket = rocket::ignite();
/// let handle = rocket.get_shutdown_handle(); /// let handle = rocket.inspect().get_shutdown_handle();
/// ///
/// # if false { /// # if false {
/// thread::spawn(move || { /// thread::spawn(move || {
@ -945,11 +1045,11 @@ impl Rocket {
/// } /// }
/// ///
/// fn main() { /// fn main() {
/// let rocket = rocket::ignite() /// let mut rocket = rocket::ignite()
/// .mount("/", routes![hello]) /// .mount("/", routes![hello])
/// .mount("/hi", routes![hello]); /// .mount("/hi", routes![hello]);
/// ///
/// for route in rocket.routes() { /// for route in rocket.inspect().routes() {
/// match route.base() { /// match route.base() {
/// "/" => assert_eq!(route.uri.path(), "/hello"), /// "/" => assert_eq!(route.uri.path(), "/hello"),
/// "/hi" => assert_eq!(route.uri.path(), "/hi/hello"), /// "/hi" => assert_eq!(route.uri.path(), "/hi/hello"),
@ -957,11 +1057,11 @@ impl Rocket {
/// } /// }
/// } /// }
/// ///
/// assert_eq!(rocket.routes().count(), 2); /// assert_eq!(rocket.inspect().routes().count(), 2);
/// } /// }
/// ``` /// ```
#[inline(always)] #[inline(always)]
pub fn routes<'a>(&'a self) -> impl Iterator<Item = &'a Route> + 'a { pub fn routes(&self) -> impl Iterator<Item = &Route> + '_ {
self.router.routes() self.router.routes()
} }
@ -974,11 +1074,11 @@ impl Rocket {
/// #[derive(PartialEq, Debug)] /// #[derive(PartialEq, Debug)]
/// struct MyState(&'static str); /// struct MyState(&'static str);
/// ///
/// let rocket = rocket::ignite().manage(MyState("hello!")); /// let mut rocket = rocket::ignite().manage(MyState("hello!"));
/// assert_eq!(rocket.state::<MyState>(), Some(&MyState("hello!"))); /// assert_eq!(rocket.inspect().state::<MyState>(), Some(&MyState("hello!")));
/// ///
/// let client = rocket::local::Client::new(rocket).expect("valid rocket"); /// let client = rocket::local::Client::new(rocket).expect("valid rocket");
/// assert_eq!(client.rocket().state::<MyState>(), Some(&MyState("hello!"))); /// 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> {

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, m, Origin::dummy()); let mut req = Request::new(rocket._manifest(), 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, Get, Origin::parse(a).expect("valid URI")); let req = Request::new(&rocket._manifest(), 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

@ -52,7 +52,7 @@ impl Router {
matches matches
} }
pub(crate) fn collisions(mut self) -> Result<Router, Vec<(Route, Route)>> { pub(crate) fn collisions(&mut self) -> Result<(), Vec<(Route, Route)>> {
let mut collisions = vec![]; let mut collisions = vec![];
for routes in self.routes.values_mut() { for routes in self.routes.values_mut() {
for i in 0..routes.len() { for i in 0..routes.len() {
@ -72,7 +72,7 @@ impl Router {
} }
if collisions.is_empty() { if collisions.is_empty() {
Ok(self) Ok(())
} else { } else {
Err(collisions) Err(collisions)
} }
@ -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, method, Origin::parse(uri).unwrap()); let request = Request::new(rocket._manifest(), 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, method, Origin::parse(uri).unwrap()); let request = Request::new(rocket._manifest(), method, Origin::parse(uri).unwrap());
router.route(&request) router.route(&request)
} }

View File

@ -55,9 +55,9 @@ pub fn test_config(environment: Environment) {
std::env::set_var("ROCKET_ENV", environment.to_string()); std::env::set_var("ROCKET_ENV", environment.to_string());
let rocket = rocket::ignite() let rocket = rocket::ignite()
.attach(AdHoc::on_attach("Local Config", |rocket| { .attach(AdHoc::on_attach("Local Config", |mut rocket| {
println!("Attaching local config."); println!("Attaching local config.");
let config = rocket.config().clone(); let config = rocket.inspect().config().clone();
Ok(rocket.manage(LocalConfig(config))) Ok(rocket.manage(LocalConfig(config)))
})) }))
.mount("/", routes![check_config]); .mount("/", routes![check_config]);

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.rocket(), "index", &context).unwrap(); let template = Template::show(client.manifest(), "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.rocket(), "index", &context).unwrap(); let template = Template::show(client.manifest(), "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

@ -67,9 +67,9 @@ fn rocket() -> rocket::Rocket {
rocket::ignite() rocket::ignite()
.mount("/", routes![hello, token]) .mount("/", routes![hello, token])
.attach(Counter::default()) .attach(Counter::default())
.attach(AdHoc::on_attach("Token State", |rocket| { .attach(AdHoc::on_attach("Token State", |mut rocket| {
println!("Adding token managed state..."); println!("Adding token managed state...");
let token_val = rocket.config().get_int("token").unwrap_or(-1); let token_val = rocket.inspect().config().get_int("token").unwrap_or(-1);
Ok(rocket.manage(Token(token_val))) Ok(rocket.manage(Token(token_val)))
})) }))
.attach(AdHoc::on_launch("Launch Message", |_| { .attach(AdHoc::on_launch("Launch Message", |_| {

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.rocket(), "error/404", &map).unwrap(); let expected = Template::show(client.manifest(), "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.rocket(), "index", &context).unwrap(); let expected = Template::show(client.manifest(), "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.rocket(), "error/404", &map).unwrap(); let expected = Template::show(client.manifest(), "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

@ -8,13 +8,13 @@ async fn test() {
let client = Client::new(rocket()).unwrap(); let client = Client::new(rocket()).unwrap();
client.get("/sync").dispatch().await; client.get("/sync").dispatch().await;
let atomics = client.rocket().state::<Atomics>().unwrap(); let atomics = client.manifest().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.rocket().state::<Atomics>().unwrap(); let atomics = client.manifest().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

@ -30,11 +30,12 @@ fn test_raw_state_count() {
use rocket::State; use rocket::State;
use super::{count, index}; use super::{count, index};
let rocket = super::rocket(); let mut rocket = super::rocket();
let manifest = rocket.inspect();
assert_eq!(count(State::from(&rocket).unwrap()), "0"); assert_eq!(count(State::from(manifest).unwrap()), "0");
assert!(index(State::from(&rocket).unwrap()).0.contains("Visits: 1")); assert!(index(State::from(manifest).unwrap()).0.contains("Visits: 1"));
assert_eq!(count(State::from(&rocket).unwrap()), "1"); assert_eq!(count(State::from(manifest).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.rocket(), "error/404", &map).unwrap(); let expected = Template::show(client.manifest(), "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.rocket(), "index", &context).unwrap(); let expected = Template::show(client.manifest(), "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.rocket(), "error/404", &map).unwrap(); let expected = Template::show(client.manifest(), "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

@ -93,8 +93,8 @@ fn index(msg: Option<FlashMessage<'_, '_>>, conn: DbConn) -> Template {
}) })
} }
fn run_db_migrations(rocket: Rocket) -> Result<Rocket, Rocket> { fn run_db_migrations(mut rocket: Rocket) -> Result<Rocket, Rocket> {
let conn = DbConn::get_one(&rocket).expect("database connection"); let conn = DbConn::get_one(rocket.inspect()).expect("database connection");
match embedded_migrations::run(&*conn) { match embedded_migrations::run(&*conn) {
Ok(()) => Ok(rocket), Ok(()) => Ok(rocket),
Err(e) => { Err(e) => {

View File

@ -16,8 +16,8 @@ macro_rules! run_test {
let _lock = DB_LOCK.lock(); let _lock = DB_LOCK.lock();
rocket::async_test(async move { rocket::async_test(async move {
let rocket = super::rocket(); let mut rocket = super::rocket();
let db = super::DbConn::get_one(&rocket); let db = super::DbConn::get_one(rocket.inspect());
let $client = Client::new(rocket).expect("Rocket client"); let $client = Client::new(rocket).expect("Rocket client");
let $conn = db.expect("failed to get database connection for testing"); let $conn = db.expect("failed to get database connection for testing");
Task::delete_all(&$conn).expect("failed to delete all tasks for testing"); Task::delete_all(&$conn).expect("failed to delete all tasks for testing");

View File

@ -209,8 +209,8 @@ fn main() {
# if false { # if false {
rocket::ignite() rocket::ignite()
.mount("/", routes![assets]) .mount("/", routes![assets])
.attach(AdHoc::on_attach("Assets Config", |rocket| { .attach(AdHoc::on_attach("Assets Config", |mut rocket| {
let assets_dir = rocket.config() let assets_dir = rocket.inspect().config()
.get_str("assets_dir") .get_str("assets_dir")
.unwrap_or("assets/") .unwrap_or("assets/")
.to_string(); .to_string();