Remove 'attach' fairings. Add 'liftoff' fairings.

Launch fairings are now fallible and take the place of attach fairings,
but they are only run, as the name implies, at launch time.

This is is a fundamental shift from eager execution of set-up routines,
including the now defunct attach fairings, to lazy execution,
precipitated by the transition to `async`. The previous functionality,
while simple, caused grave issues:

  1. A instance of 'Rocket' with async attach fairings requires an async
     runtime to be constructed.
  2. The instance is accessible in non-async contexts.
  3. The async attach fairings have no runtime in which to be run.

Here's an example:

```rust
let rocket = rocket::ignite()
    .attach(AttachFairing::from(|rocket| async {
        Ok(rocket.manage(load_from_network::<T>().await))
    }));

let state = rocket.state::<T>();
```

This had no real meaning previously yet was accepted by running the
attach fairing future in an isolated runtime. In isolation, this causes
no issue, but when attach fairing futures share reactor state with other
futures in Rocket, panics ensue.

The new Rocket application lifecycle is this:

  * Build - A Rocket instance is constructed. No fairings are run.
  * Ignition - All launch fairings are run.
  * Liftoff - If all launch fairings succeeded, the server is started.

New 'liftoff' fairings are run in this third phase.
This commit is contained in:
Sergio Benitez 2021-04-01 12:32:52 -07:00
parent 8d4d01106e
commit 0bdb6b7bc7
29 changed files with 528 additions and 519 deletions

View File

@ -69,7 +69,7 @@ macro_rules! dberr {
impl<K: 'static, C: Poolable> ConnectionPool<K, C> { impl<K: 'static, C: Poolable> ConnectionPool<K, C> {
pub fn fairing(fairing_name: &'static str, db: &'static str) -> impl Fairing { pub fn fairing(fairing_name: &'static str, db: &'static str) -> impl Fairing {
AdHoc::on_attach(fairing_name, move |rocket| async move { AdHoc::on_launch(fairing_name, move |rocket| async move {
let config = match Config::from(db, &rocket) { let config = match Config::from(db, &rocket) {
Ok(config) => config, Ok(config) => config,
Err(e) => dberr!("config", db, "{}", e, rocket), Err(e) => dberr!("config", db, "{}", e, rocket),

View File

@ -193,15 +193,11 @@ impl Fairing for SpaceHelmet {
fn info(&self) -> Info { fn info(&self) -> Info {
Info { Info {
name: "Space Helmet", name: "Space Helmet",
kind: Kind::Response | Kind::Launch, kind: Kind::Liftoff | Kind::Response,
} }
} }
async fn on_response<'r>(&self, _: &'r Request<'_>, res: &mut Response<'r>) { async fn on_liftoff(&self, rocket: &Rocket) {
self.apply(res);
}
fn on_launch(&self, rocket: &Rocket) {
if rocket.config().tls_enabled() if rocket.config().tls_enabled()
&& rocket.figment().profile() != rocket::Config::DEBUG_PROFILE && rocket.figment().profile() != rocket::Config::DEBUG_PROFILE
&& !self.is_enabled::<Hsts>() && !self.is_enabled::<Hsts>()
@ -212,4 +208,8 @@ impl Fairing for SpaceHelmet {
self.force_hsts.store(true, Ordering::Relaxed); self.force_hsts.store(true, Ordering::Relaxed);
} }
} }
async fn on_response<'r>(&self, _: &'r Request<'_>, res: &mut Response<'r>) {
self.apply(res);
}
} }

View File

@ -129,8 +129,8 @@ pub struct TemplateFairing {
impl Fairing for TemplateFairing { impl Fairing for TemplateFairing {
fn info(&self) -> Info { fn info(&self) -> Info {
// on_request only applies in debug mode, so only enable it in debug. // on_request only applies in debug mode, so only enable it in debug.
#[cfg(debug_assertions)] let kind = Kind::Attach | Kind::Request; #[cfg(debug_assertions)] let kind = Kind::Launch | Kind::Request;
#[cfg(not(debug_assertions))] let kind = Kind::Attach; #[cfg(not(debug_assertions))] let kind = Kind::Launch;
Info { kind, name: "Templates" } Info { kind, name: "Templates" }
} }
@ -140,7 +140,7 @@ 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.
async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
use rocket::figment::{Source, value::magic::RelativePathBuf}; use rocket::figment::{Source, value::magic::RelativePathBuf};
let configured_dir = rocket.figment() let configured_dir = rocket.figment()
@ -186,7 +186,7 @@ impl Fairing for TemplateFairing {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
async fn on_request(&self, req: &mut rocket::Request<'_>, _data: &mut rocket::Data) { async fn on_request(&self, req: &mut rocket::Request<'_>, _data: &mut rocket::Data) {
let cm = req.rocket().state::<ContextManager>() let cm = req.rocket().state::<ContextManager>()
.expect("Template ContextManager registered in on_attach"); .expect("Template ContextManager registered in on_launch");
cm.reload_if_needed(&self.callback); cm.reload_if_needed(&self.callback);
} }

View File

@ -395,8 +395,8 @@ impl Template {
let info = ctxt.templates.get(name).ok_or_else(|| { let info = ctxt.templates.get(name).ok_or_else(|| {
let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect(); let ts: Vec<_> = ctxt.templates.keys().map(|s| s.as_str()).collect();
error_!("Template '{}' does not exist.", name); error_!("Template '{}' does not exist.", name);
info_!("Known templates: {}", ts.join(",")); info_!("Known templates: {}", ts.join(", "));
info_!("Searched in '{:?}'.", ctxt.root); info_!("Searched in {:?}.", ctxt.root);
Status::InternalServerError Status::InternalServerError
})?; })?;

View File

@ -35,7 +35,10 @@ mod rusqlite_integration_test {
let rocket = rocket::custom(config) let rocket = rocket::custom(config)
.attach(SqliteDb::fairing()) .attach(SqliteDb::fairing())
.attach(SqliteDb2::fairing()); .attach(SqliteDb2::fairing())
._ignite()
.await
.unwrap();
let conn = SqliteDb::get_one(&rocket).await let conn = SqliteDb::get_one(&rocket).await
.expect("unable to get connection"); .expect("unable to get connection");

View File

@ -61,17 +61,17 @@ mod templates_tests {
#[test] #[test]
fn test_tera_templates() { fn test_tera_templates() {
let rocket = rocket(); let client = Client::debug(rocket()).unwrap();
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(client.rocket(), "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(client.rocket(), "tera/html_test", &map);
assert_eq!(template, Some(ESCAPED_EXPECTED.into())); assert_eq!(template, Some(ESCAPED_EXPECTED.into()));
} }
@ -105,13 +105,13 @@ mod templates_tests {
#[test] #[test]
fn test_handlebars_templates() { fn test_handlebars_templates() {
let rocket = rocket(); let client = Client::debug(rocket()).unwrap();
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(client.rocket(), "hbs/test", &map);
assert_eq!(template, Some(EXPECTED.into())); assert_eq!(template, Some(EXPECTED.into()));
} }

View File

@ -6,9 +6,8 @@ use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::time::Duration; use std::time::Duration;
use hyper::server::accept::Accept;
use log::{debug, error}; use log::{debug, error};
use hyper::server::accept::Accept;
use tokio::time::Sleep; use tokio::time::Sleep;
use tokio::io::{AsyncRead, AsyncWrite}; use tokio::io::{AsyncRead, AsyncWrite};

View File

@ -56,8 +56,10 @@ use crate::config::SecretKey;
pub struct Config { pub struct Config {
/// The selected profile. **(default: _debug_ `debug` / _release_ `release`)** /// The selected profile. **(default: _debug_ `debug` / _release_ `release`)**
/// ///
/// **Note:** This field is never serialized nor deserialized. It is set to /// **Note:** This field is never serialized nor deserialized. When part of
/// the value of the selected `Profile` during extraction. /// a `Config` `Provider`, it is emitted as the profile to select on the
/// merged-into Figment. When a `Config` is extracted, this field is set to
/// the extracting Figment's selected `Profile`.
#[serde(skip)] #[serde(skip)]
pub profile: Profile, pub profile: Profile,
/// IP address to serve on. **(default: `127.0.0.1`)** /// IP address to serve on. **(default: `127.0.0.1`)**

View File

@ -113,8 +113,9 @@ pub enum TempFile<'v> {
} }
impl<'v> TempFile<'v> { impl<'v> TempFile<'v> {
/// Persists the temporary file, atomically linking it at `path`. The /// Persists the temporary file, moving it to `path`. If a file exists at
/// `self.path()` is updated to `path`. /// the target path, `self` will atomically replace it. `self.path()` is
/// updated to `path`.
/// ///
/// This method _does not_ create a copy of `self`, nor a new link to the /// This method _does not_ create a copy of `self`, nor a new link to the
/// contents of `self`: it renames the temporary file to `path` and marks it /// contents of `self`: it renames the temporary file to `path` and marks it

View File

@ -12,62 +12,53 @@ use crate::fairing::{Fairing, Kind, Info};
/// ///
/// # Usage /// # Usage
/// ///
/// Use the [`on_attach`](#method.on_attach), [`on_launch`](#method.on_launch), /// Use [`AdHoc::on_launch`], [`AdHoc::on_liftoff`], [`AdHoc::on_request()`], or
/// [`on_request`](#method.on_request), or [`on_response`](#method.on_response) /// [`AdHoc::on_response()`] to create an `AdHoc` structure from a function or
/// constructors to create an `AdHoc` structure from a function or closure. /// closure. Then, simply attach the structure to the `Rocket` instance.
/// Then, simply attach the structure to the `Rocket` instance.
/// ///
/// # Example /// # Example
/// ///
/// The following snippet creates a `Rocket` instance with two ad-hoc fairings. /// The following snippet creates a `Rocket` instance with two ad-hoc fairings.
/// The first, a launch fairing named "Launch Printer", simply prints a message /// The first, a liftoff fairing named "Liftoff Printer", simply prints a message
/// indicating that the application is about to the launch. The second named /// indicating that Rocket has launched. The second named "Put Rewriter", a
/// "Put Rewriter", a request fairing, rewrites the method of all requests to be /// request fairing, rewrites the method of all requests to be `PUT`.
/// `PUT`.
/// ///
/// ```rust /// ```rust
/// use rocket::fairing::AdHoc; /// use rocket::fairing::AdHoc;
/// use rocket::http::Method; /// use rocket::http::Method;
/// ///
/// rocket::ignite() /// rocket::ignite()
/// .attach(AdHoc::on_launch("Launch Printer", |_| { /// .attach(AdHoc::on_liftoff("Liftoff Printer", |_| Box::pin(async move {
/// println!("Rocket is about to launch! Exciting! Here we go..."); /// println!("...annnddd we have liftoff!");
/// })) /// })))
/// .attach(AdHoc::on_request("Put Rewriter", |req, _| { /// .attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move {
/// Box::pin(async move { /// req.set_method(Method::Put);
/// req.set_method(Method::Put); /// })));
/// })
/// }));
/// ``` /// ```
pub struct AdHoc { pub struct AdHoc {
name: &'static str, name: &'static str,
kind: AdHocKind, kind: AdHocKind,
} }
// macro_rules! Async { struct Once<F: ?Sized>(Mutex<Option<Box<F>>>);
// ($kind:ident <$l:lifetime> ($($param:ty),*) -> $r:ty) => (
// dyn for<$l> $kind($($param),*) -> futures::future::BoxFuture<$l, $r> impl<F: ?Sized> Once<F> {
// + Send + 'static fn new(f: Box<F>) -> Self { Once(Mutex::new(Some(f))) }
// );
// ($kind:ident ($($param:ty),*) -> $r:ty) => ( #[track_caller]
// dyn $kind($($param),*) -> futures::future::BoxFuture<'static, $r> fn take(&self) -> Box<F> {
// + Send + Sync + 'static self.0.lock().expect("Once::lock()").take().expect("Once::take() called once")
// ); }
// ($kind:ident <$l:lifetime> ($($param:ty),*)) => ( }
// Async!($kind <$l> ($($param),*) -> ())
// ); type Result<T = Rocket, E = Rocket> = std::result::Result<T, E>;
// ($kind:ident ($($param:ty),*)) => (
// Async!($kind ($($param),*) -> ())
// );
// }
enum AdHocKind { enum AdHocKind {
/// An ad-hoc **attach** fairing. Called when the fairing is attached.
Attach(Mutex<Option<Box<dyn FnOnce(Rocket)
-> 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(&Rocket) + Send + 'static>>>), Launch(Once<dyn FnOnce(Rocket) -> BoxFuture<'static, Result> + Send + 'static>),
/// An ad-hoc **liftoff** fairing. Called just after Rocket launches.
Liftoff(Once<dyn for<'a> FnOnce(&'a Rocket) -> BoxFuture<'a, ()> + 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)
@ -75,70 +66,34 @@ enum AdHocKind {
/// 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
/// sent to a client. /// sent to a client.
Response(Box<dyn for<'a> Fn(&'a Request<'_>, &'a mut Response<'_>) Response(Box<dyn for<'r, 'b> Fn(&'r Request<'_>, &'b mut Response<'r>)
-> BoxFuture<'a, ()> + Send + Sync + 'static>), -> BoxFuture<'b, ()> + Send + Sync + 'static>),
} }
impl AdHoc { impl AdHoc {
/// Constructs an `AdHoc` attach fairing named `name`. The function `f` will /// Constructs an `AdHoc` launch fairing named `name`. The function `f` will
/// be called by Rocket when this fairing is attached. /// be called by Rocket just prior to launch. Returning an `Err` aborts
/// launch.
/// ///
/// # Example /// # Example
/// ///
/// ```rust /// ```rust
/// use rocket::fairing::AdHoc; /// use rocket::fairing::AdHoc;
/// ///
/// // The no-op attach fairing. /// // The no-op launch fairing.
/// let fairing = AdHoc::on_attach("No-Op", |rocket| async { Ok(rocket) }); /// let fairing = AdHoc::on_launch("Boom!", |rocket| async move {
/// Ok(rocket)
/// });
/// ``` /// ```
pub fn on_attach<F, Fut>(name: &'static str, f: F) -> AdHoc pub fn on_launch<F, Fut>(name: &'static str, f: F) -> AdHoc
where F: FnOnce(Rocket) -> Fut + Send + 'static, where F: FnOnce(Rocket) -> Fut + Send + 'static,
Fut: Future<Output=Result<Rocket, Rocket>> + Send + 'static, Fut: Future<Output=Result<Rocket, Rocket>> + Send + 'static,
{ {
AdHoc { AdHoc { name, kind: AdHocKind::Launch(Once::new(Box::new(|r| Box::pin(f(r))))) }
name,
kind: AdHocKind::Attach(Mutex::new(Some(Box::new(|rocket| Box::pin(f(rocket))))))
}
}
/// Constructs an `AdHoc` attach fairing that extracts a configuration of
/// type `T` from the configured provider and stores it in managed state. If
/// extractions fails, pretty-prints the error message and errors the attach
/// fairing.
///
/// # Example
///
/// ```rust
/// use serde::Deserialize;
/// use rocket::fairing::AdHoc;
///
/// #[derive(Deserialize)]
/// struct Config {
/// field: String,
/// other: usize,
/// /* and so on.. */
/// }
///
/// let fairing = AdHoc::config::<Config>();
/// ```
pub fn config<'de, T>() -> AdHoc
where T: serde::Deserialize<'de> + Send + Sync + 'static
{
AdHoc::on_attach(std::any::type_name::<T>(), |rocket| async {
let app_config = match rocket.figment().extract::<T>() {
Ok(config) => config,
Err(e) => {
crate::config::pretty_print_error(e);
return Err(rocket);
}
};
Ok(rocket.manage(app_config))
})
} }
/// Constructs an `AdHoc` launch fairing named `name`. The function `f` will /// Constructs an `AdHoc` launch fairing named `name`. The function `f` will
/// be called by Rocket just prior to launching. /// be called by Rocket just after launching.
/// ///
/// # Example /// # Example
/// ///
@ -146,14 +101,14 @@ impl AdHoc {
/// use rocket::fairing::AdHoc; /// use rocket::fairing::AdHoc;
/// ///
/// // A fairing that prints a message just before launching. /// // A fairing that prints a message just before launching.
/// let fairing = AdHoc::on_launch("Launch Count", |rocket| { /// let fairing = AdHoc::on_liftoff("Boom!", |_| Box::pin(async move {
/// println!("Launching in T-3..2..1.."); /// println!("Rocket has lifted off!");
/// }); /// }));
/// ``` /// ```
pub fn on_launch<F: Send + 'static>(name: &'static str, f: F) -> AdHoc pub fn on_liftoff<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
where F: FnOnce(&Rocket) where F: for<'a> FnOnce(&'a Rocket) -> BoxFuture<'a, ()>
{ {
AdHoc { name, kind: AdHocKind::Launch(Mutex::new(Some(Box::new(f)))) } AdHoc { name, kind: AdHocKind::Liftoff(Once::new(Box::new(f))) }
} }
/// Constructs an `AdHoc` request fairing named `name`. The function `f` /// Constructs an `AdHoc` request fairing named `name`. The function `f`
@ -178,17 +133,9 @@ impl AdHoc {
{ {
AdHoc { name, kind: AdHocKind::Request(Box::new(f)) } AdHoc { name, kind: AdHocKind::Request(Box::new(f)) }
} }
// // FIXME: Can the generated future hold references to the request with this?
// pub fn on_request<F, Fut>(name: &'static str, f: F) -> AdHoc // FIXME(rustc): We'd like to allow passing `async fn` to these methods...
// where // https://github.com/rust-lang/rust/issues/64552#issuecomment-666084589
// F: for<'a> Fn(&'a mut Request<'_>, &'a Data) -> Fut + Send + Sync + 'static,
// Fut: Future<Output=()> + Send + 'static,
// {
// AdHoc {
// name,
// kind: AdHocKind::Request(Box::new(|req, data| Box::pin(f(req, data))))
// }
// }
/// Constructs an `AdHoc` response fairing named `name`. The function `f` /// Constructs an `AdHoc` response fairing named `name`. The function `f`
/// will be called and the returned `Future` will be `await`ed by Rocket /// will be called and the returned `Future` will be `await`ed by Rocket
@ -208,18 +155,53 @@ impl AdHoc {
/// }); /// });
/// ``` /// ```
pub fn on_response<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc pub fn on_response<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
where F: for<'a> Fn(&'a Request<'_>, &'a mut Response<'_>) -> BoxFuture<'a, ()> where F: for<'b, 'r> Fn(&'r Request<'_>, &'b mut Response<'r>) -> BoxFuture<'b, ()>
{ {
AdHoc { name, kind: AdHocKind::Response(Box::new(f)) } AdHoc { name, kind: AdHocKind::Response(Box::new(f)) }
} }
/// Constructs an `AdHoc` launch fairing that extracts a configuration of
/// type `T` from the configured provider and stores it in managed state. If
/// extractions fails, pretty-prints the error message and aborts launch.
///
/// # Example
///
/// ```rust
/// use serde::Deserialize;
/// use rocket::fairing::AdHoc;
///
/// #[derive(Deserialize)]
/// struct Config {
/// field: String,
/// other: usize,
/// /* and so on.. */
/// }
///
/// let fairing = AdHoc::config::<Config>();
/// ```
pub fn config<'de, T>() -> AdHoc
where T: serde::Deserialize<'de> + Send + Sync + 'static
{
AdHoc::on_launch(std::any::type_name::<T>(), |rocket| async {
let app_config = match rocket.figment().extract::<T>() {
Ok(config) => config,
Err(e) => {
crate::config::pretty_print_error(e);
return Err(rocket);
}
};
Ok(rocket.manage(app_config))
})
}
} }
#[crate::async_trait] #[crate::async_trait]
impl Fairing for AdHoc { impl Fairing for AdHoc {
fn info(&self) -> Info { fn info(&self) -> Info {
let kind = match self.kind { let kind = match self.kind {
AdHocKind::Attach(_) => Kind::Attach,
AdHocKind::Launch(_) => Kind::Launch, AdHocKind::Launch(_) => Kind::Launch,
AdHocKind::Liftoff(_) => Kind::Liftoff,
AdHocKind::Request(_) => Kind::Request, AdHocKind::Request(_) => Kind::Request,
AdHocKind::Response(_) => Kind::Response, AdHocKind::Response(_) => Kind::Response,
}; };
@ -227,35 +209,28 @@ impl Fairing for AdHoc {
Info { name: self.name, kind } Info { name: self.name, kind }
} }
async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
if let AdHocKind::Attach(ref mutex) = self.kind { match self.kind {
let f = mutex.lock() AdHocKind::Launch(ref f) => (f.take())(rocket).await,
.expect("AdHoc::Attach lock") _ => Ok(rocket)
.take()
.expect("internal error: `on_attach` one-call invariant broken");
f(rocket).await
} else {
Ok(rocket)
} }
} }
fn on_launch(&self, state: &Rocket) { async fn on_liftoff(&self, rocket: &Rocket) {
if let AdHocKind::Launch(ref mutex) = self.kind { if let AdHocKind::Liftoff(ref f) = self.kind {
let mut opt = mutex.lock().expect("AdHoc::Launch lock"); (f.take())(rocket).await
let f = opt.take().expect("internal error: `on_launch` one-call invariant broken");
f(state)
} }
} }
async fn on_request(&self, req: &mut Request<'_>, data: &mut Data) { async fn on_request(&self, req: &mut Request<'_>, data: &mut Data) {
if let AdHocKind::Request(ref callback) = self.kind { if let AdHocKind::Request(ref f) = self.kind {
callback(req, data).await; f(req, data).await
} }
} }
async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) { async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
if let AdHocKind::Response(ref callback) = self.kind { if let AdHocKind::Response(ref f) = self.kind {
callback(req, res).await; f(req, res).await
} }
} }
} }

View File

@ -7,126 +7,133 @@ use yansi::Paint;
#[derive(Default)] #[derive(Default)]
pub struct Fairings { pub struct Fairings {
all_fairings: Vec<Box<dyn Fairing>>, all_fairings: Vec<Box<dyn Fairing>>,
attach_failures: Vec<Info>, failures: Vec<Info>,
// Index into `attach` of last run attach fairing.
last_launch: usize,
// The vectors below hold indices into `all_fairings`. // The vectors below hold indices into `all_fairings`.
attach: Vec<usize>,
launch: Vec<usize>, launch: Vec<usize>,
liftoff: Vec<usize>,
request: Vec<usize>, request: Vec<usize>,
response: Vec<usize>, response: Vec<usize>,
} }
macro_rules! iter {
($_self:ident . $kind:ident) => ({
let all_fairings = &$_self.all_fairings;
$_self.$kind.iter().filter_map(move |i| all_fairings.get(*i).map(|f| &**f))
})
}
impl Fairings { impl Fairings {
#[inline] #[inline]
pub fn new() -> Fairings { pub fn new() -> Fairings {
Fairings::default() Fairings::default()
} }
pub async fn attach(&mut self, fairing: Box<dyn Fairing>, mut rocket: Rocket) -> Rocket { pub fn add(&mut self, fairing: Box<dyn Fairing>) -> &dyn Fairing {
// Run the `on_attach` callback if this is an 'attach' fairing.
let kind = fairing.info().kind;
let fairing = self.add(fairing);
if kind.is(Kind::Attach) {
let info = fairing.info();
rocket = fairing.on_attach(rocket).await
.unwrap_or_else(|r| { self.attach_failures.push(info); r })
}
rocket
}
fn add(&mut self, fairing: Box<dyn Fairing>) -> &dyn Fairing {
let kind = fairing.info().kind; let kind = fairing.info().kind;
let index = self.all_fairings.len(); let index = self.all_fairings.len();
self.all_fairings.push(fairing); self.all_fairings.push(fairing);
if kind.is(Kind::Attach) { self.attach.push(index); }
if kind.is(Kind::Launch) { self.launch.push(index); } if kind.is(Kind::Launch) { self.launch.push(index); }
if kind.is(Kind::Liftoff) { self.liftoff.push(index); }
if kind.is(Kind::Request) { self.request.push(index); } if kind.is(Kind::Request) { self.request.push(index); }
if kind.is(Kind::Response) { self.response.push(index); } if kind.is(Kind::Response) { self.response.push(index); }
&*self.all_fairings[index] &*self.all_fairings[index]
} }
#[inline(always)]
fn fairings(&self, kind: Kind) -> impl Iterator<Item = &dyn Fairing> {
let indices = match kind {
k if k.is(Kind::Attach) => &self.attach,
k if k.is(Kind::Launch) => &self.launch,
k if k.is(Kind::Request) => &self.request,
_ => &self.response,
};
indices.iter().map(move |i| &*self.all_fairings[*i])
}
pub fn append(&mut self, others: Fairings) { pub fn append(&mut self, others: Fairings) {
for fairing in others.all_fairings { for fairing in others.all_fairings {
self.add(fairing); self.add(fairing);
} }
} }
#[inline(always)] pub async fn handle_launch(mut rocket: Rocket) -> Rocket {
pub fn handle_launch(&self, rocket: &Rocket) { while rocket.fairings.last_launch < rocket.fairings.launch.len() {
for fairing in self.fairings(Kind::Launch) { // We're going to move `rocket` while borrowing `fairings`...
fairing.on_launch(rocket); let mut fairings = std::mem::replace(&mut rocket.fairings, Fairings::new());
for fairing in iter!(fairings.launch).skip(fairings.last_launch) {
let info = fairing.info();
rocket = match fairing.on_launch(rocket).await {
Ok(rocket) => rocket,
Err(rocket) => {
fairings.failures.push(info);
rocket
}
};
fairings.last_launch += 1;
}
// Note that `rocket.fairings` may now be non-empty since launch
// fairings could have added more fairings! Move them to the end.
fairings.append(rocket.fairings);
rocket.fairings = fairings;
} }
rocket
}
#[inline(always)]
pub async fn handle_liftoff(&self, rocket: &Rocket) {
let liftoff_futures = iter!(self.liftoff).map(|f| f.on_liftoff(rocket));
futures::future::join_all(liftoff_futures).await;
} }
#[inline(always)] #[inline(always)]
pub async fn handle_request(&self, req: &mut Request<'_>, data: &mut Data) { pub async fn handle_request(&self, req: &mut Request<'_>, data: &mut Data) {
for fairing in self.fairings(Kind::Request) { for fairing in iter!(self.request) {
fairing.on_request(req, data).await fairing.on_request(req, data).await
} }
} }
#[inline(always)] #[inline(always)]
pub async fn handle_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) { pub async fn handle_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
for fairing in self.fairings(Kind::Response) { for fairing in iter!(self.response) {
fairing.on_response(request, response).await; fairing.on_response(request, response).await;
} }
} }
pub fn failures(&self) -> Option<&[Info]> { pub fn failures(&self) -> Option<&[Info]> {
if self.attach_failures.is_empty() { match self.failures.is_empty() {
None true => None,
} else { false => Some(&self.failures)
Some(&self.attach_failures)
} }
} }
pub fn pretty_print_counts(&self) { pub fn pretty_print_counts(&self) {
fn pretty_print(f: &Fairings, prefix: &str, kind: Kind) { fn pretty_print<'a>(prefix: &str, iter: impl Iterator<Item = &'a dyn Fairing>) {
let names: Vec<_> = f.fairings(kind).map(|f| f.info().name).collect(); let names: Vec<_> = iter.map(|f| f.info().name).collect();
if names.is_empty() { if names.is_empty() {
return; return;
} }
let num = names.len(); let (num, joined) = (names.len(), names.join(", "));
let joined = names.join(", ");
info_!("{} {}: {}", Paint::default(num).bold(), prefix, Paint::default(joined).bold()); info_!("{} {}: {}", Paint::default(num).bold(), prefix, Paint::default(joined).bold());
} }
if !self.all_fairings.is_empty() { if !self.all_fairings.is_empty() {
info!("{}{}:", Paint::emoji("📡 "), Paint::magenta("Fairings")); info!("{}{}:", Paint::emoji("📡 "), Paint::magenta("Fairings"));
pretty_print(self, "attach", Kind::Attach); pretty_print("launch", iter!(self.launch));
pretty_print(self, "launch", Kind::Launch); pretty_print("liftoff", iter!(self.liftoff));
pretty_print(self, "request", Kind::Request); pretty_print("request", iter!(self.request));
pretty_print(self, "response", Kind::Response); pretty_print("response", iter!(self.response));
} }
} }
} }
impl std::fmt::Debug for Fairings { impl std::fmt::Debug for Fairings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn debug_info(fs: &Fairings, kind: Kind) -> Vec<Info> { fn debug_info<'a>(iter: impl Iterator<Item = &'a dyn Fairing>) -> Vec<Info> {
fs.fairings(kind).map(|f| f.info()).collect() iter.map(|f| f.info()).collect()
} }
f.debug_struct("Fairings") f.debug_struct("Fairings")
.field("attach", &debug_info(self, Kind::Attach)) .field("launch", &debug_info(iter!(self.launch)))
.field("launch", &debug_info(self, Kind::Launch)) .field("liftoff", &debug_info(iter!(self.liftoff)))
.field("request", &debug_info(self, Kind::Request)) .field("request", &debug_info(iter!(self.request)))
.field("response", &debug_info(self, Kind::Response)) .field("response", &debug_info(iter!(self.response)))
.finish() .finish()
} }
} }

View File

@ -10,7 +10,7 @@ use std::ops::BitOr;
/// # Example /// # Example
/// ///
/// A simple `Info` structure that can be used for a `Fairing` that implements /// A simple `Info` structure that can be used for a `Fairing` that implements
/// all four callbacks: /// all callbacks:
/// ///
/// ``` /// ```
/// use rocket::fairing::{Info, Kind}; /// use rocket::fairing::{Info, Kind};
@ -18,7 +18,7 @@ use std::ops::BitOr;
/// # let _unused_info = /// # let _unused_info =
/// Info { /// Info {
/// name: "Example Fairing", /// name: "Example Fairing",
/// kind: Kind::Attach | Kind::Launch | Kind::Request | Kind::Response /// kind: Kind::Launch | Kind::Liftoff | Kind::Request | Kind::Response
/// } /// }
/// # ; /// # ;
/// ``` /// ```
@ -36,28 +36,31 @@ pub struct Info {
/// A fairing can request any combination of any of the following kinds of /// A fairing can request any combination of any of the following kinds of
/// callbacks: /// callbacks:
/// ///
/// * Attach
/// * Launch /// * Launch
/// * Liftoff
/// * Request /// * Request
/// * Response /// * Response
/// ///
/// Two `Kind` structures can be `or`d together to represent a combination. For /// Two `Kind` structures can be `or`d together to represent a combination. For
/// instance, to represent a fairing that is both a launch and request fairing, /// instance, to represent a fairing that is both a launch and request fairing,
/// use `Kind::Launch | Kind::Request`. Similarly, to represent a fairing that /// use `Kind::Launch | Kind::Request`. Similarly, to represent a fairing that
/// is only an attach fairing, use `Kind::Attach`. /// is only a launch fairing, use `Kind::Launch`.
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Kind(usize); pub struct Kind(usize);
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
impl Kind { impl Kind {
/// `Kind` flag representing a request for an 'attach' callback.
pub const Attach: Kind = Kind(0b0001);
/// `Kind` flag representing a request for a 'launch' callback. /// `Kind` flag representing a request for a 'launch' callback.
pub const Launch: Kind = Kind(0b0010); pub const Launch: Kind = Kind(1 << 0);
/// `Kind` flag representing a request for a 'liftoff' callback.
pub const Liftoff: Kind = Kind(1 << 1);
/// `Kind` flag representing a request for a 'request' callback. /// `Kind` flag representing a request for a 'request' callback.
pub const Request: Kind = Kind(0b0100); pub const Request: Kind = Kind(1 << 2);
/// `Kind` flag representing a request for a 'response' callback. /// `Kind` flag representing a request for a 'response' callback.
pub const Response: Kind = Kind(0b1000); pub const Response: Kind = Kind(1 << 3);
/// Returns `true` if `self` is a superset of `other`. In other words, /// Returns `true` if `self` is a superset of `other`. In other words,
/// returns `true` if all of the kinds in `other` are also in `self`. /// returns `true` if all of the kinds in `other` are also in `self`.
@ -73,6 +76,7 @@ impl Kind {
/// assert!(launch_and_req.is(Kind::Launch)); /// assert!(launch_and_req.is(Kind::Launch));
/// assert!(launch_and_req.is(Kind::Request)); /// assert!(launch_and_req.is(Kind::Request));
/// ///
/// assert!(!launch_and_req.is(Kind::Liftoff));
/// assert!(!launch_and_req.is(Kind::Response)); /// assert!(!launch_and_req.is(Kind::Response));
/// assert!(!launch_and_req.is(Kind::Launch | Kind::Response)); /// assert!(!launch_and_req.is(Kind::Launch | Kind::Response));
/// assert!(!launch_and_req.is(Kind::Launch | Kind::Request | Kind::Response)); /// assert!(!launch_and_req.is(Kind::Launch | Kind::Request | Kind::Response));

View File

@ -1,4 +1,4 @@
//! Fairings: callbacks at attach, launch, request, and response time. //! Fairings: callbacks at launch, liftoff, request, and response time.
//! //!
//! Fairings allow for structured interposition at various points in the //! Fairings allow for structured interposition at various points in the
//! application lifetime. Fairings can be seen as a restricted form of //! application lifetime. Fairings can be seen as a restricted form of
@ -94,35 +94,39 @@ pub use self::info_kind::{Info, Kind};
/// ///
/// ## Fairing Callbacks /// ## Fairing Callbacks
/// ///
/// There are four kinds of fairing callbacks: attach, launch, request, and /// There are four kinds of fairing callbacks: launch, liftoff, request, and
/// response. A fairing can request any combination of these callbacks through /// response. A fairing can request any combination of these callbacks through
/// the `kind` field of the `Info` structure returned from the `info` method. /// the `kind` field of the `Info` structure returned from the `info` method.
/// Rocket will only invoke the callbacks set in the `kind` field. /// Rocket will only invoke the callbacks set in the `kind` field.
/// ///
/// The four callback kinds are as follows: /// The four callback kinds are as follows:
/// ///
/// * **Attach (`on_attach`)**
///
/// An attach callback, represented by the [`Fairing::on_attach()`] method,
/// is called when a fairing is first attached via [`Rocket::attach()`]
/// method. The state of the `Rocket` instance is, at this point, not
/// finalized, as the user may still add additional information to the
/// `Rocket` instance. As a result, it is unwise to depend on the state of
/// the `Rocket` instance.
///
/// An attach callback can arbitrarily modify the `Rocket` instance being
/// constructed. It returns `Ok` if it would like launching to proceed
/// nominally and `Err` otherwise. If an attach callback returns `Err`,
/// launch will be aborted. All attach callbacks are executed on `attach`,
/// even if one or more signal a failure.
///
/// * **Launch (`on_launch`)** /// * **Launch (`on_launch`)**
/// ///
/// A launch callback, represented by the [`Fairing::on_launch()`] method, /// A launch callback, represented by the [`Fairing::on_launch()`] method,
/// is called immediately before the Rocket application has launched. At /// is called just prior to liftoff, while launching the application. The
/// this point, Rocket has opened a socket for listening but has not yet /// state of the `Rocket` instance is, at this point, not finalized, as it
/// begun accepting connections. A launch callback can inspect the `Rocket` /// may be modified at will by other launch fairings. As a result, it is
/// instance being launched. /// unwise to depend on the state of the `Rocket` instance.
///
/// All launch callbacks are executed in breadth-first `attach()` order. A
/// callback `B` executing after a callback `A` can view changes made by `A`
/// but not vice-versa.
///
/// A launch callback can arbitrarily modify the `Rocket` instance being
/// constructed. It should take care not to introduce infinite recursion by
/// recursively attaching launch fairings. It returns `Ok` if it would like
/// launching to proceed nominally and `Err` otherwise. If a launch fairing
/// returns `Err`, launch will be aborted. All launch fairings are executed
/// even if one or more signal a failure.
///
/// * **Liftoff (`on_liftoff`)**
///
/// A liftoff callback, represented by the [`Fairing::on_liftoff()`] method,
/// is called immediately after a Rocket application has launched. At this
/// point, Rocket has opened a socket for listening but has not yet begun
/// accepting connections. A liftoff callback can inspect the `Rocket`
/// instance that has launched but not otherwise gracefully abort launch.
/// ///
/// * **Request (`on_request`)** /// * **Request (`on_request`)**
/// ///
@ -155,7 +159,7 @@ pub use self::info_kind::{Info, Kind};
/// # Implementing /// # Implementing
/// ///
/// A `Fairing` implementation has one required method: [`info`]. A `Fairing` /// A `Fairing` implementation has one required method: [`info`]. A `Fairing`
/// can also implement any of the available callbacks: `on_attach`, `on_launch`, /// can also implement any of the available callbacks: `on_launch`, `on_liftoff`,
/// `on_request`, and `on_response`. A `Fairing` _must_ set the appropriate /// `on_request`, and `on_response`. A `Fairing` _must_ set the appropriate
/// callback kind in the `kind` field of the returned `Info` structure from /// callback kind in the `kind` field of the returned `Info` structure from
/// [`info`] for a callback to actually be called by Rocket. /// [`info`] for a callback to actually be called by Rocket.
@ -169,7 +173,7 @@ pub use self::info_kind::{Info, Kind};
/// ///
/// This is the `name` field, which can be any arbitrary string. Name your /// This is the `name` field, which can be any arbitrary string. Name your
/// fairing something illustrative. The name will be logged during the /// fairing something illustrative. The name will be logged during the
/// application's launch procedures. /// application's ignition procedures.
/// ///
/// 2. Determine which callbacks to actually issue on the `Fairing`. /// 2. Determine which callbacks to actually issue on the `Fairing`.
/// ///
@ -177,8 +181,8 @@ pub use self::info_kind::{Info, Kind};
/// represents the kinds of callbacks the fairing wishes to receive. Rocket /// represents the kinds of callbacks the fairing wishes to receive. Rocket
/// will only invoke the callbacks that are flagged in this set. `Kind` /// will only invoke the callbacks that are flagged in this set. `Kind`
/// structures can be `or`d together to represent any combination of kinds /// structures can be `or`d together to represent any combination of kinds
/// of callbacks. For instance, to request launch and response callbacks, /// of callbacks. For instance, to request liftoff and response callbacks,
/// return a `kind` field with the value `Kind::Launch | Kind::Response`. /// return a `kind` field with the value `Kind::Liftoff | Kind::Response`.
/// ///
/// [`info`]: Fairing::info() /// [`info`]: Fairing::info()
/// ///
@ -207,12 +211,12 @@ pub use self::info_kind::{Info, Kind};
/// # unimplemented!() /// # unimplemented!()
/// } /// }
/// ///
/// async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { /// async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
/// /* ... */ /// /* ... */
/// # unimplemented!() /// # unimplemented!()
/// } /// }
/// ///
/// fn on_launch(&self, rocket: &Rocket) { /// async fn on_liftoff(&self, rocket: &Rocket) {
/// /* ... */ /// /* ... */
/// # unimplemented!() /// # unimplemented!()
/// } /// }
@ -231,11 +235,11 @@ pub use self::info_kind::{Info, Kind};
/// ///
/// ## Example /// ## Example
/// ///
/// Imagine that we want to record the number of `GET` and `POST` requests that /// As an example, we want to record the number of `GET` and `POST` requests
/// our application has received. While we could do this with [request guards] /// that our application has received. While we could do this with [request
/// and [managed state](crate::State), it would require us to annotate every /// guards] and [managed state](crate::State), it would require us to annotate
/// `GET` and `POST` request with custom types, polluting handler signatures. /// every `GET` and `POST` request with custom types, polluting handler
/// Instead, we can create a simple fairing that acts globally. /// signatures. Instead, we can create a simple fairing that acts globally.
/// ///
/// The `Counter` fairing below records the number of all `GET` and `POST` /// The `Counter` fairing below records the number of all `GET` and `POST`
/// requests received. It makes these counts available at a special `'/counts'` /// requests received. It makes these counts available at a special `'/counts'`
@ -403,31 +407,29 @@ pub trait Fairing: Send + Sync + 'static {
/// ``` /// ```
fn info(&self) -> Info; fn info(&self) -> Info;
/// The attach callback. Returns `Ok` if launch should proceed and `Err` if /// The launch callback. Returns `Ok` if launch should proceed and `Err` if
/// launch should be aborted. /// launch should be aborted.
/// ///
/// This method is called when a fairing is attached if `Kind::Attach` is in /// This method is called when the application is being launched if
/// the `kind` field of the `Info` structure for this fairing. The `rocket` /// `Kind::Launch` is in the `kind` field of the `Info` structure for this
/// parameter is the `Rocket` instance that is currently being built for /// fairing. The `rocket` parameter is the `Rocket` instance that is
/// this application. /// currently being built for this application.
/// ///
/// ## Default Implementation /// ## Default Implementation
/// ///
/// The default implementation of this method simply returns `Ok(rocket)`. /// The default implementation of this method simply returns `Ok(rocket)`.
async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { Ok(rocket) } async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> { Ok(rocket) }
/// The launch callback. /// The liftoff callback.
/// ///
/// This method is called just prior to launching the application if /// This method is called just after launching the application if
/// `Kind::Launch` is in the `kind` field of the `Info` structure for this /// `Kind::Liftoff` is in the `kind` field of the `Info` structure for this
/// fairing. The `Rocket` parameter corresponds to the application that /// fairing. The `Rocket` parameter corresponds to the lauched application.
/// 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)] async fn on_liftoff(&self, _rocket: &Rocket) { }
fn on_launch(&self, rocket: &Rocket) {}
/// The request callback. /// The request callback.
/// ///
@ -439,8 +441,7 @@ pub trait Fairing: Send + Sync + 'static {
/// ## Default Implementation /// ## Default Implementation
/// ///
/// The default implementation of this method does nothing. /// The default implementation of this method does nothing.
#[allow(unused_variables)] async fn on_request(&self, _req: &mut Request<'_>, _data: &mut Data) {}
async fn on_request(&self, req: &mut Request<'_>, data: &mut Data) {}
/// The response callback. /// The response callback.
/// ///
@ -452,8 +453,7 @@ pub trait Fairing: Send + Sync + 'static {
/// ## Default Implementation /// ## Default Implementation
/// ///
/// The default implementation of this method does nothing. /// The default implementation of this method does nothing.
#[allow(unused_variables)] async fn on_response<'r>(&self, _req: &'r Request<'_>, _res: &mut Response<'r>) {}
async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {}
} }
#[crate::async_trait] #[crate::async_trait]
@ -464,22 +464,22 @@ impl<T: Fairing> Fairing for std::sync::Arc<T> {
} }
#[inline] #[inline]
async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
(self as &T).on_attach(rocket).await (self as &T).on_launch(rocket).await
} }
#[inline] #[inline]
fn on_launch(&self, rocket: &Rocket) { async fn on_liftoff(&self, rocket: &Rocket) {
(self as &T).on_launch(rocket) (self as &T).on_liftoff(rocket).await
} }
#[inline] #[inline]
async fn on_request(&self, req: &mut Request<'_>, data: &mut Data) { async fn on_request(&self, req: &mut Request<'_>, data: &mut Data) {
(self as &T).on_request(req, data).await; (self as &T).on_request(req, data).await
} }
#[inline] #[inline]
async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) { async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
(self as &T).on_response(req, res).await; (self as &T).on_response(req, res).await
} }
} }

View File

@ -55,10 +55,10 @@ pub struct Client {
impl Client { impl Client {
pub(crate) async fn _new( pub(crate) async fn _new(
mut rocket: Rocket, rocket: Rocket,
tracked: bool tracked: bool
) -> Result<Client, Error> { ) -> Result<Client, Error> {
rocket.prelaunch_check().await?; let rocket = rocket._ignite().await?;
let cookies = RwLock::new(cookie::CookieJar::new()); let cookies = RwLock::new(cookie::CookieJar::new());
Ok(Client { rocket, tracked, cookies }) Ok(Client { rocket, tracked, cookies })
} }

View File

@ -103,9 +103,10 @@ macro_rules! pub_client_impl {
#[doc(hidden)] #[doc(hidden)]
pub $($prefix)? fn debug(rocket: Rocket) -> Result<Self, Error> { pub $($prefix)? fn debug(rocket: Rocket) -> Result<Self, Error> {
let mut config = rocket.config().clone(); let config = rocket.figment().clone()
config.log_level = crate::config::LogLevel::Debug; .merge(("log_level", crate::config::LogLevel::Debug))
config.profile = crate::Config::DEBUG_PROFILE; .merge(("profile", crate::Config::DEBUG_PROFILE));
Self::tracked(rocket.reconfigure(config)) $(.$suffix)? Self::tracked(rocket.reconfigure(config)) $(.$suffix)?
} }

View File

@ -5,7 +5,6 @@ use yansi::Paint;
use state::Container; use state::Container;
use figment::Figment; use figment::Figment;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use futures::future::FutureExt;
use crate::logger; use crate::logger;
use crate::config::Config; use crate::config::Config;
@ -127,6 +126,7 @@ impl Rocket {
/// let mut new_config = rocket.config().clone(); /// let mut new_config = rocket.config().clone();
/// new_config.port = 8888; /// new_config.port = 8888;
/// ///
/// // Note that this tosses away any non-`Config` parameters in `Figment`.
/// let rocket = rocket.reconfigure(new_config); /// let rocket = rocket.reconfigure(new_config);
/// assert_eq!(rocket.config().port, 8888); /// assert_eq!(rocket.config().port, 8888);
/// assert_eq!(rocket.config().address, Ipv4Addr::new(18, 127, 0, 1)); /// assert_eq!(rocket.config().address, Ipv4Addr::new(18, 127, 0, 1));
@ -339,9 +339,8 @@ impl Rocket {
self self
} }
/// Attaches a fairing to this instance of Rocket. If the fairing is an /// Attaches a fairing to this instance of Rocket. No fairings are excuted.
/// _attach_ fairing, it is run immediately. All other kinds of fairings /// Fairings will be executed at their appropriate time.
/// will be executed at their appropriate time.
/// ///
/// # Example /// # Example
/// ///
@ -353,39 +352,13 @@ impl Rocket {
/// #[launch] /// #[launch]
/// fn rocket() -> rocket::Rocket { /// fn rocket() -> rocket::Rocket {
/// rocket::ignite() /// rocket::ignite()
/// .attach(AdHoc::on_launch("Launch Message", |_| { /// .attach(AdHoc::on_liftoff("Liftoff Message", |_| Box::pin(async {
/// println!("Rocket is launching!"); /// println!("We have liftoff!");
/// })) /// })))
/// } /// }
/// ``` /// ```
pub fn attach<F: Fairing>(mut self, fairing: F) -> Self { pub fn attach<F: Fairing>(mut self, fairing: F) -> Self {
let future = async move { self.fairings.add(Box::new(fairing));
let fairing = Box::new(fairing);
let mut fairings = std::mem::replace(&mut self.fairings, Fairings::new());
let rocket = fairings.attach(fairing, self).await;
(rocket, fairings)
};
// TODO: Reuse a single thread to run all attach fairings.
let (rocket, mut fairings) = match tokio::runtime::Handle::try_current() {
Ok(handle) => {
std::thread::spawn(move || {
let _e = handle.enter();
futures::executor::block_on(future)
}).join().unwrap()
}
Err(_) => {
std::thread::spawn(|| {
futures::executor::block_on(future)
}).join().unwrap()
}
};
self = rocket;
// Note that `self.fairings` may now be non-empty! Move them to the end.
fairings.append(self.fairings);
self.fairings = fairings;
self self
} }
@ -401,9 +374,9 @@ impl Rocket {
/// #[launch] /// #[launch]
/// fn rocket() -> rocket::Rocket { /// fn rocket() -> rocket::Rocket {
/// rocket::ignite() /// rocket::ignite()
/// .attach(AdHoc::on_launch("Config Printer", |rocket| { /// .attach(AdHoc::on_liftoff("Print Config", |rocket| Box::pin(async move {
/// println!("Rocket launch config: {:?}", rocket.config()); /// println!("Rocket launch config: {:?}", rocket.config());
/// })) /// })))
/// } /// }
/// ``` /// ```
#[inline(always)] #[inline(always)]
@ -535,19 +508,17 @@ impl Rocket {
self.shutdown_handle.clone() self.shutdown_handle.clone()
} }
/// Perform "pre-launch" checks: verify: // Perform "pre-launch" checks: verify:
/// * there are no routing colisionns // * there are no routing colisionns
/// * there were no fairing failures // * there were no fairing failures
/// * a secret key, if needed, is securely configured // * a secret key, if needed, is securely configured
pub(crate) async fn prelaunch_check(&mut self) -> Result<(), Error> { pub async fn _ignite(mut self) -> Result<Rocket, Error> {
// Check for routing collisions.
if let Err(collisions) = self.router.finalize() { if let Err(collisions) = self.router.finalize() {
return Err(Error::new(ErrorKind::Collisions(collisions))); return Err(Error::new(ErrorKind::Collisions(collisions)));
} }
if let Some(failures) = self.fairings.failures() { // Check for safely configured secrets.
return Err(Error::new(ErrorKind::FailedFairings(failures.to_vec())))
}
#[cfg(feature = "secrets")] #[cfg(feature = "secrets")]
if !self.config.secret_key.is_provided() { if !self.config.secret_key.is_provided() {
let profile = self.figment.profile(); let profile = self.figment.profile();
@ -567,7 +538,19 @@ impl Rocket {
} }
}; };
Ok(()) // Run launch fairings. Check for failed fairings.
self = Fairings::handle_launch(self).await;
if let Some(failures) = self.fairings.failures() {
return Err(Error::new(ErrorKind::FailedFairings(failures.to_vec())))
}
// Freeze managed state for synchronization-free accesses later.
self.managed_state.freeze();
// Show all of the fairings.
self.fairings.pretty_print_counts();
Ok(self)
} }
/// Returns a `Future` that drives the server, listening for and dispatching /// Returns a `Future` that drives the server, listening for and dispatching
@ -593,60 +576,18 @@ impl Rocket {
/// # } /// # }
/// } /// }
/// ``` /// ```
pub async fn launch(mut self) -> Result<(), Error> { pub async fn launch(self) -> Result<(), Error> {
use std::net::ToSocketAddrs; let rocket = self._ignite().await?;
use futures::future::Either;
use crate::http::private::bind_tcp;
self.prelaunch_check().await?; rocket.default_tcp_http_server(|rocket| Box::pin(async move {
let proto = rocket.config.tls_enabled().then(|| "https").unwrap_or("http");
let addr = format!("{}://{}:{}", proto, rocket.config.address, rocket.config.port);
launch_info!("{}{} {}",
Paint::emoji("🚀 "),
Paint::default("Rocket has launched from").bold(),
Paint::default(addr).bold().underline());
let full_addr = format!("{}:{}", self.config.address, self.config.port); rocket.fairings.handle_liftoff(&rocket).await;
let addr = full_addr.to_socket_addrs() })).await
.map(|mut addrs| addrs.next().expect(">= 1 socket addr"))
.map_err(|e| Error::new(ErrorKind::Io(e)))?;
// If `ctrl-c` shutdown is enabled, we `select` on `the ctrl-c` signal
// and server. Otherwise, we only wait on the `server`, hence `pending`.
let shutdown_handle = self.shutdown_handle.clone();
let shutdown_signal = match self.config.ctrlc {
true => tokio::signal::ctrl_c().boxed(),
false => futures::future::pending().boxed(),
};
#[cfg(feature = "tls")]
let server = {
use crate::http::private::tls::bind_tls;
if let Some(tls_config) = &self.config.tls {
let (certs, key) = tls_config.to_readers().map_err(ErrorKind::Io)?;
let l = bind_tls(addr, certs, key).await.map_err(ErrorKind::Bind)?;
self.listen_on(l).boxed()
} else {
let l = bind_tcp(addr).await.map_err(ErrorKind::Bind)?;
self.listen_on(l).boxed()
}
};
#[cfg(not(feature = "tls"))]
let server = {
let l = bind_tcp(addr).await.map_err(ErrorKind::Bind)?;
self.listen_on(l).boxed()
};
match futures::future::select(shutdown_signal, server).await {
Either::Left((Ok(()), server)) => {
// Ctrl-was pressed. Signal shutdown, wait for the server.
shutdown_handle.shutdown();
server.await
}
Either::Left((Err(err), server)) => {
// Error setting up ctrl-c signal. Let the user know.
warn!("Failed to enable `ctrl-c` graceful signal shutdown.");
info_!("Error: {}", err);
server.await
}
// Server shut down before Ctrl-C; return the result.
Either::Right((result, _)) => result,
}
} }
} }

View File

@ -2,7 +2,7 @@ use std::io;
use std::sync::Arc; use std::sync::Arc;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use futures::future::{FutureExt, Future}; use futures::future::{self, FutureExt, Future, TryFutureExt, BoxFuture};
use tokio::sync::oneshot; use tokio::sync::oneshot;
use yansi::Paint; use yansi::Paint;
@ -11,15 +11,15 @@ use crate::form::Form;
use crate::response::{Response, Body}; use crate::response::{Response, Body};
use crate::outcome::Outcome; use crate::outcome::Outcome;
use crate::error::{Error, ErrorKind}; use crate::error::{Error, ErrorKind};
use crate::logger::PaintExt;
use crate::ext::AsyncReadExt; use crate::ext::AsyncReadExt;
use crate::http::{Method, Status, Header, hyper}; use crate::http::{Method, Status, Header, hyper};
use crate::http::private::{Listener, Connection, Incoming}; use crate::http::private::{Listener, Connection, Incoming};
use crate::http::uri::Origin; use crate::http::uri::Origin;
use crate::http::private::bind_tcp;
// A token returned to force the execution of one method before another. // A token returned to force the execution of one method before another.
pub(crate) struct Token; pub(crate) struct RequestToken;
async fn handle<Fut, T, F>(name: Option<&str>, run: F) -> Option<T> async fn handle<Fut, T, F>(name: Option<&str>, run: F) -> Option<T>
where F: FnOnce() -> Fut, Fut: Future<Output = T>, where F: FnOnce() -> Fut, Fut: Future<Output = T>,
@ -186,7 +186,7 @@ impl Rocket {
&self, &self,
req: &mut Request<'_>, req: &mut Request<'_>,
data: &mut Data data: &mut Data
) -> Token { ) -> RequestToken {
// Check if this is a form and if the form contains the special _method // Check if this is a form and if the form contains the special _method
// field which we use to reinterpret the request's method. // field which we use to reinterpret the request's method.
let (min_len, max_len) = ("_method=get".len(), "_method=delete".len()); let (min_len, max_len) = ("_method=get".len(), "_method=delete".len());
@ -207,13 +207,13 @@ impl Rocket {
// Run request fairings. // Run request fairings.
self.fairings.handle_request(req, data).await; self.fairings.handle_request(req, data).await;
Token RequestToken
} }
#[inline] #[inline]
pub(crate) async fn dispatch<'s, 'r: 's>( pub(crate) async fn dispatch<'s, 'r: 's>(
&'s self, &'s self,
_token: Token, _token: RequestToken,
request: &'r Request<'s>, request: &'r Request<'s>,
data: Data data: Data
) -> Response<'r> { ) -> Response<'r> {
@ -370,33 +370,43 @@ impl Rocket {
crate::catcher::default(Status::InternalServerError, req) crate::catcher::default(Status::InternalServerError, req)
} }
// TODO.async: Solidify the Listener APIs and make this function public pub async fn default_tcp_http_server<C>(mut self, ready: C) -> Result<(), Error>
pub(crate) async fn listen_on<L>(mut self, listener: L) -> Result<(), Error> where C: for<'a> Fn(&'a Self) -> BoxFuture<'a, ()>
where L: Listener + Send + Unpin + 'static,
<L as Listener>::Connection: Send + Unpin + 'static,
{ {
// We do this twice if `listen_on` was called through `launch()` but use std::net::ToSocketAddrs;
// only once if `listen_on()` gets called directly.
self.prelaunch_check().await?;
// Freeze managed state for synchronization-free accesses later. // Determine the address we're going to serve on.
self.managed_state.freeze(); let addr = format!("{}:{}", self.config.address, self.config.port);
let mut addr = addr.to_socket_addrs()
.map(|mut addrs| addrs.next().expect(">= 1 socket addr"))
.map_err(|e| Error::new(ErrorKind::Io(e)))?;
// Determine the address and port we actually bound to. #[cfg(feature = "tls")]
self.config.port = listener.local_addr().map(|a| a.port()).unwrap_or(0); if let Some(ref config) = self.config.tls {
let proto = self.config.tls.as_ref().map_or("http://", |_| "https://"); use crate::http::private::tls::bind_tls;
let full_addr = format!("{}:{}", self.config.address, self.config.port);
// Run the launch fairings. let (certs, key) = config.to_readers().map_err(ErrorKind::Io)?;
self.fairings.pretty_print_counts(); let l = bind_tls(addr, certs, key).await.map_err(ErrorKind::Bind)?;
self.fairings.handle_launch(&self); addr = l.local_addr().unwrap_or(addr);
self.config.address = addr.ip();
self.config.port = addr.port();
ready(&mut self).await;
return self.http_server(l).await;
}
launch_info!("{}{} {}{}", let l = bind_tcp(addr).await.map_err(ErrorKind::Bind)?;
Paint::emoji("🚀 "), addr = l.local_addr().unwrap_or(addr);
Paint::default("Rocket has launched from").bold(), self.config.address = addr.ip();
Paint::default(proto).bold().underline(), self.config.port = addr.port();
Paint::default(&full_addr).bold().underline()); ready(&mut self).await;
self.http_server(l).await
}
// TODO.async: Solidify the Listener APIs and make this function public
pub async fn http_server<L>(mut self, listener: L) -> Result<(), Error>
where L: Listener + Send + Unpin + 'static,
<L as Listener>::Connection: Send + Unpin + 'static
{
// Determine keep-alives. // Determine keep-alives.
let http1_keepalive = self.config.keep_alive != 0; let http1_keepalive = self.config.keep_alive != 0;
let http2_keep_alive = match self.config.keep_alive { let http2_keep_alive = match self.config.keep_alive {
@ -404,6 +414,13 @@ impl Rocket {
n => Some(std::time::Duration::from_secs(n as u64)) n => Some(std::time::Duration::from_secs(n as u64))
}; };
// Get the shutdown handle (to initiate) and signal (when initiated).
let shutdown_handle = self.shutdown_handle.clone();
let shutdown_signal = match self.config.ctrlc {
true => tokio::signal::ctrl_c().boxed(),
false => future::pending().boxed(),
};
// 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.take() let mut shutdown_receiver = self.shutdown_receiver.take()
.expect("shutdown receiver has already been used"); .expect("shutdown receiver has already been used");
@ -420,12 +437,30 @@ impl Rocket {
}); });
// NOTE: `hyper` uses `tokio::spawn()` as the default executor. // NOTE: `hyper` uses `tokio::spawn()` as the default executor.
hyper::Server::builder(Incoming::from_listener(listener)) let server = hyper::Server::builder(Incoming::from_listener(listener))
.http1_keepalive(http1_keepalive) .http1_keepalive(http1_keepalive)
.http2_keep_alive_interval(http2_keep_alive) .http2_keep_alive_interval(http2_keep_alive)
.serve(service) .serve(service)
.with_graceful_shutdown(async move { shutdown_receiver.recv().await; }) .with_graceful_shutdown(async move { shutdown_receiver.recv().await; })
.await .map_err(|e| Error::new(ErrorKind::Runtime(Box::new(e))));
.map_err(|e| Error::new(ErrorKind::Runtime(Box::new(e))))
tokio::pin!(server);
let selecter = future::select(shutdown_signal, server);
match selecter.await {
future::Either::Left((Ok(()), server)) => {
// Ctrl-was pressed. Signal shutdown, wait for the server.
shutdown_handle.shutdown();
server.await
}
future::Either::Left((Err(err), server)) => {
// Error setting up ctrl-c signal. Let the user know.
warn!("Failed to enable `ctrl-c` graceful signal shutdown.");
info_!("Error: {}", err);
server.await
}
// Server shut down before Ctrl-C; return the result.
future::Either::Right((result, _)) => result,
}
} }
} }

View File

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

View File

@ -0,0 +1,79 @@
use rocket::error::Error;
use rocket::fairing::AdHoc;
#[rocket::async_test]
async fn test_inspectable_launch_state() -> Result<(), Error> {
let rocket = rocket::ignite()
.attach(AdHoc::on_launch("Add State", |rocket| async {
Ok(rocket.manage("Hi!"))
}))
._ignite()
.await?;
let state = rocket.state::<&'static str>();
assert_eq!(state, Some(&"Hi!"));
Ok(())
}
#[rocket::async_test]
async fn test_inspectable_launch_state_in_liftoff() -> Result<(), Error> {
let rocket = rocket::ignite()
.attach(AdHoc::on_launch("Add State", |rocket| async {
Ok(rocket.manage("Hi!"))
}))
.attach(AdHoc::on_launch("Inspect State", |rocket| async {
let state = rocket.state::<&'static str>();
assert_eq!(state, Some(&"Hi!"));
Ok(rocket)
}))
.attach(AdHoc::on_liftoff("Inspect State", |rocket| Box::pin(async move {
let state = rocket.state::<&'static str>();
assert_eq!(state, Some(&"Hi!"));
})))
._ignite()
.await?;
let state = rocket.state::<&'static str>();
assert_eq!(state, Some(&"Hi!"));
Ok(())
}
#[rocket::async_test]
async fn test_launch_state_is_well_ordered() -> Result<(), Error> {
let rocket = rocket::ignite()
.attach(AdHoc::on_launch("Inspect State Pre", |rocket| async {
let state = rocket.state::<&'static str>();
assert_eq!(state, None);
Ok(rocket)
}))
.attach(AdHoc::on_launch("Add State", |rocket| async {
Ok(rocket.manage("Hi!"))
}))
.attach(AdHoc::on_launch("Inspect State", |rocket| async {
let state = rocket.state::<&'static str>();
assert_eq!(state, Some(&"Hi!"));
Ok(rocket)
}))
._ignite()
.await?;
let state = rocket.state::<&'static str>();
assert_eq!(state, Some(&"Hi!"));
Ok(())
}
#[should_panic]
#[rocket::async_test]
async fn negative_test_launch_state() {
let _ = rocket::ignite()
.attach(AdHoc::on_launch("Add State", |rocket| async {
Ok(rocket.manage("Hi!"))
}))
.attach(AdHoc::on_launch("Inspect State", |rocket| async {
let state = rocket.state::<&'static str>();
assert_ne!(state, Some(&"Hi!"));
Ok(rocket)
}))
._ignite()
.await;
}

View File

@ -22,7 +22,7 @@ fn index(counter: State<'_, Counter>) -> String {
fn rocket() -> rocket::Rocket { fn rocket() -> rocket::Rocket {
rocket::ignite() rocket::ignite()
.mount("/", routes![index]) .mount("/", routes![index])
.attach(AdHoc::on_attach("Outer", |rocket| async { .attach(AdHoc::on_launch("Outer", |rocket| async {
let counter = Counter::default(); let counter = Counter::default();
counter.attach.fetch_add(1, Ordering::Relaxed); counter.attach.fetch_add(1, Ordering::Relaxed);
let rocket = rocket.manage(counter) let rocket = rocket.manage(counter)

View File

@ -6,8 +6,10 @@ use rocket::futures::channel::oneshot;
async fn on_launch_fairing_can_inspect_port() { async fn on_launch_fairing_can_inspect_port() {
let (tx, rx) = oneshot::channel(); let (tx, rx) = oneshot::channel();
let rocket = rocket::custom(Config { port: 0, ..Config::debug_default() }) let rocket = rocket::custom(Config { port: 0, ..Config::debug_default() })
.attach(AdHoc::on_launch("Send Port -> Channel", move |rocket| { .attach(AdHoc::on_liftoff("Send Port -> Channel", move |rocket| {
tx.send(rocket.config().port).unwrap(); Box::pin(async move {
tx.send(rocket.config().port).unwrap();
})
})); }));
rocket::tokio::spawn(rocket.launch()); rocket::tokio::spawn(rocket.launch());

View File

@ -8,7 +8,7 @@ async fn test_await_timer_inside_attach() {
} }
rocket::ignite() rocket::ignite()
.attach(rocket::fairing::AdHoc::on_attach("1", |rocket| async { .attach(rocket::fairing::AdHoc::on_launch("1", |rocket| async {
do_async_setup().await; do_async_setup().await;
Ok(rocket) Ok(rocket)
})); }));

View File

@ -7,9 +7,8 @@ use rocket::fairing::AdHoc;
#[rocket::launch] #[rocket::launch]
fn rocket() -> rocket::Rocket { fn rocket() -> rocket::Rocket {
rocket::ignite() rocket::ignite()
.attach(AdHoc::on_attach("Config Reader", |rocket| async { .attach(AdHoc::on_liftoff("Config Reader", |rocket| Box::pin(async move {
let value = rocket.figment().find_value("").unwrap(); let value = rocket.figment().find_value("").unwrap();
println!("{:#?}", value); println!("{:#?}", value);
Ok(rocket) })))
}))
} }

View File

@ -23,19 +23,11 @@ impl Fairing for Counter {
fn info(&self) -> Info { fn info(&self) -> Info {
Info { Info {
name: "GET/POST Counter", name: "GET/POST Counter",
kind: Kind::Attach | Kind::Request kind: Kind::Launch | Kind::Request
} }
} }
async fn on_request(&self, request: &mut Request<'_>, _: &mut Data) { async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
if request.method() == Method::Get {
self.get.fetch_add(1, Ordering::Relaxed);
} else if request.method() == Method::Post {
self.post.fetch_add(1, Ordering::Relaxed);
}
}
async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
#[get("/counts")] #[get("/counts")]
fn counts(counts: State<'_, Counter>) -> String { fn counts(counts: State<'_, Counter>) -> String {
let get_count = counts.get.load(Ordering::Relaxed); let get_count = counts.get.load(Ordering::Relaxed);
@ -45,6 +37,14 @@ impl Fairing for Counter {
Ok(rocket.manage(self.clone()).mount("/", routes![counts])) Ok(rocket.manage(self.clone()).mount("/", routes![counts]))
} }
async fn on_request(&self, request: &mut Request<'_>, _: &mut Data) {
if request.method() == Method::Get {
self.get.fetch_add(1, Ordering::Relaxed);
} else if request.method() == Method::Post {
self.post.fetch_add(1, Ordering::Relaxed);
}
}
} }
#[put("/")] #[put("/")]
@ -62,16 +62,16 @@ 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| async { .attach(AdHoc::on_launch("Token State", |rocket| async {
println!("Adding token managed state..."); println!("Adding token managed state...");
match rocket.figment().extract_inner("token") { match rocket.figment().extract_inner("token") {
Ok(value) => Ok(rocket.manage(Token(value))), Ok(value) => Ok(rocket.manage(Token(value))),
Err(_) => Err(rocket) Err(_) => Err(rocket)
} }
})) }))
.attach(AdHoc::on_launch("Launch Message", |_| { .attach(AdHoc::on_liftoff("Liftoff Message", |_| Box::pin(async move {
println!("Rocket is about to launch!"); println!("We have liftoff!");
})) })))
.attach(AdHoc::on_request("PUT Rewriter", |req, _| { .attach(AdHoc::on_request("PUT Rewriter", |req, _| {
Box::pin(async move { Box::pin(async move {
println!(" => Incoming request: {}", req); println!(" => Incoming request: {}", req);

View File

@ -12,7 +12,7 @@ async fn rendezvous(barrier: State<'_, Barrier>) -> &'static str {
pub fn rocket() -> rocket::Rocket { pub fn rocket() -> rocket::Rocket {
rocket::ignite() rocket::ignite()
.mount("/", routes![rendezvous]) .mount("/", routes![rendezvous])
.attach(AdHoc::on_attach("Add Channel", |rocket| async { .attach(AdHoc::on_launch("Add Channel", |rocket| async {
Ok(rocket.manage(Barrier::new(2))) Ok(rocket.manage(Barrier::new(2)))
})) }))
} }

View File

@ -11,7 +11,6 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
diesel = { version = "1.3", features = ["sqlite", "r2d2"] } diesel = { version = "1.3", features = ["sqlite", "r2d2"] }
diesel_migrations = "1.3" diesel_migrations = "1.3"
log = "0.4"
[dev-dependencies] [dev-dependencies]
parking_lot = "0.11" parking_lot = "0.11"

View File

@ -1,31 +1,25 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
#[macro_use] extern crate diesel; #[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_migrations; #[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate log;
#[macro_use] extern crate rocket_contrib; #[macro_use] extern crate rocket_contrib;
#[cfg(test)]
mod tests;
mod task; mod task;
#[cfg(test)] mod tests;
use std::fmt::Display;
use rocket::Rocket; use rocket::Rocket;
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
use rocket::request::FlashMessage; use rocket::request::FlashMessage;
use rocket::form::Form;
use rocket::response::{Flash, Redirect}; use rocket::response::{Flash, Redirect};
use rocket_contrib::{templates::Template, serve::{StaticFiles, crate_relative}}; use rocket::form::Form;
use diesel::SqliteConnection;
use rocket_contrib::templates::Template;
use rocket_contrib::serve::{StaticFiles, crate_relative};
use crate::task::{Task, Todo}; use crate::task::{Task, Todo};
// This macro from `diesel_migrations` defines an `embedded_migrations` module
// containing a function named `run`. This allows the example to be run and
// tested without any outside setup of the database.
embed_migrations!();
#[database("sqlite_database")] #[database("sqlite_database")]
pub struct DbConn(SqliteConnection); pub struct DbConn(diesel::SqliteConnection);
#[derive(Debug, serde::Serialize)] #[derive(Debug, serde::Serialize)]
struct Context { struct Context {
@ -34,7 +28,7 @@ struct Context {
} }
impl Context { impl Context {
pub async fn err<M: Display>(conn: &DbConn, msg: M) -> Context { pub async fn err<M: std::fmt::Display>(conn: &DbConn, msg: M) -> Context {
Context { Context {
msg: Some(("error".into(), msg.to_string())), msg: Some(("error".into(), msg.to_string())),
tasks: Task::all(conn).await.unwrap_or_default() tasks: Task::all(conn).await.unwrap_or_default()
@ -97,23 +91,27 @@ async fn index(msg: Option<FlashMessage<'_>>, conn: DbConn) -> Template {
} }
async fn run_db_migrations(rocket: Rocket) -> Result<Rocket, Rocket> { async fn run_db_migrations(rocket: Rocket) -> Result<Rocket, Rocket> {
DbConn::get_one(&rocket).await // This macro from `diesel_migrations` defines an `embedded_migrations`
.expect("database connection") // module containing a function named `run`. This allows the example to be
.run(|c| match embedded_migrations::run(c) { // run and tested without any outside setup of the database.
Ok(()) => Ok(rocket), embed_migrations!();
Err(e) => {
error!("Failed to run database migrations: {:?}", e); let conn = DbConn::get_one(&rocket).await.expect("database connection");
Err(rocket) match conn.run(|c| embedded_migrations::run(c)).await {
} Ok(()) => Ok(rocket),
}).await Err(e) => {
error!("Failed to run database migrations: {:?}", e);
Err(rocket)
}
}
} }
#[launch] #[launch]
fn rocket() -> Rocket { fn rocket() -> Rocket {
rocket::ignite() rocket::ignite()
.attach(DbConn::fairing()) .attach(DbConn::fairing())
.attach(AdHoc::on_attach("Database Migrations", run_db_migrations))
.attach(Template::fairing()) .attach(Template::fairing())
.attach(AdHoc::on_launch("Database Migrations", run_db_migrations))
.mount("/", StaticFiles::from(crate_relative!("/static"))) .mount("/", StaticFiles::from(crate_relative!("/static")))
.mount("/", routes![index]) .mount("/", routes![index])
.mount("/todo", routes![new, toggle, delete]) .mount("/todo", routes![new, toggle, delete])

View File

@ -16,8 +16,7 @@ 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 $client = Client::tracked(super::rocket()).await.expect("Rocket client");
let $client = Client::tracked(rocket).await.expect("Rocket client");
let db = super::DbConn::get_one($client.rocket()).await; let db = super::DbConn::get_one($client.rocket()).await;
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).await.expect("failed to delete all tasks for testing"); Task::delete_all(&$conn).await.expect("failed to delete all tasks for testing");
@ -27,6 +26,16 @@ macro_rules! run_test {
}) })
} }
#[test]
fn test_index() {
use rocket::local::blocking::Client;
let _lock = DB_LOCK.lock();
let client = Client::tracked(super::rocket()).unwrap();
let response = client.get("/").dispatch();
assert_eq!(response.status(), Status::Ok);
}
#[test] #[test]
fn test_insertion_deletion() { fn test_insertion_deletion() {
run_test!(|client, conn| { run_test!(|client, conn| {

View File

@ -74,20 +74,19 @@ be significant.
There are four events for which Rocket issues fairing callbacks. Each of these There are four events for which Rocket issues fairing callbacks. Each of these
events is described below: events is described below:
* **Attach (`on_attach`)**
An attach callback is called when a fairing is first attached via the
[`attach`](@api/rocket/struct.Rocket.html#method.attach) method. An attach
callback can arbitrarily modify the `Rocket` instance being constructed and
optionally abort launch. Attach fairings are commonly used to parse and
validate configuration values, aborting on bad configurations, and inserting
the parsed value into managed state for later retrieval.
* **Launch (`on_launch`)** * **Launch (`on_launch`)**
A launch callback is called immediately before the Rocket application has A launch callback is called just prior to liftoff while launching the
launched. A launch callback can inspect the `Rocket` instance being application. A launch callback can arbitrarily modify the `Rocket` instance
launched. A launch callback can be a convenient hook for launching services being constructed. They are are commonly used to parse and validate
configuration values, aborting on bad configurations, and inserting the
parsed value into managed state for later retrieval.
* **liftoff (`on_liftoff`)**
A liftoff callback is called immediately after a Rocket application has
launched. A liftoff callback can inspect the `Rocket` instance being
launched. A liftoff callback can be a convenient hook for launching services
related to the Rocket application being launched. related to the Rocket application being launched.
* **Request (`on_request`)** * **Request (`on_request`)**
@ -112,14 +111,14 @@ Recall that a fairing is any type that implements the [`Fairing`] trait. A
`Fairing` implementation has one required method: [`info`], which returns an `Fairing` implementation has one required method: [`info`], which returns an
[`Info`] structure. This structure is used by Rocket to assign a name to the [`Info`] structure. This structure is used by Rocket to assign a name to the
fairing and determine the set of callbacks the fairing is registering for. A fairing and determine the set of callbacks the fairing is registering for. A
`Fairing` can implement any of the available callbacks: [`on_attach`], `Fairing` can implement any of the available callbacks: [`on_launch`],
[`on_launch`], [`on_request`], and [`on_response`]. Each callback has a default [`on_liftoff`], [`on_request`], and [`on_response`]. Each callback has a default
implementation that does absolutely nothing. implementation that does absolutely nothing.
[`Info`]: @api/rocket/fairing/struct.Info.html [`Info`]: @api/rocket/fairing/struct.Info.html
[`info`]: @api/rocket/fairing/trait.Fairing.html#tymethod.info [`info`]: @api/rocket/fairing/trait.Fairing.html#tymethod.info
[`on_attach`]: @api/rocket/fairing/trait.Fairing.html#method.on_attach
[`on_launch`]: @api/rocket/fairing/trait.Fairing.html#method.on_launch [`on_launch`]: @api/rocket/fairing/trait.Fairing.html#method.on_launch
[`on_liftoff`]: @api/rocket/fairing/trait.Fairing.html#method.on_liftoff
[`on_request`]: @api/rocket/fairing/trait.Fairing.html#method.on_request [`on_request`]: @api/rocket/fairing/trait.Fairing.html#method.on_request
[`on_response`]: @api/rocket/fairing/trait.Fairing.html#method.on_response [`on_response`]: @api/rocket/fairing/trait.Fairing.html#method.on_response
@ -133,10 +132,10 @@ need simply be thread-safe and statically available or heap allocated.
### Example ### Example
Imagine that we want to record the number of `GET` and `POST` requests that our As an example, we want to record the number of `GET` and `POST` requests that
application has received. While we could do this with request guards and managed our application has received. While we could do this with request guards and
state, it would require us to annotate every `GET` and `POST` request with managed state, it would require us to annotate every `GET` and `POST` request
custom types, polluting handler signatures. Instead, we can create a simple with custom types, polluting handler signatures. Instead, we can create a simple
fairing that acts globally. fairing that acts globally.
The code for a `Counter` fairing below implements exactly this. The fairing The code for a `Counter` fairing below implements exactly this. The fairing
@ -205,23 +204,23 @@ documentation](@api/rocket/fairing/trait.Fairing.html#example).
For simple occasions, implementing the `Fairing` trait can be cumbersome. This For simple occasions, implementing the `Fairing` trait can be cumbersome. This
is why Rocket provides the [`AdHoc`] type, which creates a fairing from a simple is why Rocket provides the [`AdHoc`] type, which creates a fairing from a simple
function or closure. Using the `AdHoc` type is easy: simply call the function or closure. Using the `AdHoc` type is easy: simply call the
`on_attach`, `on_launch`, `on_request`, or `on_response` constructors on `AdHoc` `on_launch`, `on_liftoff`, `on_request`, or `on_response` constructors on
to create an `AdHoc` structure from a function or closure. `AdHoc` to create an `AdHoc` structure from a function or closure.
As an example, the code below creates a `Rocket` instance with two attached As an example, the code below creates a `Rocket` instance with two attached
ad-hoc fairings. The first, a launch fairing named "Launch Printer", simply ad-hoc fairings. The first, a liftoff fairing named "Liftoff Printer", simply
prints a message indicating that the application is about to launch. The prints a message indicating that the application has launched. The second named
second named "Put Rewriter", a request fairing, rewrites the method of all "Put Rewriter", a request fairing, rewrites the method of all requests to be
requests to be `PUT`. `PUT`.
```rust ```rust
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
use rocket::http::Method; use rocket::http::Method;
rocket::ignite() rocket::ignite()
.attach(AdHoc::on_launch("Launch Printer", |_| { .attach(AdHoc::on_liftoff("Liftoff Printer", |_| Box::pin(async move {
println!("Rocket is about to launch! Exciting! Here we go..."); println!("...annnddd we have liftoff!");
})) })))
.attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move { .attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move {
req.set_method(Method::Put); req.set_method(Method::Put);
}))); })));