Fairings, v3.

Modifying the `Rocket` structure just before launch doesn't make sense for
several reasons: 1) those affects can't influence the launch, and 2) they won't
be observed in tests. Thus, an `Attach` fairing kind was added that ameliorates
these issues.
This commit is contained in:
Sergio Benitez 2017-05-17 01:39:36 -07:00
parent 9c9740f966
commit 28a1ef0916
9 changed files with 249 additions and 92 deletions

View File

@ -0,0 +1,2 @@
[global]
token = 123

View File

@ -6,10 +6,12 @@ extern crate rocket;
use std::io::Cursor;
use std::sync::atomic::{AtomicUsize, Ordering};
use rocket::{Request, Data, Response};
use rocket::{Request, State, Data, Response};
use rocket::fairing::{AdHoc, Fairing, Info, Kind};
use rocket::http::{Method, ContentType, Status};
struct Token(i64);
#[cfg(test)] mod tests;
#[derive(Default)]
@ -56,13 +58,22 @@ fn hello() -> &'static str {
"Hello, world!"
}
#[get("/token")]
fn token(token: State<Token>) -> String {
format!("{}", token.0)
}
fn rocket() -> rocket::Rocket {
rocket::ignite()
.mount("/", routes![hello])
.mount("/", routes![hello, token])
.attach(Counter::default())
.attach(AdHoc::on_attach(|rocket| {
println!("Adding token managed state...");
let token_val = rocket.config().get_int("token").unwrap_or(-1);
Ok(rocket.manage(Token(token_val)))
}))
.attach(AdHoc::on_launch(|rocket| {
println!("Rocket is about to launch! Exciting! Here we go...");
Ok(rocket)
println!("Rocket is about to launch!");
}))
.attach(AdHoc::on_request(|req, _| {
println!(" => Incoming request: {}", req);

View File

@ -9,6 +9,7 @@ fn rewrite_get_put() {
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Hello, fairings!".into()));
}
#[test]
fn counts() {
let rocket = rocket();
@ -33,3 +34,13 @@ fn counts() {
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Get: 4\nPost: 1".into()));
}
#[test]
fn token() {
let rocket = rocket();
// Ensure the token is '123', which is what we have in `Rocket.toml`.
let mut req = MockRequest::new(Get, "/token");
let mut res = req.dispatch_with(&rocket);
assert_eq!(res.body_string(), Some("123".into()));
}

View File

@ -8,7 +8,7 @@ use fairing::{Fairing, Kind, Info};
///
/// # Usage
///
/// Use the [`on_launch`](#method.on_launch),
/// 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.
@ -25,28 +25,47 @@ use fairing::{Fairing, Kind, Info};
/// use rocket::http::Method;
///
/// rocket::ignite()
/// .attach(AdHoc::on_launch(|rocket| {
/// .attach(AdHoc::on_launch(|_| {
/// println!("Rocket is about to launch! Exciting! Here we go...");
/// Ok(rocket)
/// }))
/// .attach(AdHoc::on_request(|req, _| {
/// req.set_method(Method::Put);
/// }));
/// ```
pub enum AdHoc {
/// An ad-hoc **attach** fairing. Called when the fairing is attached.
#[doc(hidden)]
Attach(Box<Fn(Rocket) -> Result<Rocket, Rocket> + Send + Sync + 'static>),
/// An ad-hoc **launch** fairing. Called just before Rocket launches.
#[doc(hidden)]
Launch(Box<Fn(Rocket) -> Result<Rocket, Rocket> + Send + Sync + 'static>),
Launch(Box<Fn(&Rocket) + Send + Sync + 'static>),
/// An ad-hoc **request** fairing. Called when a request is received.
#[doc(hidden)]
Request(Box<Fn(&mut Request, &Data) + Send + Sync + 'static>),
/// An ad-hoc **response** fairing. Called when a response is ready to be
/// sent to a client.
#[doc(hidden)]
Response(Box<Fn(&Request, &mut Response) + Send + Sync + 'static>)
Response(Box<Fn(&Request, &mut Response) + Send + Sync + 'static>),
}
impl AdHoc {
/// Constructs an `AdHoc` attach fairing. The function `f` will be called by
/// Rocket when this fairing is attached.
///
/// # Example
///
/// ```rust
/// use rocket::fairing::AdHoc;
///
/// // The no-op attach fairing.
/// let fairing = AdHoc::on_attach(|rocket| Ok(rocket));
/// ```
pub fn on_attach<F>(f: F) -> AdHoc
where F: Fn(Rocket) -> Result<Rocket, Rocket> + Send + Sync + 'static
{
AdHoc::Attach(Box::new(f))
}
/// Constructs an `AdHoc` launch fairing. The function `f` will be called by
/// Rocket just prior to launching.
///
@ -55,11 +74,13 @@ impl AdHoc {
/// ```rust
/// use rocket::fairing::AdHoc;
///
/// // The no-op launch fairing.
/// let fairing = AdHoc::on_launch(|rocket| Ok(rocket));
/// // A fairing that prints a message just before launching.
/// let fairing = AdHoc::on_launch(|rocket| {
/// println!("Launching in T-3..2..1..");
/// });
/// ```
pub fn on_launch<F>(f: F) -> AdHoc
where F: Fn(Rocket) -> Result<Rocket, Rocket> + Send + Sync + 'static
where F: Fn(&Rocket) + Send + Sync + 'static
{
AdHoc::Launch(Box::new(f))
}
@ -109,16 +130,43 @@ impl Fairing for AdHoc {
fn info(&self) -> Info {
use self::AdHoc::*;
match *self {
Launch(_) => Info { name: "AdHoc::Launch", kind: Kind::Launch },
Request(_) => Info { name: "AdHoc::Request", kind: Kind::Request },
Response(_) => Info { name: "AdHoc::Response", kind: Kind::Response }
Attach(_) => {
Info {
name: "AdHoc::Attach",
kind: Kind::Attach,
}
}
Launch(_) => {
Info {
name: "AdHoc::Launch",
kind: Kind::Launch,
}
}
Request(_) => {
Info {
name: "AdHoc::Request",
kind: Kind::Request,
}
}
Response(_) => {
Info {
name: "AdHoc::Response",
kind: Kind::Response,
}
}
}
}
fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
match *self {
AdHoc::Launch(ref launch_fn) => launch_fn(rocket),
_ => Ok(rocket)
AdHoc::Attach(ref callback) => callback(rocket),
_ => Ok(rocket),
}
}
fn on_launch(&self, rocket: &Rocket) {
if let AdHoc::Launch(ref callback) = *self {
callback(rocket)
}
}

View File

@ -4,6 +4,7 @@ use fairing::{Fairing, Kind};
#[derive(Default)]
pub struct Fairings {
all_fairings: Vec<Box<Fairing>>,
attach_failure: bool,
launch: Vec<&'static Fairing>,
request: Vec<&'static Fairing>,
response: Vec<&'static Fairing>,
@ -15,11 +16,16 @@ impl Fairings {
Fairings::default()
}
#[inline]
pub fn attach(&mut self, fairing: Box<Fairing>) {
pub fn attach(&mut self, fairing: Box<Fairing>, mut rocket: Rocket) -> Rocket {
// Get the kind information.
let kind = fairing.info().kind;
// Run the `on_attach` callback if this is an 'attach' fairing.
if kind.is(Kind::Attach) {
rocket = fairing.on_attach(rocket)
.unwrap_or_else(|r| { self.attach_failure = true; r })
}
// The `Fairings` structure separates `all_fairings` into kind groups so
// we don't have to search through all fairings and do a comparison at
// runtime. We need references since a single structure can be multiple
@ -36,22 +42,25 @@ impl Fairings {
// deallocating `Box<Fairing>` structures. As such, the references will
// always be valid. Note: `ptr` doesn't point into the `Vec`, so
// reallocations there are irrelvant. Instead, it points into the heap.
let ptr: &'static Fairing = unsafe { ::std::mem::transmute(&*fairing) };
//
// Also, we don't save attach fairings since we don't need them anymore.
if !kind.is_exactly(Kind::Attach) {
let ptr: &'static Fairing = unsafe { ::std::mem::transmute(&*fairing) };
self.all_fairings.push(fairing);
if kind.is(Kind::Launch) { self.launch.push(ptr); }
if kind.is(Kind::Request) { self.request.push(ptr); }
if kind.is(Kind::Response) { self.response.push(ptr); }
self.all_fairings.push(fairing);
if kind.is(Kind::Launch) { self.launch.push(ptr); }
if kind.is(Kind::Request) { self.request.push(ptr); }
if kind.is(Kind::Response) { self.response.push(ptr); }
}
rocket
}
#[inline(always)]
pub fn handle_launch(&mut self, mut rocket: Rocket) -> Option<Rocket> {
let mut success = Some(());
for f in &self.launch {
rocket = f.on_launch(rocket).unwrap_or_else(|r| { success = None; r });
pub fn handle_launch(&self, rocket: &Rocket) {
for fairing in &self.launch {
fairing.on_launch(rocket);
}
success.map(|_| rocket)
}
#[inline(always)]
@ -68,6 +77,10 @@ impl Fairings {
}
}
pub fn had_failure(&self) -> bool {
self.attach_failure
}
pub fn pretty_print_counts(&self) {
use term_painter::ToStyle;
use term_painter::Color::{White, Magenta};
@ -78,8 +91,10 @@ impl Fairings {
fn info_if_nonempty(kind: &str, fairings: &[&Fairing]) {
let names: Vec<&str> = fairings.iter().map(|f| f.info().name).collect();
info_!("{} {}: {}", White.paint(fairings.len()), kind,
White.paint(names.join(", ")));
info_!("{} {}: {}",
White.paint(fairings.len()),
kind,
White.paint(names.join(", ")));
}
info_if_nonempty("launch", &self.launch);

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 three callbacks:
/// all four callbacks:
///
/// ```
/// use rocket::fairing::{Info, Kind};
@ -18,7 +18,7 @@ use std::ops::BitOr;
/// # let _unused_info =
/// Info {
/// name: "Example Fairing",
/// kind: Kind::Launch | Kind::Request | Kind::Response
/// kind: Kind::Attach | Kind::Launch | Kind::Request | Kind::Response
/// }
/// # ;
/// ```
@ -35,6 +35,7 @@ pub struct Info {
/// A fairing can request any combination of any of the following kinds of
/// callbacks:
///
/// * Attach
/// * Launch
/// * Request
/// * Response
@ -42,18 +43,20 @@ pub struct Info {
/// 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 all three kinds, use `Kind::Launch | Kind::Request | Kind::Response`.
/// is only an attach fairing, use `Kind::Attach`.
#[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(0b001);
pub const Launch: Kind = Kind(0b0010);
/// `Kind` flag representing a request for a 'request' callback.
pub const Request: Kind = Kind(0b010);
pub const Request: Kind = Kind(0b0100);
/// `Kind` flag representing a request for a 'response' callback.
pub const Response: Kind = Kind(0b100);
pub const Response: Kind = Kind(0b1000);
/// 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`.
@ -77,6 +80,26 @@ impl Kind {
pub fn is(self, other: Kind) -> bool {
(other.0 & self.0) == other.0
}
/// Returns `true` if `self` is exactly `other`.
///
/// # Example
///
/// ```rust
/// use rocket::fairing::Kind;
///
/// let launch_and_req = Kind::Launch | Kind::Request;
/// assert!(launch_and_req.is_exactly(Kind::Launch | Kind::Request));
///
/// assert!(!launch_and_req.is_exactly(Kind::Launch));
/// assert!(!launch_and_req.is_exactly(Kind::Request));
/// assert!(!launch_and_req.is_exactly(Kind::Response));
/// assert!(!launch_and_req.is_exactly(Kind::Launch | Kind::Response));
/// ```
#[inline]
pub fn is_exactly(self, other: Kind) -> bool {
self.0 == other.0
}
}
impl BitOr for Kind {

View File

@ -1,4 +1,4 @@
//! Fairings: structured interposition at launch, request, and response time.
//! Fairings: callbacks at attach, launch, 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
@ -26,7 +26,7 @@
//! ```
//!
//! Once a fairing is attached, Rocket will execute it at the appropiate time,
//! which varies depending on the fairing type. See the
//! which varies depending on the fairing implementation. See the
//! [`Fairing`](/rocket/fairing/trait.Fairing.html) trait documentation for more
//! information on the dispatching of fairing methods.
//!
@ -37,7 +37,8 @@
//! fairing callbacks may not be commutative, it is important to communicate to
//! the user every consequence of a fairing. Furthermore, a `Fairing` should
//! take care to act locally so that the actions of other `Fairings` are not
//! jeopardized.
//! jeopardized. For instance, unless it is made abundantly clear, a fairing
//! should not rewrite every request.
use {Rocket, Request, Response, Data};
mod fairings;
@ -90,12 +91,29 @@ pub use self::info_kind::{Info, Kind};
///
/// ## Fairing Callbacks
///
/// There are three kinds of fairing callbacks: launch, request, and response.
/// As mentioned above, 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.
/// There are four kinds of fairing callbacks: attach, launch, request, and
/// response. As mentioned above, 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 three callback kinds are as follows:
/// The four callback kinds are as follows:
///
/// * **Attach (`on_attach`)**
///
/// An attach callback, represented by the
/// [`on_attach`](/rocket/fairing/trait.Fairing.html#method.on_attach)
/// method, is called when a fairing is first attached via the
/// [`attach`](/rocket/struct.Rocket.html#method.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 a launch callback returns `Err`,
/// launch will be aborted. All attach callbacks are executed on `launch`,
/// even if one or more signal a failure.
///
/// * **Launch (`on_launch`)**
///
@ -103,10 +121,8 @@ pub use self::info_kind::{Info, Kind};
/// [`on_launch`](/rocket/fairing/trait.Fairing.html#method.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
/// arbitrarily modify the `Rocket` instance being launched. It returns `Ok`
/// if it would like launching to proceed nominally and `Err` otherwise. If
/// a launch callback returns `Err`, launch is aborted.
/// has not yet begun accepting connections. A launch callback can inspect
/// the `Rocket` instance being launched.
///
/// * **Request (`on_request`)**
///
@ -136,15 +152,15 @@ 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_launch`, `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 issued by Rocket.
/// also implement any of the available callbacks: `on_attach`, `on_launch`,
/// `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 issued by Rocket.
///
/// A `Fairing` must be `Send + Sync + 'static`. This means that the fairing
/// must be sendable across thread boundaries (`Send`), thread-safe (`Sync`),
/// and have no non-`'static` reference (`'static`). Note that these bounds _do
/// not_ prohibit a `Fairing` from having state: the state need simply be
/// not_ prohibit a `Fairing` from holding state: the state need simply be
/// thread-safe and statically available or heap allocated.
///
/// # Example
@ -154,7 +170,7 @@ pub use self::info_kind::{Info, Kind};
/// guards](/rocket/request/trait.FromRequest.html) and [managed
/// state](/rocket/request/struct.State.html), 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 does this globally.
/// 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'`
@ -238,23 +254,38 @@ pub trait Fairing: Send + Sync + 'static {
/// fn info(&self) -> Info {
/// Info {
/// name: "My Custom Fairing",
/// kind: Kind::Launch | Kind::Response
/// kind: Kind::Attach | Kind::Launch | Kind::Response
/// }
/// }
/// }
/// ```
fn info(&self) -> Info;
/// The launch callback. Returns `Ok` if launch should proceed and `Err` if
/// The attach callback. Returns `Ok` if launch should proceed and `Err` if
/// launch should be aborted.
///
/// This method is called just prior to launching an application if
/// `Kind::Launch` is in the `kind` field of the `Info` structure for this
/// fairing. The `rocket` parameter is the `Rocket` instance that was built
/// for this application.
/// 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.
///
/// ## Default Implementation
///
/// The default implementation of this method simply returns `Ok(rocket)`.
fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> { Ok(rocket) }
fn on_attach(&self, rocket: Rocket) -> Result<Rocket, Rocket> { Ok(rocket) }
/// The launch 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 curresponds to the application that
/// will be launched.
///
/// ## Default Implementation
///
/// The default implementation of this method does nothing.
#[allow(unused_variables)]
fn on_launch(&self, rocket: &Rocket) {}
/// The request callback.
///
@ -263,9 +294,11 @@ pub trait Fairing: Send + Sync + 'static {
/// `&mut Request` parameter is the incoming request, and the `&Data`
/// parameter is the incoming data in the request.
///
/// ## Default Implementation
///
/// The default implementation of this method does nothing.
#[allow(unused_variables)]
fn on_request(&self, request: &mut Request, data: &Data) { }
fn on_request(&self, request: &mut Request, data: &Data) {}
/// The response callback.
///
@ -274,7 +307,9 @@ pub trait Fairing: Send + Sync + 'static {
/// this fairing. The `&Request` parameter is the request that was routed,
/// and the `&mut Response` parameter is the resulting response.
///
/// ## Default Implementation
///
/// The default implementation of this method does nothing.
#[allow(unused_variables)]
fn on_response(&self, request: &Request, response: &mut Response) { }
fn on_response(&self, request: &Request, response: &mut Response) {}
}

View File

@ -284,7 +284,6 @@ impl Rocket {
for route in matches {
// Retrieve and set the requests parameters.
info_!("Matched: {}", route);
// FIXME: Users should not be able to use this.
request.set_params(route);
// Dispatch the request to the handler.
@ -598,25 +597,37 @@ impl Rocket {
/// use rocket::Rocket;
/// use rocket::fairing::AdHoc;
///
/// fn youll_see(rocket: Rocket) -> Result<Rocket, Rocket> {
/// println!("Rocket is about to launch! You just see...");
/// Ok(rocket)
/// }
///
/// fn main() {
/// # if false { // We don't actually want to launch the server in an example.
/// rocket::ignite()
/// .attach(AdHoc::on_launch(youll_see))
/// .attach(AdHoc::on_launch(|_| {
/// println!("Rocket is about to launch! You just see...");
/// }))
/// .launch();
/// # }
/// }
/// ```
#[inline]
pub fn attach<F: Fairing>(mut self, fairing: F) -> Self {
self.fairings.attach(Box::new(fairing));
// Attach the fairings, which requires us to move `self`.
let mut fairings = mem::replace(&mut self.fairings, Fairings::new());
self = fairings.attach(Box::new(fairing), self);
// Make sure we keep the fairings around!
self.fairings = fairings;
self
}
pub(crate) fn prelaunch_check(&self) -> Option<LaunchError> {
if self.router.has_collisions() {
Some(LaunchError::from(LaunchErrorKind::Collision))
} else if self.fairings.had_failure() {
Some(LaunchError::from(LaunchErrorKind::FailedFairing))
} else {
None
}
}
/// Starts the application server and begins listening for and dispatching
/// requests to mounted routes and catchers. Unless there is an error, this
/// function does not return and blocks until program termination.
@ -637,9 +648,9 @@ impl Rocket {
/// rocket::ignite().launch();
/// # }
/// ```
pub fn launch(mut self) -> LaunchError {
if self.router.has_collisions() {
return LaunchError::from(LaunchErrorKind::Collision);
pub fn launch(self) -> LaunchError {
if let Some(error) = self.prelaunch_check() {
return error;
}
self.fairings.pretty_print_counts();
@ -657,15 +668,8 @@ impl Rocket {
Err(e) => return LaunchError::from(e)
};
// Run all of the launch fairings.
let mut fairings = mem::replace(&mut self.fairings, Fairings::new());
self = match fairings.handle_launch(self) {
Some(rocket) => rocket,
None => return LaunchError::from(LaunchErrorKind::FailedFairing)
};
// Make sure we keep the request/response fairings!
self.fairings = fairings;
// Run the launch fairings.
self.fairings.handle_launch(&self);
launch_info!("🚀 {} {}{}",
White.paint("Rocket has launched from"),

View File

@ -76,7 +76,7 @@
//! ```
use ::{Rocket, Request, Response, Data};
use http::{Method, Header, Cookie};
use http::{Method, Status, Header, Cookie};
use std::net::SocketAddr;
@ -201,11 +201,12 @@ impl<'r> MockRequest<'r> {
/// Dispatch this request using a given instance of Rocket.
///
/// Returns the body of the response if there was a response. The return
/// value is `None` if any of the following occurs:
///
/// 1. The returned body was not valid UTF8.
/// 2. The application failed to respond.
/// It is possible that the supplied `rocket` instance contains malformed
/// input such as colliding or invalid routes or failed fairings. When this
/// is the case, the returned `Response` will contain a status of
/// `InternalServerError`, and the body will contain the error that
/// occurred. In all other cases, the returned `Response` will be that of
/// the application.
///
/// # Examples
///
@ -234,6 +235,13 @@ impl<'r> MockRequest<'r> {
/// # }
/// ```
pub fn dispatch_with<'s>(&'s mut self, rocket: &'r Rocket) -> Response<'s> {
if let Some(error) = rocket.prelaunch_check() {
return Response::build()
.status(Status::InternalServerError)
.sized_body(::std::io::Cursor::new(error.to_string()))
.finalize()
}
let data = ::std::mem::replace(&mut self.data, Data::local(vec![]));
rocket.dispatch(&mut self.request, data)
}