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> {
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) {
Ok(config) => config,
Err(e) => dberr!("config", db, "{}", e, rocket),

View File

@ -193,15 +193,11 @@ impl Fairing for SpaceHelmet {
fn info(&self) -> Info {
Info {
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>) {
self.apply(res);
}
fn on_launch(&self, rocket: &Rocket) {
async fn on_liftoff(&self, rocket: &Rocket) {
if rocket.config().tls_enabled()
&& rocket.figment().profile() != rocket::Config::DEBUG_PROFILE
&& !self.is_enabled::<Hsts>()
@ -212,4 +208,8 @@ impl Fairing for SpaceHelmet {
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 {
fn info(&self) -> Info {
// on_request only applies in debug mode, so only enable it in debug.
#[cfg(debug_assertions)] let kind = Kind::Attach | Kind::Request;
#[cfg(not(debug_assertions))] let kind = Kind::Attach;
#[cfg(debug_assertions)] let kind = Kind::Launch | Kind::Request;
#[cfg(not(debug_assertions))] let kind = Kind::Launch;
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
/// template engines. In debug mode, the `ContextManager::new` method
/// 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};
let configured_dir = rocket.figment()
@ -186,7 +186,7 @@ impl Fairing for TemplateFairing {
#[cfg(debug_assertions)]
async fn on_request(&self, req: &mut rocket::Request<'_>, _data: &mut rocket::Data) {
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);
}

View File

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

View File

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

View File

@ -61,17 +61,17 @@ mod templates_tests {
#[test]
fn test_tera_templates() {
let rocket = rocket();
let client = Client::debug(rocket()).unwrap();
let mut map = HashMap::new();
map.insert("title", "_test_");
map.insert("content", "<script />");
// 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()));
// 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()));
}
@ -105,13 +105,13 @@ mod templates_tests {
#[test]
fn test_handlebars_templates() {
let rocket = rocket();
let client = Client::debug(rocket()).unwrap();
let mut map = HashMap::new();
map.insert("title", "_test_");
map.insert("content", "<script /> hi");
// 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()));
}

View File

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

View File

@ -56,8 +56,10 @@ use crate::config::SecretKey;
pub struct Config {
/// The selected profile. **(default: _debug_ `debug` / _release_ `release`)**
///
/// **Note:** This field is never serialized nor deserialized. It is set to
/// the value of the selected `Profile` during extraction.
/// **Note:** This field is never serialized nor deserialized. When part of
/// 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)]
pub profile: Profile,
/// IP address to serve on. **(default: `127.0.0.1`)**

View File

@ -113,8 +113,9 @@ pub enum TempFile<'v> {
}
impl<'v> TempFile<'v> {
/// Persists the temporary file, atomically linking it at `path`. The
/// `self.path()` is updated to `path`.
/// Persists the temporary file, moving it to `path`. If a file exists at
/// 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
/// 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
///
/// Use the [`on_attach`](#method.on_attach), [`on_launch`](#method.on_launch),
/// [`on_request`](#method.on_request), or [`on_response`](#method.on_response)
/// constructors to create an `AdHoc` structure from a function or closure.
/// Then, simply attach the structure to the `Rocket` instance.
/// Use [`AdHoc::on_launch`], [`AdHoc::on_liftoff`], [`AdHoc::on_request()`], or
/// [`AdHoc::on_response()`] to create an `AdHoc` structure from a function or
/// closure. Then, simply attach the structure to the `Rocket` instance.
///
/// # Example
///
/// The following snippet creates a `Rocket` instance with two ad-hoc fairings.
/// The first, a launch fairing named "Launch Printer", simply prints a message
/// indicating that the application is about to the launch. The second named
/// "Put Rewriter", a request fairing, rewrites the method of all requests to be
/// `PUT`.
/// The first, a liftoff fairing named "Liftoff Printer", simply prints a message
/// indicating that Rocket has launched. The second named "Put Rewriter", a
/// request fairing, rewrites the method of all requests to be `PUT`.
///
/// ```rust
/// use rocket::fairing::AdHoc;
/// use rocket::http::Method;
///
/// rocket::ignite()
/// .attach(AdHoc::on_launch("Launch Printer", |_| {
/// println!("Rocket is about to launch! Exciting! Here we go...");
/// }))
/// .attach(AdHoc::on_request("Put Rewriter", |req, _| {
/// Box::pin(async move {
/// req.set_method(Method::Put);
/// })
/// }));
/// .attach(AdHoc::on_liftoff("Liftoff Printer", |_| Box::pin(async move {
/// println!("...annnddd we have liftoff!");
/// })))
/// .attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move {
/// req.set_method(Method::Put);
/// })));
/// ```
pub struct AdHoc {
name: &'static str,
kind: AdHocKind,
}
// macro_rules! Async {
// ($kind:ident <$l:lifetime> ($($param:ty),*) -> $r:ty) => (
// dyn for<$l> $kind($($param),*) -> futures::future::BoxFuture<$l, $r>
// + Send + 'static
// );
// ($kind:ident ($($param:ty),*) -> $r:ty) => (
// dyn $kind($($param),*) -> futures::future::BoxFuture<'static, $r>
// + Send + Sync + 'static
// );
// ($kind:ident <$l:lifetime> ($($param:ty),*)) => (
// Async!($kind <$l> ($($param),*) -> ())
// );
// ($kind:ident ($($param:ty),*)) => (
// Async!($kind ($($param),*) -> ())
// );
// }
struct Once<F: ?Sized>(Mutex<Option<Box<F>>>);
impl<F: ?Sized> Once<F> {
fn new(f: Box<F>) -> Self { Once(Mutex::new(Some(f))) }
#[track_caller]
fn take(&self) -> Box<F> {
self.0.lock().expect("Once::lock()").take().expect("Once::take() called once")
}
}
type Result<T = Rocket, E = Rocket> = std::result::Result<T, E>;
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.
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.
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
/// sent to a client.
Response(Box<dyn for<'a> Fn(&'a Request<'_>, &'a mut Response<'_>)
-> BoxFuture<'a, ()> + Send + Sync + 'static>),
Response(Box<dyn for<'r, 'b> Fn(&'r Request<'_>, &'b mut Response<'r>)
-> BoxFuture<'b, ()> + Send + Sync + 'static>),
}
impl AdHoc {
/// Constructs an `AdHoc` attach fairing named `name`. The function `f` will
/// be called by Rocket when this fairing is attached.
/// Constructs an `AdHoc` launch fairing named `name`. The function `f` will
/// be called by Rocket just prior to launch. Returning an `Err` aborts
/// launch.
///
/// # Example
///
/// ```rust
/// use rocket::fairing::AdHoc;
///
/// // The no-op attach fairing.
/// let fairing = AdHoc::on_attach("No-Op", |rocket| async { Ok(rocket) });
/// // The no-op launch fairing.
/// 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,
Fut: Future<Output=Result<Rocket, Rocket>> + Send + 'static,
{
AdHoc {
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))
})
AdHoc { name, kind: AdHocKind::Launch(Once::new(Box::new(|r| Box::pin(f(r))))) }
}
/// 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
///
@ -146,14 +101,14 @@ impl AdHoc {
/// use rocket::fairing::AdHoc;
///
/// // A fairing that prints a message just before launching.
/// let fairing = AdHoc::on_launch("Launch Count", |rocket| {
/// println!("Launching in T-3..2..1..");
/// });
/// let fairing = AdHoc::on_liftoff("Boom!", |_| Box::pin(async move {
/// println!("Rocket has lifted off!");
/// }));
/// ```
pub fn on_launch<F: Send + 'static>(name: &'static str, f: F) -> AdHoc
where F: FnOnce(&Rocket)
pub fn on_liftoff<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
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`
@ -178,17 +133,9 @@ impl AdHoc {
{
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
// where
// 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))))
// }
// }
// FIXME(rustc): We'd like to allow passing `async fn` to these methods...
// https://github.com/rust-lang/rust/issues/64552#issuecomment-666084589
/// Constructs an `AdHoc` response fairing named `name`. The function `f`
/// 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
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)) }
}
/// 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]
impl Fairing for AdHoc {
fn info(&self) -> Info {
let kind = match self.kind {
AdHocKind::Attach(_) => Kind::Attach,
AdHocKind::Launch(_) => Kind::Launch,
AdHocKind::Liftoff(_) => Kind::Liftoff,
AdHocKind::Request(_) => Kind::Request,
AdHocKind::Response(_) => Kind::Response,
};
@ -227,35 +209,28 @@ impl Fairing for AdHoc {
Info { name: self.name, kind }
}
async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
if let AdHocKind::Attach(ref mutex) = self.kind {
let f = mutex.lock()
.expect("AdHoc::Attach lock")
.take()
.expect("internal error: `on_attach` one-call invariant broken");
f(rocket).await
} else {
Ok(rocket)
async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
match self.kind {
AdHocKind::Launch(ref f) => (f.take())(rocket).await,
_ => Ok(rocket)
}
}
fn on_launch(&self, state: &Rocket) {
if let AdHocKind::Launch(ref mutex) = self.kind {
let mut opt = mutex.lock().expect("AdHoc::Launch lock");
let f = opt.take().expect("internal error: `on_launch` one-call invariant broken");
f(state)
async fn on_liftoff(&self, rocket: &Rocket) {
if let AdHocKind::Liftoff(ref f) = self.kind {
(f.take())(rocket).await
}
}
async fn on_request(&self, req: &mut Request<'_>, data: &mut Data) {
if let AdHocKind::Request(ref callback) = self.kind {
callback(req, data).await;
if let AdHocKind::Request(ref f) = self.kind {
f(req, data).await
}
}
async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
if let AdHocKind::Response(ref callback) = self.kind {
callback(req, res).await;
if let AdHocKind::Response(ref f) = self.kind {
f(req, res).await
}
}
}

View File

@ -7,126 +7,133 @@ use yansi::Paint;
#[derive(Default)]
pub struct Fairings {
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`.
attach: Vec<usize>,
launch: Vec<usize>,
liftoff: Vec<usize>,
request: 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 {
#[inline]
pub fn new() -> Fairings {
Fairings::default()
}
pub async fn attach(&mut self, fairing: Box<dyn Fairing>, mut rocket: Rocket) -> Rocket {
// 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 {
pub fn add(&mut self, fairing: Box<dyn Fairing>) -> &dyn Fairing {
let kind = fairing.info().kind;
let index = self.all_fairings.len();
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::Liftoff) { self.liftoff.push(index); }
if kind.is(Kind::Request) { self.request.push(index); }
if kind.is(Kind::Response) { self.response.push(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) {
for fairing in others.all_fairings {
self.add(fairing);
}
}
#[inline(always)]
pub fn handle_launch(&self, rocket: &Rocket) {
for fairing in self.fairings(Kind::Launch) {
fairing.on_launch(rocket);
pub async fn handle_launch(mut rocket: Rocket) -> Rocket {
while rocket.fairings.last_launch < rocket.fairings.launch.len() {
// We're going to move `rocket` while borrowing `fairings`...
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)]
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
}
}
#[inline(always)]
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;
}
}
pub fn failures(&self) -> Option<&[Info]> {
if self.attach_failures.is_empty() {
None
} else {
Some(&self.attach_failures)
match self.failures.is_empty() {
true => None,
false => Some(&self.failures)
}
}
pub fn pretty_print_counts(&self) {
fn pretty_print(f: &Fairings, prefix: &str, kind: Kind) {
let names: Vec<_> = f.fairings(kind).map(|f| f.info().name).collect();
fn pretty_print<'a>(prefix: &str, iter: impl Iterator<Item = &'a dyn Fairing>) {
let names: Vec<_> = iter.map(|f| f.info().name).collect();
if names.is_empty() {
return;
}
let num = names.len();
let joined = names.join(", ");
let (num, joined) = (names.len(), names.join(", "));
info_!("{} {}: {}", Paint::default(num).bold(), prefix, Paint::default(joined).bold());
}
if !self.all_fairings.is_empty() {
info!("{}{}:", Paint::emoji("📡 "), Paint::magenta("Fairings"));
pretty_print(self, "attach", Kind::Attach);
pretty_print(self, "launch", Kind::Launch);
pretty_print(self, "request", Kind::Request);
pretty_print(self, "response", Kind::Response);
pretty_print("launch", iter!(self.launch));
pretty_print("liftoff", iter!(self.liftoff));
pretty_print("request", iter!(self.request));
pretty_print("response", iter!(self.response));
}
}
}
impl std::fmt::Debug for Fairings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn debug_info(fs: &Fairings, kind: Kind) -> Vec<Info> {
fs.fairings(kind).map(|f| f.info()).collect()
fn debug_info<'a>(iter: impl Iterator<Item = &'a dyn Fairing>) -> Vec<Info> {
iter.map(|f| f.info()).collect()
}
f.debug_struct("Fairings")
.field("attach", &debug_info(self, Kind::Attach))
.field("launch", &debug_info(self, Kind::Launch))
.field("request", &debug_info(self, Kind::Request))
.field("response", &debug_info(self, Kind::Response))
.field("launch", &debug_info(iter!(self.launch)))
.field("liftoff", &debug_info(iter!(self.liftoff)))
.field("request", &debug_info(iter!(self.request)))
.field("response", &debug_info(iter!(self.response)))
.finish()
}
}

View File

@ -10,7 +10,7 @@ use std::ops::BitOr;
/// # Example
///
/// A simple `Info` structure that can be used for a `Fairing` that implements
/// all four callbacks:
/// all callbacks:
///
/// ```
/// use rocket::fairing::{Info, Kind};
@ -18,7 +18,7 @@ use std::ops::BitOr;
/// # let _unused_info =
/// Info {
/// 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
/// callbacks:
///
/// * Attach
/// * Launch
/// * Liftoff
/// * Request
/// * Response
///
/// 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,
/// 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)]
pub struct Kind(usize);
#[allow(non_upper_case_globals)]
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.
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.
pub const Request: Kind = Kind(0b0100);
pub const Request: Kind = Kind(1 << 2);
/// `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 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::Request));
///
/// assert!(!launch_and_req.is(Kind::Liftoff));
/// assert!(!launch_and_req.is(Kind::Response));
/// assert!(!launch_and_req.is(Kind::Launch | 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
//! application lifetime. Fairings can be seen as a restricted form of
@ -94,35 +94,39 @@ pub use self::info_kind::{Info, Kind};
///
/// ## 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
/// the `kind` field of the `Info` structure returned from the `info` method.
/// Rocket will only invoke the callbacks set in the `kind` field.
///
/// 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`)**
///
/// A launch callback, represented by the [`Fairing::on_launch()`] method,
/// is called immediately before the Rocket application has launched. At
/// this point, Rocket has opened a socket for listening but has not yet
/// begun accepting connections. A launch callback can inspect the `Rocket`
/// instance being launched.
/// is called just prior to liftoff, while launching the application. The
/// state of the `Rocket` instance is, at this point, not finalized, as it
/// may be modified at will by other launch fairings. As a result, it is
/// 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`)**
///
@ -155,7 +159,7 @@ pub use self::info_kind::{Info, Kind};
/// # Implementing
///
/// 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
/// callback kind in the `kind` field of the returned `Info` structure from
/// [`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
/// 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`.
///
@ -177,8 +181,8 @@ pub use self::info_kind::{Info, Kind};
/// represents the kinds of callbacks the fairing wishes to receive. Rocket
/// will only invoke the callbacks that are flagged in this set. `Kind`
/// structures can be `or`d together to represent any combination of kinds
/// of callbacks. For instance, to request launch and response callbacks,
/// return a `kind` field with the value `Kind::Launch | Kind::Response`.
/// of callbacks. For instance, to request liftoff and response callbacks,
/// return a `kind` field with the value `Kind::Liftoff | Kind::Response`.
///
/// [`info`]: Fairing::info()
///
@ -207,12 +211,12 @@ pub use self::info_kind::{Info, Kind};
/// # unimplemented!()
/// }
///
/// async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
/// async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
/// /* ... */
/// # unimplemented!()
/// }
///
/// fn on_launch(&self, rocket: &Rocket) {
/// async fn on_liftoff(&self, rocket: &Rocket) {
/// /* ... */
/// # unimplemented!()
/// }
@ -231,11 +235,11 @@ pub use self::info_kind::{Info, Kind};
///
/// ## Example
///
/// Imagine that we want to record the number of `GET` and `POST` requests that
/// our application has received. While we could do this with [request guards]
/// and [managed state](crate::State), it would require us to annotate every
/// `GET` and `POST` request with custom types, polluting handler signatures.
/// Instead, we can create a simple fairing that acts globally.
/// As an example, we want to record the number of `GET` and `POST` requests
/// that our application has received. While we could do this with [request
/// guards] and [managed state](crate::State), it would require us to annotate
/// every `GET` and `POST` request with custom types, polluting handler
/// signatures. Instead, we can create a simple fairing that acts globally.
///
/// The `Counter` fairing below records the number of all `GET` and `POST`
/// 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;
/// 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.
///
/// This method is called when a fairing is attached if `Kind::Attach` is in
/// the `kind` field of the `Info` structure for this fairing. The `rocket`
/// parameter is the `Rocket` instance that is currently being built for
/// this application.
/// This method is called when the application is being launched if
/// `Kind::Launch` is in the `kind` field of the `Info` structure for this
/// fairing. The `rocket` parameter is the `Rocket` instance that is
/// currently being built for this application.
///
/// ## Default Implementation
///
/// 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
/// `Kind::Launch` is in the `kind` field of the `Info` structure for this
/// fairing. The `Rocket` parameter corresponds to the application that
/// will be launched.
/// This method is called just after launching the application if
/// `Kind::Liftoff` is in the `kind` field of the `Info` structure for this
/// fairing. The `Rocket` parameter corresponds to the lauched application.
///
/// ## Default Implementation
///
/// The default implementation of this method does nothing.
#[allow(unused_variables)]
fn on_launch(&self, rocket: &Rocket) {}
async fn on_liftoff(&self, _rocket: &Rocket) { }
/// The request callback.
///
@ -439,8 +441,7 @@ pub trait Fairing: Send + Sync + 'static {
/// ## Default Implementation
///
/// 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.
///
@ -452,8 +453,7 @@ pub trait Fairing: Send + Sync + 'static {
/// ## Default Implementation
///
/// 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]
@ -464,22 +464,22 @@ impl<T: Fairing> Fairing for std::sync::Arc<T> {
}
#[inline]
async fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
(self as &T).on_attach(rocket).await
async fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
(self as &T).on_launch(rocket).await
}
#[inline]
fn on_launch(&self, rocket: &Rocket) {
(self as &T).on_launch(rocket)
async fn on_liftoff(&self, rocket: &Rocket) {
(self as &T).on_liftoff(rocket).await
}
#[inline]
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]
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 {
pub(crate) async fn _new(
mut rocket: Rocket,
rocket: Rocket,
tracked: bool
) -> Result<Client, Error> {
rocket.prelaunch_check().await?;
let rocket = rocket._ignite().await?;
let cookies = RwLock::new(cookie::CookieJar::new());
Ok(Client { rocket, tracked, cookies })
}

View File

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

View File

@ -5,7 +5,6 @@ use yansi::Paint;
use state::Container;
use figment::Figment;
use tokio::sync::mpsc;
use futures::future::FutureExt;
use crate::logger;
use crate::config::Config;
@ -127,6 +126,7 @@ impl Rocket {
/// let mut new_config = rocket.config().clone();
/// new_config.port = 8888;
///
/// // Note that this tosses away any non-`Config` parameters in `Figment`.
/// let rocket = rocket.reconfigure(new_config);
/// assert_eq!(rocket.config().port, 8888);
/// assert_eq!(rocket.config().address, Ipv4Addr::new(18, 127, 0, 1));
@ -339,9 +339,8 @@ impl Rocket {
self
}
/// Attaches a fairing to this instance of Rocket. If the fairing is an
/// _attach_ fairing, it is run immediately. All other kinds of fairings
/// will be executed at their appropriate time.
/// Attaches a fairing to this instance of Rocket. No fairings are excuted.
/// Fairings will be executed at their appropriate time.
///
/// # Example
///
@ -353,39 +352,13 @@ impl Rocket {
/// #[launch]
/// fn rocket() -> rocket::Rocket {
/// rocket::ignite()
/// .attach(AdHoc::on_launch("Launch Message", |_| {
/// println!("Rocket is launching!");
/// }))
/// .attach(AdHoc::on_liftoff("Liftoff Message", |_| Box::pin(async {
/// println!("We have liftoff!");
/// })))
/// }
/// ```
pub fn attach<F: Fairing>(mut self, fairing: F) -> Self {
let future = async move {
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.fairings.add(Box::new(fairing));
self
}
@ -401,9 +374,9 @@ impl Rocket {
/// #[launch]
/// fn rocket() -> rocket::Rocket {
/// 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());
/// }))
/// })))
/// }
/// ```
#[inline(always)]
@ -535,19 +508,17 @@ impl Rocket {
self.shutdown_handle.clone()
}
/// Perform "pre-launch" checks: verify:
/// * there are no routing colisionns
/// * there were no fairing failures
/// * a secret key, if needed, is securely configured
pub(crate) async fn prelaunch_check(&mut self) -> Result<(), Error> {
// Perform "pre-launch" checks: verify:
// * there are no routing colisionns
// * there were no fairing failures
// * a secret key, if needed, is securely configured
pub async fn _ignite(mut self) -> Result<Rocket, Error> {
// Check for routing collisions.
if let Err(collisions) = self.router.finalize() {
return Err(Error::new(ErrorKind::Collisions(collisions)));
}
if let Some(failures) = self.fairings.failures() {
return Err(Error::new(ErrorKind::FailedFairings(failures.to_vec())))
}
// Check for safely configured secrets.
#[cfg(feature = "secrets")]
if !self.config.secret_key.is_provided() {
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
@ -593,60 +576,18 @@ impl Rocket {
/// # }
/// }
/// ```
pub async fn launch(mut self) -> Result<(), Error> {
use std::net::ToSocketAddrs;
use futures::future::Either;
use crate::http::private::bind_tcp;
pub async fn launch(self) -> Result<(), Error> {
let rocket = self._ignite().await?;
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);
let addr = full_addr.to_socket_addrs()
.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,
}
rocket.fairings.handle_liftoff(&rocket).await;
})).await
}
}

View File

@ -2,7 +2,7 @@ use std::io;
use std::sync::Arc;
use futures::stream::StreamExt;
use futures::future::{FutureExt, Future};
use futures::future::{self, FutureExt, Future, TryFutureExt, BoxFuture};
use tokio::sync::oneshot;
use yansi::Paint;
@ -11,15 +11,15 @@ use crate::form::Form;
use crate::response::{Response, Body};
use crate::outcome::Outcome;
use crate::error::{Error, ErrorKind};
use crate::logger::PaintExt;
use crate::ext::AsyncReadExt;
use crate::http::{Method, Status, Header, hyper};
use crate::http::private::{Listener, Connection, Incoming};
use crate::http::uri::Origin;
use crate::http::private::bind_tcp;
// 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>
where F: FnOnce() -> Fut, Fut: Future<Output = T>,
@ -186,7 +186,7 @@ impl Rocket {
&self,
req: &mut Request<'_>,
data: &mut Data
) -> Token {
) -> RequestToken {
// Check if this is a form and if the form contains the special _method
// field which we use to reinterpret the request's method.
let (min_len, max_len) = ("_method=get".len(), "_method=delete".len());
@ -207,13 +207,13 @@ impl Rocket {
// Run request fairings.
self.fairings.handle_request(req, data).await;
Token
RequestToken
}
#[inline]
pub(crate) async fn dispatch<'s, 'r: 's>(
&'s self,
_token: Token,
_token: RequestToken,
request: &'r Request<'s>,
data: Data
) -> Response<'r> {
@ -370,33 +370,43 @@ impl Rocket {
crate::catcher::default(Status::InternalServerError, req)
}
// TODO.async: Solidify the Listener APIs and make this function public
pub(crate) async fn listen_on<L>(mut self, listener: L) -> Result<(), Error>
where L: Listener + Send + Unpin + 'static,
<L as Listener>::Connection: Send + Unpin + 'static,
pub async fn default_tcp_http_server<C>(mut self, ready: C) -> Result<(), Error>
where C: for<'a> Fn(&'a Self) -> BoxFuture<'a, ()>
{
// We do this twice if `listen_on` was called through `launch()` but
// only once if `listen_on()` gets called directly.
self.prelaunch_check().await?;
use std::net::ToSocketAddrs;
// Freeze managed state for synchronization-free accesses later.
self.managed_state.freeze();
// Determine the address we're going to serve on.
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.
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);
#[cfg(feature = "tls")]
if let Some(ref config) = self.config.tls {
use crate::http::private::tls::bind_tls;
// Run the launch fairings.
self.fairings.pretty_print_counts();
self.fairings.handle_launch(&self);
let (certs, key) = config.to_readers().map_err(ErrorKind::Io)?;
let l = bind_tls(addr, certs, key).await.map_err(ErrorKind::Bind)?;
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!("{}{} {}{}",
Paint::emoji("🚀 "),
Paint::default("Rocket has launched from").bold(),
Paint::default(proto).bold().underline(),
Paint::default(&full_addr).bold().underline());
let l = bind_tcp(addr).await.map_err(ErrorKind::Bind)?;
addr = l.local_addr().unwrap_or(addr);
self.config.address = addr.ip();
self.config.port = addr.port();
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.
let http1_keepalive = self.config.keep_alive != 0;
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))
};
// 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`.
let mut shutdown_receiver = self.shutdown_receiver.take()
.expect("shutdown receiver has already been used");
@ -420,12 +437,30 @@ impl Rocket {
});
// 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)
.http2_keep_alive_interval(http2_keep_alive)
.serve(service)
.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 {
rocket::ignite()
.mount("/", routes![index])
.attach(AdHoc::on_attach("Outer", |rocket| async {
.attach(AdHoc::on_launch("Outer", |rocket| async {
let counter = Counter::default();
counter.attach.fetch_add(1, Ordering::Relaxed);
let rocket = rocket.manage(counter)

View File

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

View File

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

View File

@ -7,9 +7,8 @@ use rocket::fairing::AdHoc;
#[rocket::launch]
fn rocket() -> rocket::Rocket {
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();
println!("{:#?}", value);
Ok(rocket)
}))
})))
}

View File

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

View File

@ -12,7 +12,7 @@ async fn rendezvous(barrier: State<'_, Barrier>) -> &'static str {
pub fn rocket() -> rocket::Rocket {
rocket::ignite()
.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)))
}))
}

View File

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

View File

@ -1,31 +1,25 @@
#[macro_use] extern crate rocket;
#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate log;
#[macro_use] extern crate rocket_contrib;
#[cfg(test)]
mod tests;
mod task;
#[cfg(test)] mod tests;
use std::fmt::Display;
use rocket::Rocket;
use rocket::fairing::AdHoc;
use rocket::request::FlashMessage;
use rocket::form::Form;
use rocket::response::{Flash, Redirect};
use rocket_contrib::{templates::Template, serve::{StaticFiles, crate_relative}};
use diesel::SqliteConnection;
use rocket::form::Form;
use rocket_contrib::templates::Template;
use rocket_contrib::serve::{StaticFiles, crate_relative};
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")]
pub struct DbConn(SqliteConnection);
pub struct DbConn(diesel::SqliteConnection);
#[derive(Debug, serde::Serialize)]
struct Context {
@ -34,7 +28,7 @@ struct 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 {
msg: Some(("error".into(), msg.to_string())),
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> {
DbConn::get_one(&rocket).await
.expect("database connection")
.run(|c| match embedded_migrations::run(c) {
Ok(()) => Ok(rocket),
Err(e) => {
error!("Failed to run database migrations: {:?}", e);
Err(rocket)
}
}).await
// 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!();
let conn = DbConn::get_one(&rocket).await.expect("database connection");
match conn.run(|c| embedded_migrations::run(c)).await {
Ok(()) => Ok(rocket),
Err(e) => {
error!("Failed to run database migrations: {:?}", e);
Err(rocket)
}
}
}
#[launch]
fn rocket() -> Rocket {
rocket::ignite()
.attach(DbConn::fairing())
.attach(AdHoc::on_attach("Database Migrations", run_db_migrations))
.attach(Template::fairing())
.attach(AdHoc::on_launch("Database Migrations", run_db_migrations))
.mount("/", StaticFiles::from(crate_relative!("/static")))
.mount("/", routes![index])
.mount("/todo", routes![new, toggle, delete])

View File

@ -16,8 +16,7 @@ macro_rules! run_test {
let _lock = DB_LOCK.lock();
rocket::async_test(async move {
let rocket = super::rocket();
let $client = Client::tracked(rocket).await.expect("Rocket client");
let $client = Client::tracked(super::rocket()).await.expect("Rocket client");
let db = super::DbConn::get_one($client.rocket()).await;
let $conn = db.expect("failed to get database connection 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]
fn test_insertion_deletion() {
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
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`)**
A launch callback is called immediately before the Rocket application has
launched. A launch callback can inspect the `Rocket` instance being
launched. A launch callback can be a convenient hook for launching services
A launch callback is called just prior to liftoff while launching the
application. A launch callback can arbitrarily modify the `Rocket` instance
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.
* **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
[`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` can implement any of the available callbacks: [`on_attach`],
[`on_launch`], [`on_request`], and [`on_response`]. Each callback has a default
`Fairing` can implement any of the available callbacks: [`on_launch`],
[`on_liftoff`], [`on_request`], and [`on_response`]. Each callback has a default
implementation that does absolutely nothing.
[`Info`]: @api/rocket/fairing/struct.Info.html
[`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_liftoff`]: @api/rocket/fairing/trait.Fairing.html#method.on_liftoff
[`on_request`]: @api/rocket/fairing/trait.Fairing.html#method.on_request
[`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
Imagine that we want to record the number of `GET` and `POST` requests that our
application has received. While we could do this with request guards and managed
state, it would require us to annotate every `GET` and `POST` request with
custom types, polluting handler signatures. Instead, we can create a simple
As an example, we want to record the number of `GET` and `POST` requests that
our application has received. While we could do this with request guards and
managed state, it would require us to annotate every `GET` and `POST` request
with custom types, polluting handler signatures. Instead, we can create a simple
fairing that acts globally.
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
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
`on_attach`, `on_launch`, `on_request`, or `on_response` constructors on `AdHoc`
to create an `AdHoc` structure from a function or closure.
`on_launch`, `on_liftoff`, `on_request`, or `on_response` constructors on
`AdHoc` to create an `AdHoc` structure from a function or closure.
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
prints a message indicating that the application is about to launch. The
second named "Put Rewriter", a request fairing, rewrites the method of all
requests to be `PUT`.
ad-hoc fairings. The first, a liftoff fairing named "Liftoff Printer", simply
prints a message indicating that the application has launched. The second named
"Put Rewriter", a request fairing, rewrites the method of all requests to be
`PUT`.
```rust
use rocket::fairing::AdHoc;
use rocket::http::Method;
rocket::ignite()
.attach(AdHoc::on_launch("Launch Printer", |_| {
println!("Rocket is about to launch! Exciting! Here we go...");
}))
.attach(AdHoc::on_liftoff("Liftoff Printer", |_| Box::pin(async move {
println!("...annnddd we have liftoff!");
})))
.attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move {
req.set_method(Method::Put);
})));