Fairings v2.

This commit is contained in:
Sergio Benitez 2017-05-14 21:46:01 -07:00
parent 9a7484f7a8
commit 9c9740f966
8 changed files with 640 additions and 181 deletions

View File

@ -4,12 +4,53 @@
extern crate rocket; extern crate rocket;
use std::io::Cursor; use std::io::Cursor;
use std::sync::atomic::{AtomicUsize, Ordering};
use rocket::Fairing; use rocket::{Request, Data, Response};
use rocket::http::Method; use rocket::fairing::{AdHoc, Fairing, Info, Kind};
use rocket::http::{Method, ContentType, Status};
#[cfg(test)] mod tests; #[cfg(test)] mod tests;
#[derive(Default)]
struct Counter {
get: AtomicUsize,
post: AtomicUsize,
}
impl Fairing for Counter {
fn info(&self) -> Info {
Info {
name: "GET/POST Counter",
kind: Kind::Request | Kind::Response
}
}
fn on_request(&self, request: &mut Request, _: &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);
}
}
fn on_response(&self, request: &Request, response: &mut Response) {
if response.status() != Status::NotFound {
return
}
if request.method() == Method::Get && request.uri().path() == "/counts" {
let get_count = self.get.load(Ordering::Relaxed);
let post_count = self.post.load(Ordering::Relaxed);
let body = format!("Get: {}\nPost: {}", get_count, post_count);
response.set_status(Status::Ok);
response.set_header(ContentType::Plain);
response.set_sized_body(Cursor::new(body));
}
}
}
#[put("/")] #[put("/")]
fn hello() -> &'static str { fn hello() -> &'static str {
"Hello, world!" "Hello, world!"
@ -18,19 +59,24 @@ fn hello() -> &'static str {
fn rocket() -> rocket::Rocket { fn rocket() -> rocket::Rocket {
rocket::ignite() rocket::ignite()
.mount("/", routes![hello]) .mount("/", routes![hello])
.attach(Fairing::Launch(Box::new(|rocket| { .attach(Counter::default())
.attach(AdHoc::on_launch(|rocket| {
println!("Rocket is about to launch! Exciting! Here we go..."); println!("Rocket is about to launch! Exciting! Here we go...");
Ok(rocket) Ok(rocket)
}))) }))
.attach(Fairing::Request(Box::new(|req, _| { .attach(AdHoc::on_request(|req, _| {
println!(" => Incoming request: {}", req); println!(" => Incoming request: {}", req);
println!(" => Changing method to `PUT`."); if req.uri().path() == "/" {
req.set_method(Method::Put); println!(" => Changing method to `PUT`.");
}))) req.set_method(Method::Put);
.attach(Fairing::Response(Box::new(|_, res| { }
println!(" => Rewriting response body."); }))
res.set_sized_body(Cursor::new("Hello, fairings!")); .attach(AdHoc::on_response(|req, res| {
}))) if req.uri().path() == "/" {
println!(" => Rewriting response body.");
res.set_sized_body(Cursor::new("Hello, fairings!"));
}
}))
} }
fn main() { fn main() {

View File

@ -3,9 +3,33 @@ use rocket::testing::MockRequest;
use rocket::http::Method::*; use rocket::http::Method::*;
#[test] #[test]
fn fairings() { fn rewrite_get_put() {
let rocket = rocket(); let rocket = rocket();
let mut req = MockRequest::new(Get, "/"); let mut req = MockRequest::new(Get, "/");
let mut response = req.dispatch_with(&rocket); let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Hello, fairings!".into())); assert_eq!(response.body_string(), Some("Hello, fairings!".into()));
} }
#[test]
fn counts() {
let rocket = rocket();
// Issue 1 GET request.
let mut req = MockRequest::new(Get, "/");
req.dispatch_with(&rocket);
// Check the GET count, taking into account _this_ GET request.
let mut req = MockRequest::new(Get, "/counts");
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Get: 2\nPost: 0".into()));
// Issue 1 more GET request and a POST.
let mut req = MockRequest::new(Get, "/");
req.dispatch_with(&rocket);
let mut req = MockRequest::new(Post, "/");
req.dispatch_with(&rocket);
// Check the counts.
let mut req = MockRequest::new(Get, "/counts");
let mut response = req.dispatch_with(&rocket);
assert_eq!(response.body_string(), Some("Get: 4\nPost: 1".into()));
}

136
lib/src/fairing/ad_hoc.rs Normal file
View File

@ -0,0 +1,136 @@
use {Rocket, Request, Response, Data};
use fairing::{Fairing, Kind, Info};
/// A ad-hoc fairing that can be created from a function or closure.
///
/// This enum can be used to create a fairing from a simple function or clusure
/// without creating a new structure or implementing `Fairing` directly.
///
/// # Usage
///
/// Use the [`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.
///
/// # Example
///
/// The following snippet creates a `Rocket` instance with two ad-hoc fairings.
/// The first, a launch fairing, simply prints a message indicating that the
/// application is about to the launch. The second, 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(|rocket| {
/// 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 **launch** fairing. Called just before Rocket launches.
#[doc(hidden)]
Launch(Box<Fn(Rocket) -> Result<Rocket, 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>)
}
impl AdHoc {
/// Constructs an `AdHoc` launch fairing. The function `f` will be called by
/// Rocket just prior to launching.
///
/// # Example
///
/// ```rust
/// use rocket::fairing::AdHoc;
///
/// // The no-op launch fairing.
/// let fairing = AdHoc::on_launch(|rocket| Ok(rocket));
/// ```
pub fn on_launch<F>(f: F) -> AdHoc
where F: Fn(Rocket) -> Result<Rocket, Rocket> + Send + Sync + 'static
{
AdHoc::Launch(Box::new(f))
}
/// Constructs an `AdHoc` request fairing. The function `f` will be called
/// by Rocket when a new request is received.
///
/// # Example
///
/// ```rust
/// use rocket::fairing::AdHoc;
///
/// // The no-op request fairing.
/// let fairing = AdHoc::on_request(|req, data| {
/// // do something with the request and data...
/// # let (_, _) = (req, data);
/// });
/// ```
pub fn on_request<F>(f: F) -> AdHoc
where F: Fn(&mut Request, &Data) + Send + Sync + 'static
{
AdHoc::Request(Box::new(f))
}
/// Constructs an `AdHoc` response fairing. The function `f` will be called
/// by Rocket when a response is ready to be sent.
///
/// # Example
///
/// ```rust
/// use rocket::fairing::AdHoc;
///
/// // The no-op response fairing.
/// let fairing = AdHoc::on_response(|req, resp| {
/// // do something with the request and pending response...
/// # let (_, _) = (req, resp);
/// });
/// ```
pub fn on_response<F>(f: F) -> AdHoc
where F: Fn(&Request, &mut Response) + Send + Sync + 'static
{
AdHoc::Response(Box::new(f))
}
}
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 }
}
}
fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> {
match *self {
AdHoc::Launch(ref launch_fn) => launch_fn(rocket),
_ => Ok(rocket)
}
}
fn on_request(&self, request: &mut Request, data: &Data) {
if let AdHoc::Request(ref callback) = *self {
callback(request, data)
}
}
fn on_response(&self, request: &Request, response: &mut Response) {
if let AdHoc::Response(ref callback) = *self {
callback(request, response)
}
}
}

View File

@ -0,0 +1,89 @@
use {Rocket, Request, Response, Data};
use fairing::{Fairing, Kind};
#[derive(Default)]
pub struct Fairings {
all_fairings: Vec<Box<Fairing>>,
launch: Vec<&'static Fairing>,
request: Vec<&'static Fairing>,
response: Vec<&'static Fairing>,
}
impl Fairings {
#[inline]
pub fn new() -> Fairings {
Fairings::default()
}
#[inline]
pub fn attach(&mut self, fairing: Box<Fairing>) {
// Get the kind information.
let kind = fairing.info().kind;
// 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
// kinds. The lifetime of that reference is really the lifetime of the
// `Box` for referred fairing, but that lifetime is dynamic; there's no
// way to express it. So we cheat and say that the lifetime is
// `'static` and cast it here. For this to be safe, the following must
// be preserved:
//
// 1) The references can never be exposed with a `'static` lifetime.
// 2) The `Box<Fairing>` must live for the lifetime of the reference.
//
// We maintain these invariants by not exposing the references and never
// 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) };
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); }
}
#[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 });
}
success.map(|_| rocket)
}
#[inline(always)]
pub fn handle_request(&self, req: &mut Request, data: &Data) {
for fairing in &self.request {
fairing.on_request(req, data);
}
}
#[inline(always)]
pub fn handle_response(&self, request: &Request, response: &mut Response) {
for fairing in &self.response {
fairing.on_response(request, response);
}
}
pub fn pretty_print_counts(&self) {
use term_painter::ToStyle;
use term_painter::Color::{White, Magenta};
if self.all_fairings.len() > 0 {
info!("📡 {}:", Magenta.paint("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_if_nonempty("launch", &self.launch);
info_if_nonempty("request", &self.request);
info_if_nonempty("response", &self.response);
}
}

View File

@ -0,0 +1,89 @@
use std::ops::BitOr;
/// Information about a [`Fairing`](/rocket/fairing/trait.Fairing.html).
///
/// The `name` field is an arbitrary name for a fairing. The `kind` field is a
/// is an `or`d set of [`Kind`](/rocket/fairing/struct.Kind.html) structures.
/// Rocket uses the values set in `Kind` to determine which callbacks from a
/// given `Fairing` implementation to actually call.
///
/// # Example
///
/// A simple `Info` structure that can be used for a `Fairing` that implements
/// all three callbacks:
///
/// ```
/// use rocket::fairing::{Info, Kind};
///
/// # let _unused_info =
/// Info {
/// name: "Example Fairing",
/// kind: Kind::Launch | Kind::Request | Kind::Response
/// }
/// # ;
/// ```
pub struct Info {
/// The name of the fairing.
pub name: &'static str,
/// A set representing the callbacks the fairing wishes to receive.
pub kind: Kind
}
/// A bitset representing the kinds of callbacks a
/// [`Fairing`](/rocket/fairing/trait.Fairing.html) wishes to receive.
///
/// A fairing can request any combination of any of the following kinds of
/// callbacks:
///
/// * Launch
/// * 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 all three kinds, use `Kind::Launch | Kind::Request | Kind::Response`.
#[derive(Debug, Clone, Copy)]
pub struct Kind(usize);
#[allow(non_upper_case_globals)]
impl Kind {
/// `Kind` flag representing a request for a 'launch' callback.
pub const Launch: Kind = Kind(0b001);
/// `Kind` flag representing a request for a 'request' callback.
pub const Request: Kind = Kind(0b010);
/// `Kind` flag representing a request for a 'response' callback.
pub const Response: Kind = Kind(0b100);
/// 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`.
///
/// # Example
///
/// ```rust
/// use rocket::fairing::Kind;
///
/// let launch_and_req = Kind::Launch | Kind::Request;
/// assert!(launch_and_req.is(Kind::Launch | Kind::Request));
///
/// assert!(launch_and_req.is(Kind::Launch));
/// assert!(launch_and_req.is(Kind::Request));
///
/// 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));
/// ```
#[inline]
pub fn is(self, other: Kind) -> bool {
(other.0 & self.0) == other.0
}
}
impl BitOr for Kind {
type Output = Self;
#[inline(always)]
fn bitor(self, rhs: Self) -> Self {
Kind(self.0 | rhs.0)
}
}

View File

@ -2,34 +2,52 @@
//! //!
//! Fairings allow for structured interposition at various points in the //! Fairings allow for structured interposition at various points in the
//! application lifetime. Fairings can be seen as a restricted form of //! application lifetime. Fairings can be seen as a restricted form of
//! "middleware". A fairing is simply a function with a particular signature //! "middleware". A fairing is an arbitrary structure with methods representing
//! that Rocket will run at a requested point in a program. You can use fairings //! callbacks that Rocket will run at requested points in a program. You can use
//! to rewrite or record information about requests and responses, or to perform //! fairings to rewrite or record information about requests and responses, or
//! an action once a Rocket application has launched. //! to perform an action once a Rocket application has launched.
//! //!
//! ## Attaching //! ## Attaching
//! //!
//! You must inform Rocket about fairings that you wish to be active by calling //! You must inform Rocket about fairings that you wish to be active by calling
//! the [`attach`](/rocket/struct.Rocket.html#method.attach) method on the //! the [`attach`](/rocket/struct.Rocket.html#method.attach) method on the
//! [`Rocket`](/rocket/struct.Rocket.html) instance and passing in the //! [`Rocket`](/rocket/struct.Rocket.html) instance and passing in the
//! appropriate [`Fairing`](/rocket/fairing/enum.Fairing.html). For instance, to //! appropriate [`Fairing`](/rocket/fairing/trait.Fairing.html). For instance,
//! attach `Request` and `Response` fairings named `req_fairing` and //! to attach fairings named `req_fairing` and `res_fairing` to a new Rocket
//! `res_fairing` to a new Rocket instance, you might write: //! instance, you might write:
//! //!
//! ```rust //! ```rust
//! # use rocket::Fairing; //! # use rocket::fairing::AdHoc;
//! # let req_fairing = Fairing::Request(Box::new(|_, _| ())); //! # let req_fairing = AdHoc::on_request(|_, _| ());
//! # let res_fairing = Fairing::Response(Box::new(|_, _| ())); //! # let res_fairing = AdHoc::on_response(|_, _| ());
//! # #[allow(unused_variables)]
//! let rocket = rocket::ignite() //! let rocket = rocket::ignite()
//! .attach(vec![req_fairing, res_fairing]); //! .attach(req_fairing)
//! .attach(res_fairing);
//! ``` //! ```
//! //!
//! Once a fairing is attached, Rocket will execute it at the appropiate time, //! Once a fairing is attached, Rocket will execute it at the appropiate time,
//! which varies depending on the fairing type. //! which varies depending on the fairing type. See the
//! [`Fairing`](/rocket/fairing/trait.Fairing.html) trait documentation for more
//! information on the dispatching of fairing methods.
//!
//! ## Ordering
//!
//! `Fairing`s are executed in the same order in which they are attached: the
//! first attached fairing has its callbacks executed before all others. Because
//! 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.
use {Rocket, Request, Response, Data}; use {Rocket, Request, Response, Data};
mod fairings;
mod ad_hoc;
mod info_kind;
pub(crate) use self::fairings::Fairings;
pub use self::ad_hoc::AdHoc;
pub use self::info_kind::{Info, Kind};
// We might imagine that a request fairing returns an `Outcome`. If it returns // We might imagine that a request fairing returns an `Outcome`. If it returns
// `Success`, we don't do any routing and use that response directly. Same if it // `Success`, we don't do any routing and use that response directly. Same if it
// returns `Failure`. We only route if it returns `Forward`. I've chosen not to // returns `Failure`. We only route if it returns `Forward`. I've chosen not to
@ -42,159 +60,221 @@ use {Rocket, Request, Response, Data};
// appropriate response. This allows the users to handle `OPTIONS` requests // appropriate response. This allows the users to handle `OPTIONS` requests
// when they'd like but default to the fairing when they don't want to. // when they'd like but default to the fairing when they don't want to.
/// The type of a **launch** fairing callback. /// Trait implemented by fairings: Rocket's structured middleware.
/// ///
/// The `Rocket` parameter is the `Rocket` instance being built. The launch /// ## Fairing Information
/// fairing can modify the `Rocket` instance arbitrarily.
/// ///
/// TODO: Document fully with examples before 0.3. /// Every `Fairing` must implement the
pub type LaunchFn = Box<Fn(Rocket) -> Result<Rocket, Rocket> + Send + Sync + 'static>; /// [`info`](/rocket/fairing/trait.Fairing.html#tymethod.info) method, which
/// The type of a **request** fairing callback. /// returns an [`Info`](http://localhost:8000/rocket/fairing/struct.Info.html)
/// structure. This structure is used by Rocket to:
/// ///
/// The `&mut Request` parameter is the incoming request, and the `&Data` /// 1. Assign a name to the `Fairing`.
/// parameter is the incoming data in the request.
/// ///
/// TODO: Document fully with examples before 0.3. /// This is the `name` field, which can be any arbitrary string. Name your
pub type RequestFn = Box<Fn(&mut Request, &Data) + Send + Sync + 'static>; /// fairing something illustrative. The name will be logged during the
/// The type of a **response** fairing callback. /// application's launch procedures.
/// ///
/// The `&Request` parameter is the request that was routed, and the `&mut /// 2. Determine which callbacks to actually issue on the `Fairing`.
/// Response` parameter is the result response.
/// ///
/// TODO: Document fully with examples before 0.3. /// This is the `kind` field of type
pub type ResponseFn = Box<Fn(&Request, &mut Response) + Send + Sync + 'static>; /// [`Kind`](/rocket/fairing/struct.Kind.html). This field is a bitset that
/// represents the kinds of callbacks the fairing wishes to receive. Rocket
/// An enum representing the three fairing types: launch, request, and response. /// 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`.
/// ///
/// ## Fairing Types /// See the [top-level documentation](/rocket/fairing/) for more general
/// information.
/// ///
/// The three types of fairings, launch, request, and response, operate as /// ## Fairing Callbacks
/// follows:
/// ///
/// * *Launch Fairings* /// 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.
/// ///
/// An attached launch fairing will be called immediately before the Rocket /// The three callback kinds are as follows:
/// application has launched. At this point, Rocket has opened a socket for
/// listening but has not yet begun accepting connections. A launch fairing
/// 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 fairing returns `Err`, launch is aborted. The
/// [`LaunchFn`](/rocket/fairing/type.LaunchFn.html) documentation contains
/// further information and tips on the function signature.
/// ///
/// * *Request Fairings* /// * **Launch (`on_launch`)**
/// ///
/// An attached request fairing is called when a request is received. At /// A launch callback, represented by the
/// this point, Rocket has parsed the incoming HTTP into a /// [`on_launch`](/rocket/fairing/trait.Fairing.html#method.on_launch)
/// [Request](/rocket/struct.Request.html) and /// method, is called immediately before the Rocket application has
/// [Data](/rocket/struct.Data.html) object but has not routed the request. /// launched. At this point, Rocket has opened a socket for listening but
/// A request fairing can modify the request at will and /// has not yet begun accepting connections. A launch callback can
/// [peek](/rocket/struct.Data.html#method.peek) into the incoming data. It /// arbitrarily modify the `Rocket` instance being launched. It returns `Ok`
/// may not, however, abort or respond directly to the request; these issues /// if it would like launching to proceed nominally and `Err` otherwise. If
/// are better handled via [request /// a launch callback returns `Err`, launch is aborted.
///
/// * **Request (`on_request`)**
///
/// A request callback, represented by the
/// [`on_request`](/rocket/fairing/trait.Fairing.html#method.on_request)
/// method, is called just after a request is received. At this point,
/// Rocket has parsed the incoming HTTP into
/// [`Request`](/rocket/struct.Request.html) and
/// [`Data`](/rocket/struct.Data.html) structures but has not routed the
/// request. A request callback can modify the request at will and
/// [`peek`](/rocket/struct.Data.html#method.peek) into the incoming data.
/// It may not, however, abort or respond directly to the request; these
/// issues are better handled via [request
/// guards](/rocket/request/trait.FromRequest.html) or via response /// guards](/rocket/request/trait.FromRequest.html) or via response
/// fairings. A modified request is routed as if it was the original /// callbacks. A modified request is routed as if it was the original
/// request. The [`RequestFn`](/rocket/fairing/type.RequestFn.html) /// request.
/// documentation contains further information and tips on the function
/// signature.
/// ///
/// * *Response Fairings* /// * **Response (`on_response`)**
/// ///
/// An attached response fairing is called when a response is ready to be /// A response callback is called when a response is ready to be sent to the
/// sent to the client. At this point, Rocket has completed all routing, /// client. At this point, Rocket has completed all routing, including to
/// including to error catchers, and has generated the would-be final /// error catchers, and has generated the would-be final response. A
/// response. A response fairing can modify the response at will. A response /// response callback can modify the response at will. For exammple, a
/// fairing, can, for example, provide a default response when the user /// response callback can provide a default response when the user fails to
/// fails to handle the request by checking for 404 responses. The /// handle the request by checking for 404 responses.
/// [`ResponseFn`](/rocket/fairing/type.ResponseFn.html) documentation
/// contains further information and tips on the function signature.
/// ///
/// See the [top-level documentation](/rocket/fairing/) for general information. /// # Implementing
pub enum Fairing { ///
/// A launch fairing. Called just before Rocket launches. /// A `Fairing` implementation has one required method: `info`. A `Fairing` can
Launch(LaunchFn), /// also implement any of the available callbacks: `on_launch`, `on_request`,
/// A request fairing. Called when a request is received. /// and `on_response`. A `Fairing` _must_ set the appropriate callback kind in
Request(RequestFn), /// the `kind` field of the returned `Info` structure from `info` for a callback
/// A response fairing. Called when a response is ready to be sent. /// to actually be issued by Rocket.
Response(ResponseFn), ///
} /// A `Fairing` must be `Send + Sync + 'static`. This means that the fairing
/// must be sendable across thread boundaries (`Send`), thread-safe (`Sync`),
#[derive(Default)] /// and have no non-`'static` reference (`'static`). Note that these bounds _do
pub(crate) struct Fairings { /// not_ prohibit a `Fairing` from having state: the state need simply be
pub launch: Vec<LaunchFn>, /// thread-safe and statically available or heap allocated.
pub request: Vec<RequestFn>, ///
pub response: Vec<ResponseFn>, /// # Example
} ///
/// Imagine that we want to record the number of `GET` and `POST` requests that
impl Fairings { /// our application has received. While we could do this with [request
#[inline] /// guards](/rocket/request/trait.FromRequest.html) and [managed
pub fn new() -> Fairings { /// state](/rocket/request/struct.State.html), it would require us to annotate
Fairings::default() /// every `GET` and `POST` request with custom types, polluting handler
} /// signatures. Instead, we can create a simple fairing that does this globally.
///
#[inline(always)] /// The `Counter` fairing below records the number of all `GET` and `POST`
pub fn attach_all(&mut self, fairings: Vec<Fairing>) { /// requests received. It makes these counts available at a special `'/counts'`
for fairing in fairings { /// path.
self.attach(fairing) ///
} /// ```rust
} /// use std::io::Cursor;
/// use std::sync::atomic::{AtomicUsize, Ordering};
#[inline] ///
pub fn attach(&mut self, fairing: Fairing) { /// use rocket::{Request, Data, Response};
match fairing { /// use rocket::fairing::{Fairing, Info, Kind};
Fairing::Launch(f) => self.launch.push(f), /// use rocket::http::{Method, ContentType, Status};
Fairing::Request(f) => self.request.push(f), ///
Fairing::Response(f) => self.response.push(f), /// #[derive(Default)]
} /// struct Counter {
} /// get: AtomicUsize,
/// post: AtomicUsize,
#[inline(always)] /// }
pub fn handle_launch(&mut self, mut rocket: Rocket) -> Option<Rocket> { ///
let mut success = Some(()); /// impl Fairing for Counter {
let launch_fairings = ::std::mem::replace(&mut self.launch, vec![]); /// fn info(&self) -> Info {
for fairing in launch_fairings { /// Info {
rocket = fairing(rocket).unwrap_or_else(|r| { success = None; r }); /// name: "GET/POST Counter",
} /// kind: Kind::Request | Kind::Response
/// }
success.map(|_| rocket) /// }
} ///
/// fn on_request(&self, request: &mut Request, _: &Data) {
#[inline(always)] /// if request.method() == Method::Get {
pub fn handle_request(&self, req: &mut Request, data: &Data) { /// self.get.fetch_add(1, Ordering::Relaxed);
for fairing in &self.request { /// } else if request.method() == Method::Post {
fairing(req, data); /// self.post.fetch_add(1, Ordering::Relaxed);
} /// }
} /// }
///
#[inline(always)] /// fn on_response(&self, request: &Request, response: &mut Response) {
pub fn handle_response(&self, request: &Request, response: &mut Response) { /// // Don't change a successful user's response, ever.
for fairing in &self.response { /// if response.status() != Status::NotFound {
fairing(request, response); /// return
} /// }
} ///
/// if request.method() == Method::Get && request.uri().path() == "/counts" {
fn num_attached(&self) -> usize { /// let get_count = self.get.load(Ordering::Relaxed);
self.launch.len() + self.request.len() + self.response.len() /// let post_count = self.post.load(Ordering::Relaxed);
} ///
/// let body = format!("Get: {}\nPost: {}", get_count, post_count);
pub fn pretty_print_counts(&self) { /// response.set_status(Status::Ok);
use term_painter::ToStyle; /// response.set_header(ContentType::Plain);
use term_painter::Color::{White, Magenta}; /// response.set_sized_body(Cursor::new(body));
/// }
if self.num_attached() > 0 { /// }
info!("📡 {}:", Magenta.paint("Fairings")); /// }
} /// ```
pub trait Fairing: Send + Sync + 'static {
if !self.launch.is_empty() { /// Returns an [`Info`](/rocket/fairing/struct.Info.html) structure
info_!("{} launch", White.paint(self.launch.len())); /// containing the `name` and [`Kind`](/rocket/fairing/struct.Kind.html) of
} /// this fairing. The `name` can be any arbitrary string. `Kind` must be an
/// `or`d set of `Kind` variants.
if !self.request.is_empty() { ///
info_!("{} request", White.paint(self.request.len())); /// This is the only required method of a `Fairing`. All other methods have
} /// no-op default implementations.
///
if !self.response.is_empty() { /// Rocket will only dispatch callbacks to this fairing for the kinds in the
info_!("{} response", White.paint(self.response.len())); /// `kind` field of the returned `Info` structure. For instance, if
} /// `Kind::Launch | Kind::Request` is used, then Rocket will only call the
} /// `on_launch` and `on_request` methods of the fairing. Similarly, if
/// `Kind::Response` is used, Rocket will only call the `on_response` method
/// of this fairing.
///
/// # Example
///
/// An `info` implementation for `MyFairing`: a fairing named "My Custom
/// Fairing" that is both a launch and response fairing.
///
/// ```rust
/// use rocket::fairing::{Fairing, Info, Kind};
///
/// struct MyFairing;
///
/// impl Fairing for MyFairing {
/// fn info(&self) -> Info {
/// Info {
/// name: "My Custom Fairing",
/// kind: Kind::Launch | Kind::Response
/// }
/// }
/// }
/// ```
fn info(&self) -> Info;
/// The launch 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.
///
/// The default implementation of this method simply returns `Ok(rocket)`.
fn on_launch(&self, rocket: Rocket) -> Result<Rocket, Rocket> { Ok(rocket) }
/// The request callback.
///
/// 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`
/// parameter is the incoming data in the request.
///
/// The default implementation of this method does nothing.
#[allow(unused_variables)]
fn on_request(&self, request: &mut Request, data: &Data) { }
/// The response callback.
///
/// 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,
/// and the `&mut Response` parameter is the resulting response.
///
/// The default implementation of this method does nothing.
#[allow(unused_variables)]
fn on_response(&self, request: &Request, response: &mut Response) { }
} }

View File

@ -126,12 +126,12 @@ pub mod outcome;
pub mod config; pub mod config;
pub mod data; pub mod data;
pub mod handler; pub mod handler;
pub mod error;
pub mod fairing; pub mod fairing;
mod router; mod router;
mod rocket; mod rocket;
mod codegen; mod codegen;
mod error;
mod catcher; mod catcher;
mod ext; mod ext;
@ -141,7 +141,6 @@ mod ext;
#[doc(hidden)] pub use codegen::{StaticRouteInfo, StaticCatchInfo}; #[doc(hidden)] pub use codegen::{StaticRouteInfo, StaticCatchInfo};
#[doc(inline)] pub use outcome::Outcome; #[doc(inline)] pub use outcome::Outcome;
#[doc(inline)] pub use data::Data; #[doc(inline)] pub use data::Data;
#[doc(inline)] pub use fairing::Fairing;
pub use router::Route; pub use router::Route;
pub use request::{Request, State}; pub use request::{Request, State};
pub use error::{Error, LaunchError}; pub use error::{Error, LaunchError};

View File

@ -11,7 +11,7 @@ use state::Container;
#[cfg(feature = "tls")] use hyper_rustls::TlsServer; #[cfg(feature = "tls")] use hyper_rustls::TlsServer;
use {logger, handler}; use {logger, handler};
use ext::{ReadExt, IntoCollection}; use ext::ReadExt;
use config::{self, Config, LoggedValue}; use config::{self, Config, LoggedValue};
use request::{Request, FormItems}; use request::{Request, FormItems};
use data::Data; use data::Data;
@ -587,21 +587,18 @@ impl Rocket {
self self
} }
/// Attaches zero or more fairings to this instance of Rocket. /// Attaches a fairing to this instance of Rocket.
/// ///
/// The `fairings` parameter to this function is generic: it may be either /// # Example
/// a `Vec<Fairing>`, `&[Fairing]`, or simply `Fairing`. In all cases, all
/// supplied fairings are attached.
///
/// # Examples
/// ///
/// ```rust /// ```rust
/// # #![feature(plugin)] /// # #![feature(plugin)]
/// # #![plugin(rocket_codegen)] /// # #![plugin(rocket_codegen)]
/// # extern crate rocket; /// # extern crate rocket;
/// use rocket::{Rocket, Fairing}; /// use rocket::Rocket;
/// use rocket::fairing::AdHoc;
/// ///
/// fn launch_fairing(rocket: Rocket) -> Result<Rocket, Rocket> { /// fn youll_see(rocket: Rocket) -> Result<Rocket, Rocket> {
/// println!("Rocket is about to launch! You just see..."); /// println!("Rocket is about to launch! You just see...");
/// Ok(rocket) /// Ok(rocket)
/// } /// }
@ -609,15 +606,14 @@ impl Rocket {
/// fn main() { /// fn main() {
/// # if false { // We don't actually want to launch the server in an example. /// # if false { // We don't actually want to launch the server in an example.
/// rocket::ignite() /// rocket::ignite()
/// .attach(Fairing::Launch(Box::new(launch_fairing))) /// .attach(AdHoc::on_launch(youll_see))
/// .launch(); /// .launch();
/// # } /// # }
/// } /// }
/// ``` /// ```
#[inline] #[inline]
pub fn attach<C: IntoCollection<Fairing>>(mut self, fairings: C) -> Self { pub fn attach<F: Fairing>(mut self, fairing: F) -> Self {
let fairings = fairings.into_collection::<[Fairing; 1]>().into_vec(); self.fairings.attach(Box::new(fairing));
self.fairings.attach_all(fairings);
self self
} }