Introduce shutdown fairings.

Also adds 'Client::terminate()' to run graceful shutdown in testing.

Resolves #1707.
This commit is contained in:
Sergio Benitez 2022-05-07 06:11:04 -05:00
parent 1575f47753
commit 7908dc43ca
12 changed files with 353 additions and 30 deletions

View File

@ -66,6 +66,9 @@ enum AdHocKind {
/// sent to a client.
Response(Box<dyn for<'r, 'b> Fn(&'r Request<'_>, &'b mut Response<'r>)
-> BoxFuture<'b, ()> + Send + Sync + 'static>),
/// An ad-hoc **shutdown** fairing. Called on shutdown.
Shutdown(Once<dyn for<'a> FnOnce(&'a Rocket<Orbit>) -> BoxFuture<'a, ()> + Send + 'static>),
}
impl AdHoc {
@ -181,6 +184,27 @@ impl AdHoc {
AdHoc { name, kind: AdHocKind::Response(Box::new(f)) }
}
/// Constructs an `AdHoc` shutdown fairing named `name`. The function `f`
/// will be called by Rocket when [shutdown is triggered].
///
/// [shutdown is triggered]: crate::config::Shutdown#triggers
///
/// # Example
///
/// ```rust
/// use rocket::fairing::AdHoc;
///
/// // A fairing that prints a message just before launching.
/// let fairing = AdHoc::on_shutdown("Bye!", |_| Box::pin(async move {
/// println!("Rocket is on its way back!");
/// }));
/// ```
pub fn on_shutdown<F: Send + Sync + 'static>(name: &'static str, f: F) -> AdHoc
where F: for<'a> FnOnce(&'a Rocket<Orbit>) -> BoxFuture<'a, ()>
{
AdHoc { name, kind: AdHocKind::Shutdown(Once::new(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.
@ -229,6 +253,7 @@ impl Fairing for AdHoc {
AdHocKind::Liftoff(_) => Kind::Liftoff,
AdHocKind::Request(_) => Kind::Request,
AdHocKind::Response(_) => Kind::Response,
AdHocKind::Shutdown(_) => Kind::Shutdown,
};
Info { name: self.name, kind }
@ -258,4 +283,10 @@ impl Fairing for AdHoc {
f(req, res).await
}
}
async fn on_shutdown(&self, rocket: &Rocket<Orbit>) {
if let AdHocKind::Shutdown(ref f) = self.kind {
(f.take())(rocket).await
}
}
}

View File

@ -19,6 +19,7 @@ pub struct Fairings {
liftoff: Vec<usize>,
request: Vec<usize>,
response: Vec<usize>,
shutdown: Vec<usize>,
}
macro_rules! iter {
@ -46,6 +47,7 @@ impl Fairings {
.chain(self.liftoff.iter())
.chain(self.request.iter())
.chain(self.response.iter())
.chain(self.shutdown.iter())
}
pub fn add(&mut self, fairing: Box<dyn Fairing>) {
@ -90,6 +92,7 @@ impl Fairings {
remove(i, &mut self.liftoff);
remove(i, &mut self.request);
remove(i, &mut self.response);
remove(i, &mut self.shutdown);
}
}
@ -99,6 +102,7 @@ impl Fairings {
if this_info.kind.is(Kind::Liftoff) { self.liftoff.push(index); }
if this_info.kind.is(Kind::Request) { self.request.push(index); }
if this_info.kind.is(Kind::Response) { self.response.push(index); }
if this_info.kind.is(Kind::Shutdown) { self.shutdown.push(index); }
}
pub fn append(&mut self, others: &mut Fairings) {
@ -153,6 +157,12 @@ impl Fairings {
}
}
#[inline(always)]
pub async fn handle_shutdown(&self, rocket: &Rocket<Orbit>) {
let shutdown_futures = iter!(self.shutdown).map(|f| f.on_shutdown(rocket));
futures::future::join_all(shutdown_futures).await;
}
pub fn audit(&self) -> Result<(), &[Info]> {
match self.failures.is_empty() {
true => Ok(()),
@ -184,6 +194,7 @@ impl std::fmt::Debug for Fairings {
.field("liftoff", &debug_info(iter!(self.liftoff)))
.field("request", &debug_info(iter!(self.request)))
.field("response", &debug_info(iter!(self.response)))
.field("shutdown", &debug_info(iter!(self.shutdown)))
.finish()
}
}

View File

@ -18,7 +18,7 @@ use std::ops::BitOr;
/// # let _unused_info =
/// Info {
/// name: "Example Fairing",
/// kind: Kind::Ignite | Kind::Liftoff | Kind::Request | Kind::Response
/// kind: Kind::Ignite | Kind::Liftoff | Kind::Request | Kind::Response | Kind::Shutdown
/// }
/// # ;
/// ```
@ -40,6 +40,7 @@ pub struct Info {
/// * Liftoff
/// * Request
/// * Response
/// * Shutdown
///
/// Two `Kind` structures can be `or`d together to represent a combination. For
/// instance, to represent a fairing that is both an ignite and request fairing,
@ -66,9 +67,12 @@ impl Kind {
/// `Kind` flag representing a request for a 'response' callback.
pub const Response: Kind = Kind(1 << 3);
/// `Kind` flag representing a request for a 'shutdown' callback.
pub const Shutdown: Kind = Kind(1 << 4);
/// `Kind` flag representing a
/// [singleton](crate::fairing::Fairing#singletons) fairing.
pub const Singleton: Kind = Kind(1 << 4);
pub const Singleton: Kind = Kind(1 << 5);
/// 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`.
@ -141,6 +145,7 @@ impl std::fmt::Display for Kind {
write("liftoff", Kind::Liftoff)?;
write("request", Kind::Request)?;
write("response", Kind::Response)?;
write("shutdown", Kind::Shutdown)?;
write("singleton", Kind::Singleton)
}
}

View File

@ -101,14 +101,15 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
///
/// ## Fairing Callbacks
///
/// 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 identified in the fairing's [`Kind`].
/// There are five kinds of fairing callbacks: launch, liftoff, request,
/// response, and shutdown. 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 identified in the
/// fairing's [`Kind`].
///
/// The four callback kinds are as follows:
/// The callback kinds are as follows:
///
/// * **Ignite (`on_ignite`)**
/// * **<a name="ignite">Ignite</a> (`on_ignite`)**
///
/// An ignite callback, represented by the [`Fairing::on_ignite()`] method,
/// is called just prior to liftoff, during ignition. The state of the
@ -126,15 +127,20 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
/// ignite fairing returns `Err`, launch will be aborted. All ignite
/// fairings are executed even if one or more signal a failure.
///
/// * **Liftoff (`on_liftoff`)**
/// * **<a name="liftoff">Liftoff</a> (`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.
/// instance that has launched and even schedule a shutdown using
/// [`Shutdown::notify()`](crate::Shutdown::notify()) via
/// [`Rocket::shutdown()`].
///
/// * **Request (`on_request`)**
/// Liftoff fairings are run concurrently; resolution of all fairings is
/// awaited before resuming request serving.
///
/// * **<a name="request">Request</a> (`on_request`)**
///
/// A request callback, represented by the [`Fairing::on_request()`] method,
/// is called just after a request is received, immediately after
@ -147,7 +153,7 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
/// via response callbacks. Any modifications to a request are persisted and
/// can potentially alter how a request is routed.
///
/// * **Response (`on_response`)**
/// * **<a name="response">Response</a> (`on_response`)**
///
/// A response callback, represented by the [`Fairing::on_response()`]
/// method, is called when a response is ready to be sent to the client. At
@ -162,6 +168,33 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
/// request. Additionally, Rocket will automatically strip the body for
/// `HEAD` requests _after_ response fairings have run.
///
/// * **<a name="shutdown">Shutdown</a> (`on_shutdown`)**
///
/// A shutdown callback, represented by the [`Fairing::on_shutdown()`]
/// method, is called when [shutdown is triggered]. At this point, graceful
/// shutdown has commenced but not completed; no new requests are accepted
/// but the application may still be actively serving existing requests.
///
/// Rocket guarantees, however, that all requests are completed or aborted
/// once [grace and mercy periods] have expired. This implies that a
/// shutdown fairing that (asynchronously) sleeps for `grace + mercy + ε`
/// seconds before executing any logic will execute said logic after all
/// requests have been processed or aborted. Note that such fairings may
/// wish to operate using the `Ok` return value of [`Rocket::launch()`]
/// instead.
///
/// All registered shutdown fairings are run concurrently; resolution of all
/// fairings is awaited before resuming shutdown. Shutdown fairings do not
/// affect grace and mercy periods. In other words, any time consumed by
/// shutdown fairings is not added to grace and mercy periods.
///
/// ***Note: Shutdown fairings are only run during testing if the `Client`
/// is terminated using [`Client::terminate()`].***
///
/// [shutdown is triggered]: crate::config::Shutdown#triggers
/// [grace and mercy periods]: crate::config::Shutdown#summary
/// [`Client::terminate()`]: crate::local::blocking::Client::terminate()
///
/// # Singletons
///
/// In general, any number of instances of a given fairing type can be attached
@ -246,6 +279,11 @@ pub type Result<T = Rocket<Build>, E = Rocket<Build>> = std::result::Result<T, E
/// /* ... */
/// # unimplemented!()
/// }
///
/// async fn on_shutdown(&self, rocket: &Rocket<Orbit>) {
/// /* ... */
/// # unimplemented!()
/// }
/// }
/// ```
///
@ -426,6 +464,8 @@ pub trait Fairing: Send + Sync + Any + 'static {
/// The ignite callback. Returns `Ok` if ignition should proceed and `Err`
/// if ignition and launch should be aborted.
///
/// See [Fairing Callbacks](#ignite) for complete semantics.
///
/// This method is called during ignition and if `Kind::Ignite` 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
@ -438,6 +478,8 @@ pub trait Fairing: Send + Sync + Any + 'static {
/// The liftoff callback.
///
/// See [Fairing Callbacks](#liftoff) for complete semantics.
///
/// 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.
@ -449,6 +491,8 @@ pub trait Fairing: Send + Sync + Any + 'static {
/// The request callback.
///
/// See [Fairing Callbacks](#request) for complete semantics.
///
/// This method is called when a new request is received if `Kind::Request`
/// is in the `kind` field of the `Info` structure for this fairing. The
/// `&mut Request` parameter is the incoming request, and the `&Data`
@ -461,6 +505,8 @@ pub trait Fairing: Send + Sync + Any + 'static {
/// The response callback.
///
/// See [Fairing Callbacks](#response) for complete semantics.
///
/// This method is called when a response is ready to be issued to a client
/// if `Kind::Response` is in the `kind` field of the `Info` structure for
/// this fairing. The `&Request` parameter is the request that was routed,
@ -470,6 +516,21 @@ pub trait Fairing: Send + Sync + Any + 'static {
///
/// The default implementation of this method does nothing.
async fn on_response<'r>(&self, _req: &'r Request<'_>, _res: &mut Response<'r>) {}
/// The shutdown callback.
///
/// See [Fairing Callbacks](#shutdown) for complete semantics.
///
/// This method is called when [shutdown is triggered] if `Kind::Shutdown`
/// is in the `kind` field of the `Info` structure for this fairing. The
/// `Rocket` parameter corresponds to the running application.
///
/// [shutdown is triggered]: crate::config::Shutdown#triggers
///
/// ## Default Implementation
///
/// The default implementation of this method does nothing.
async fn on_shutdown(&self, _rocket: &Rocket<Orbit>) { }
}
#[crate::async_trait]
@ -498,4 +559,9 @@ impl<T: Fairing> Fairing for std::sync::Arc<T> {
async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
(self as &T).on_response(req, res).await
}
#[inline]
async fn on_shutdown(&self, rocket: &Rocket<Orbit>) {
(self as &T).on_shutdown(rocket).await
}
}

View File

@ -451,7 +451,8 @@ impl<'v> TempFile<'v> {
.unwrap_or(Limits::FILE);
let temp_dir = req.rocket().config().temp_dir.relative();
let file = task::spawn_blocking(move || NamedTempFile::new_in(temp_dir)).await;
let file = task::spawn_blocking(move || NamedTempFile::new_in(temp_dir));
let file = file.await;
let file = file.map_err(|_| io::Error::new(io::ErrorKind::Other, "spawn_block panic"))??;
let (file, temp_path) = file.into_parts();

View File

@ -2,7 +2,7 @@ use std::fmt;
use parking_lot::RwLock;
use crate::{Rocket, Phase, Orbit, Error};
use crate::{Rocket, Phase, Orbit, Ignite, Error};
use crate::local::asynchronous::{LocalRequest, LocalResponse};
use crate::http::{Method, uri::Origin, private::cookie};
@ -63,6 +63,7 @@ impl Client {
}
// WARNING: This is unstable! Do not use this method outside of Rocket!
// This is used by the `Client` doctests.
#[doc(hidden)]
pub fn _test<T, F>(f: F) -> T
where F: FnOnce(&Self, LocalRequest<'_>, LocalResponse<'_>) -> T + Send
@ -101,6 +102,13 @@ impl Client {
LocalRequest::new(self, method, uri)
}
pub(crate) async fn _terminate(self) -> Rocket<Ignite> {
let rocket = self.rocket;
rocket.shutdown().notify();
rocket.fairings.handle_shutdown(&rocket).await;
rocket.into_ignite()
}
// Generates the public API methods, which call the private methods above.
pub_client_impl!("use rocket::local::asynchronous::Client;" @async await);
}

View File

@ -1,7 +1,7 @@
use std::fmt;
use std::cell::RefCell;
use crate::{Rocket, Phase, Orbit, Error};
use crate::{Rocket, Phase, Orbit, Ignite, Error};
use crate::local::{asynchronous, blocking::{LocalRequest, LocalResponse}};
use crate::http::{Method, uri::Origin};
@ -78,6 +78,15 @@ impl Client {
self.inner()._with_raw_cookies(f)
}
pub(crate) fn _terminate(mut self) -> Rocket<Ignite> {
let runtime = tokio::runtime::Builder::new_current_thread().build().unwrap();
let runtime = self.runtime.replace(runtime);
let inner = self.inner.take().expect("invariant broken: self.inner is Some");
let rocket = runtime.block_on(inner._terminate());
runtime.shutdown_timeout(std::time::Duration::from_secs(1));
rocket
}
#[inline(always)]
fn _req<'c, 'u: 'c, U>(&'c self, method: Method, uri: U) -> LocalRequest<'c>
where U: TryInto<Origin<'u>> + fmt::Display
@ -97,10 +106,11 @@ impl std::fmt::Debug for Client {
impl Drop for Client {
fn drop(&mut self) {
let client = self.inner.take();
if let Some(client) = self.inner.take() {
self.block_on(async { drop(client) });
}
}
}
#[cfg(doctest)]
mod doctest {

View File

@ -95,6 +95,29 @@ macro_rules! pub_client_impl {
Self::_new(rocket, false) $(.$suffix)?
}
/// Terminates `Client` by initiating a graceful shutdown via
/// [`Shutdown::notify()`] and running shutdown fairings.
///
/// This method _must_ be called on a `Client` if graceful shutdown is
/// required for testing as `Drop` _does not_ signal `Shutdown` nor run
/// shutdown fairings. Returns the instance of `Rocket` being managed by
/// this client after all shutdown fairings run to completion.
///
/// [`Shutdown::notify()`]: crate::Shutdown::notify()
///
/// ```rust,no_run
#[doc = $import]
///
/// # fn f(client: Client) {
/// let client: Client = client;
/// let rocket = client.terminate();
/// # }
/// ```
#[inline(always)]
pub $($prefix)? fn terminate(self) -> Rocket<Ignite> {
Self::_terminate(self) $(.$suffix)?
}
#[doc(hidden)]
pub $($prefix)? fn debug_with(routes: Vec<crate::Route>) -> Result<Self, Error> {
let rocket = crate::custom(crate::Config::debug_default());

View File

@ -664,7 +664,7 @@ impl Rocket<Ignite> {
}
impl Rocket<Orbit> {
fn into_ignite(self) -> Rocket<Ignite> {
pub(crate) fn into_ignite(self) -> Rocket<Ignite> {
Rocket(Igniting {
router: self.0.router,
fairings: self.0.fairings,

View File

@ -502,10 +502,15 @@ impl Rocket<Orbit> {
biased;
_ = shutdown => {
// Run shutdown fairings. We compute `sleep()` for grace periods
// beforehand to ensure we don't add shutdown fairing completion
// time, which is arbitrary, to these periods.
info!("Shutdown requested. Waiting for pending I/O...");
let grace_timer = sleep(Duration::from_secs(grace));
let mercy_timer = sleep(Duration::from_secs(grace + mercy));
let shutdown_timer = sleep(Duration::from_secs(grace + mercy + 1));
rocket.fairings.handle_shutdown(&*rocket).await;
tokio::pin!(grace_timer, mercy_timer, shutdown_timer);
tokio::select! {
biased;

View File

@ -0,0 +1,151 @@
#[macro_use] extern crate rocket;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use rocket::fairing::AdHoc;
// Want to test:
//
// * stalled connection + sleep in shutdown -> conn closed
// * stalled shutdown fairing stalls shutdown but not > grace + mercy
// - sleep < grace + mercy
// - sleep > grace + mercy
#[derive(Default)]
struct Flags {
liftoff: AtomicBool,
shutdown: AtomicUsize
}
#[test]
fn shutdown_fairing_runs() {
use rocket::local::blocking::Client;
let rocket = rocket::build()
.manage(Flags::default())
.attach(AdHoc::on_liftoff("Liftoff Flag", |rocket| Box::pin(async move {
let flags = rocket.state::<Flags>().unwrap();
flags.liftoff.store(true, Ordering::SeqCst);
})))
.attach(AdHoc::on_shutdown("Shutdown Flag", |rocket| Box::pin(async move {
let flags = rocket.state::<Flags>().unwrap();
flags.shutdown.fetch_add(1, Ordering::SeqCst);
})));
let client = Client::debug(rocket).unwrap();
let flags = client.rocket().state::<Flags>().unwrap();
assert!(flags.liftoff.load(Ordering::SeqCst));
assert_eq!(0, flags.shutdown.load(Ordering::SeqCst));
let rocket = client.terminate();
let flags = rocket.state::<Flags>().unwrap();
assert_eq!(1, flags.shutdown.load(Ordering::SeqCst));
}
#[async_test]
async fn async_shutdown_fairing_runs() {
use rocket::local::asynchronous::Client;
let rocket = rocket::build()
.manage(Flags::default())
.attach(AdHoc::on_liftoff("Liftoff Flag", |rocket| Box::pin(async move {
let flags = rocket.state::<Flags>().unwrap();
flags.liftoff.store(true, Ordering::SeqCst);
})))
.attach(AdHoc::on_shutdown("Shutdown Flag", |rocket| Box::pin(async move {
let flags = rocket.state::<Flags>().unwrap();
flags.shutdown.fetch_add(1, Ordering::SeqCst);
})));
let client = Client::debug(rocket).await.unwrap();
let flags = client.rocket().state::<Flags>().unwrap();
assert!(flags.liftoff.load(Ordering::SeqCst));
assert_eq!(0, flags.shutdown.load(Ordering::SeqCst));
let rocket = client.terminate().await;
let flags = rocket.state::<Flags>().unwrap();
assert_eq!(1, flags.shutdown.load(Ordering::SeqCst));
}
#[async_test]
async fn multiple_shutdown_fairing_runs() {
use rocket::local::asynchronous::Client;
let rocket = rocket::build()
.manage(Flags::default())
.attach(AdHoc::on_shutdown("Shutdown Flag 1", |rocket| Box::pin(async move {
let flags = rocket.state::<Flags>().unwrap();
flags.shutdown.fetch_add(1, Ordering::SeqCst);
})))
.attach(AdHoc::on_shutdown("Shutdown Flag 2", |rocket| Box::pin(async move {
let flags = rocket.state::<Flags>().unwrap();
flags.shutdown.fetch_add(1, Ordering::SeqCst);
})));
let client = Client::debug(rocket).await.unwrap();
let flags = client.rocket().state::<Flags>().unwrap();
assert_eq!(0, flags.shutdown.load(Ordering::SeqCst));
let rocket = client.terminate().await;
let flags = rocket.state::<Flags>().unwrap();
assert_eq!(2, flags.shutdown.load(Ordering::SeqCst));
}
#[async_test]
async fn async_slow_shutdown_doesnt_elongate_grace() {
use rocket::local::asynchronous::Client;
let mut config = rocket::Config::debug_default();
config.shutdown.grace = 1;
config.shutdown.mercy = 1;
let rocket = rocket::build()
.manage(Flags::default())
.configure(config)
.attach(AdHoc::on_shutdown("Slow Shutdown", |rocket| Box::pin(async move {
tokio::time::sleep(std::time::Duration::from_secs(4)).await;
let flags = rocket.state::<Flags>().unwrap();
flags.shutdown.fetch_add(1, Ordering::SeqCst);
})));
let client = Client::debug(rocket).await.unwrap();
let flags = client.rocket().state::<Flags>().unwrap();
assert_eq!(0, flags.shutdown.load(Ordering::SeqCst));
let start = std::time::Instant::now();
let rocket = client.terminate().await;
let elapsed = start.elapsed();
let flags = rocket.state::<Flags>().unwrap();
assert!(elapsed > std::time::Duration::from_secs(2));
assert!(elapsed < std::time::Duration::from_secs(5));
assert_eq!(1, flags.shutdown.load(Ordering::SeqCst));
}
#[test]
fn background_tasks_dont_prevent_terminate() {
use rocket::local::blocking::Client;
#[get("/")]
fn index() {
tokio::task::spawn(async {
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
});
tokio::task::spawn_blocking(|| {
std::thread::sleep(std::time::Duration::from_secs(10));
});
}
let mut config = rocket::Config::debug_default();
config.shutdown.grace = 1;
config.shutdown.mercy = 1;
let rocket = rocket::build().configure(config).mount("/", routes![index]);
let client = Client::debug(rocket).unwrap();
let response = client.get("/").dispatch();
assert!(response.status().class().is_success());
drop(response);
let _ = client.terminate();
}

View File

@ -72,8 +72,8 @@ order in which fairings are attached may be significant.
### Callbacks
There are four events for which Rocket issues fairing callbacks. Each of these
events is described below:
There are five events for which Rocket issues fairing callbacks. Each of these
events is breifly described below and in details in the [`Fairing`] trait docs:
* **Ignite (`on_ignite`)**
@ -106,7 +106,16 @@ events is described below:
example, response fairings can also be used to inject headers into all
outgoing responses.
* **Shutdown (`on_shutdown`)**
A shutdown callback is called when [shutdown is triggered]. At this point,
graceful shutdown has commenced but not completed; no new requests are
accepted but the application may still be actively serving existing
requests. All registered shutdown fairings are run concurrently; resolution
of all fairings is awaited before resuming shutdown.
[ignition]: @api/rocket/struct.Rocket.html#method.ignite
[shutdown is triggered]: @api/rocket/config/struct.Shutdown.html#triggers
## Implementing
@ -115,8 +124,8 @@ Recall that a fairing is any type that implements the [`Fairing`] trait. A
[`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_ignite`],
[`on_liftoff`], [`on_request`], and [`on_response`]. Each callback has a default
implementation that does absolutely nothing.
[`on_liftoff`], [`on_request`], [`on_response`], and [`on_shutdown`]. 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
@ -124,6 +133,7 @@ implementation that does absolutely nothing.
[`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
[`on_shutdown`]: @api/rocket/fairing/trait.Fairing.html#method.on_shutdown
### Requirements
@ -204,17 +214,16 @@ documentation](@api/rocket/fairing/trait.Fairing.html#example).
## Ad-Hoc Fairings
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
For simpler cases, 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_ignite`, `on_liftoff`, `on_request`, or `on_response` constructors on
`AdHoc` to create an `AdHoc` structure from a function or closure.
`on_ignite`, `on_liftoff`, `on_request`, `on_response`, or `on_shutdown`
constructors on `AdHoc` to create a fairing from a function or closure.
As an example, the code below creates a `Rocket` instance with two attached
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`.
ad-hoc fairings. The first, a liftoff fairing named "Liftoff Printer", 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;
@ -226,6 +235,9 @@ rocket::build()
})))
.attach(AdHoc::on_request("Put Rewriter", |req, _| Box::pin(async move {
req.set_method(Method::Put);
})))
.attach(AdHoc::on_shutdown("Shutdown Printer", |_| Box::pin(async move {
println!("...shutdown has commenced!");
})));
```