From 9c9740f9661354f8a240c8979a7890517bec7994 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Sun, 14 May 2017 21:46:01 -0700 Subject: [PATCH] Fairings v2. --- examples/fairings/src/main.rs | 70 +++++- examples/fairings/src/tests.rs | 26 ++- lib/src/fairing/ad_hoc.rs | 136 ++++++++++++ lib/src/fairing/fairings.rs | 89 ++++++++ lib/src/fairing/info_kind.rs | 89 ++++++++ lib/src/fairing/mod.rs | 386 ++++++++++++++++++++------------- lib/src/lib.rs | 3 +- lib/src/rocket.rs | 22 +- 8 files changed, 640 insertions(+), 181 deletions(-) create mode 100644 lib/src/fairing/ad_hoc.rs create mode 100644 lib/src/fairing/fairings.rs create mode 100644 lib/src/fairing/info_kind.rs diff --git a/examples/fairings/src/main.rs b/examples/fairings/src/main.rs index f28ebbcb..579e8720 100644 --- a/examples/fairings/src/main.rs +++ b/examples/fairings/src/main.rs @@ -4,12 +4,53 @@ extern crate rocket; use std::io::Cursor; +use std::sync::atomic::{AtomicUsize, Ordering}; -use rocket::Fairing; -use rocket::http::Method; +use rocket::{Request, Data, Response}; +use rocket::fairing::{AdHoc, Fairing, Info, Kind}; +use rocket::http::{Method, ContentType, Status}; #[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("/")] fn hello() -> &'static str { "Hello, world!" @@ -18,19 +59,24 @@ fn hello() -> &'static str { fn rocket() -> rocket::Rocket { rocket::ignite() .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..."); Ok(rocket) - }))) - .attach(Fairing::Request(Box::new(|req, _| { + })) + .attach(AdHoc::on_request(|req, _| { println!(" => Incoming request: {}", req); - 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!")); - }))) + if req.uri().path() == "/" { + println!(" => Changing method to `PUT`."); + req.set_method(Method::Put); + } + })) + .attach(AdHoc::on_response(|req, res| { + if req.uri().path() == "/" { + println!(" => Rewriting response body."); + res.set_sized_body(Cursor::new("Hello, fairings!")); + } + })) } fn main() { diff --git a/examples/fairings/src/tests.rs b/examples/fairings/src/tests.rs index 197cefd2..ecba4a6a 100644 --- a/examples/fairings/src/tests.rs +++ b/examples/fairings/src/tests.rs @@ -3,9 +3,33 @@ use rocket::testing::MockRequest; use rocket::http::Method::*; #[test] -fn fairings() { +fn rewrite_get_put() { let rocket = rocket(); let mut req = MockRequest::new(Get, "/"); let mut response = req.dispatch_with(&rocket); 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())); +} diff --git a/lib/src/fairing/ad_hoc.rs b/lib/src/fairing/ad_hoc.rs new file mode 100644 index 00000000..3ba630f6 --- /dev/null +++ b/lib/src/fairing/ad_hoc.rs @@ -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 Result + Send + Sync + 'static>), + /// An ad-hoc **request** fairing. Called when a request is received. + #[doc(hidden)] + Request(Box), + /// An ad-hoc **response** fairing. Called when a response is ready to be + /// sent to a client. + #[doc(hidden)] + Response(Box) +} + +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) -> AdHoc + where F: Fn(Rocket) -> Result + 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) -> 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) -> 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 { + 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) + } + } +} diff --git a/lib/src/fairing/fairings.rs b/lib/src/fairing/fairings.rs new file mode 100644 index 00000000..4129d444 --- /dev/null +++ b/lib/src/fairing/fairings.rs @@ -0,0 +1,89 @@ +use {Rocket, Request, Response, Data}; +use fairing::{Fairing, Kind}; + +#[derive(Default)] +pub struct Fairings { + all_fairings: Vec>, + 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) { + // 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` must live for the lifetime of the reference. + // + // We maintain these invariants by not exposing the references and never + // deallocating `Box` 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 { + 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); + } +} diff --git a/lib/src/fairing/info_kind.rs b/lib/src/fairing/info_kind.rs new file mode 100644 index 00000000..ac0091ec --- /dev/null +++ b/lib/src/fairing/info_kind.rs @@ -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) + } +} diff --git a/lib/src/fairing/mod.rs b/lib/src/fairing/mod.rs index dd176d49..f2e2f08e 100644 --- a/lib/src/fairing/mod.rs +++ b/lib/src/fairing/mod.rs @@ -2,34 +2,52 @@ //! //! Fairings allow for structured interposition at various points in the //! application lifetime. Fairings can be seen as a restricted form of -//! "middleware". A fairing is simply a function with a particular signature -//! that Rocket will run at a requested point in a program. You can use fairings -//! to rewrite or record information about requests and responses, or to perform -//! an action once a Rocket application has launched. +//! "middleware". A fairing is an arbitrary structure with methods representing +//! callbacks that Rocket will run at requested points in a program. You can use +//! fairings to rewrite or record information about requests and responses, or +//! to perform an action once a Rocket application has launched. //! //! ## Attaching //! //! 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 //! [`Rocket`](/rocket/struct.Rocket.html) instance and passing in the -//! appropriate [`Fairing`](/rocket/fairing/enum.Fairing.html). For instance, to -//! attach `Request` and `Response` fairings named `req_fairing` and -//! `res_fairing` to a new Rocket instance, you might write: +//! appropriate [`Fairing`](/rocket/fairing/trait.Fairing.html). For instance, +//! to attach fairings named `req_fairing` and `res_fairing` to a new Rocket +//! instance, you might write: //! //! ```rust -//! # use rocket::Fairing; -//! # let req_fairing = Fairing::Request(Box::new(|_, _| ())); -//! # let res_fairing = Fairing::Response(Box::new(|_, _| ())); -//! # #[allow(unused_variables)] +//! # use rocket::fairing::AdHoc; +//! # let req_fairing = AdHoc::on_request(|_, _| ()); +//! # let res_fairing = AdHoc::on_response(|_, _| ()); //! 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, -//! 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}; +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 // `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 @@ -42,159 +60,221 @@ use {Rocket, Request, Response, Data}; // 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. -/// 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 can modify the `Rocket` instance arbitrarily. +/// ## Fairing Information /// -/// TODO: Document fully with examples before 0.3. -pub type LaunchFn = Box Result + Send + Sync + 'static>; -/// The type of a **request** fairing callback. +/// Every `Fairing` must implement the +/// [`info`](/rocket/fairing/trait.Fairing.html#tymethod.info) method, which +/// 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` -/// parameter is the incoming data in the request. +/// 1. Assign a name to the `Fairing`. /// -/// TODO: Document fully with examples before 0.3. -pub type RequestFn = Box; -/// The type of a **response** fairing callback. +/// 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. /// -/// The `&Request` parameter is the request that was routed, and the `&mut -/// Response` parameter is the result response. +/// 2. Determine which callbacks to actually issue on the `Fairing`. /// -/// TODO: Document fully with examples before 0.3. -pub type ResponseFn = Box; - -/// An enum representing the three fairing types: launch, request, and response. +/// This is the `kind` field of type +/// [`Kind`](/rocket/fairing/struct.Kind.html). This field is a bitset that +/// 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`. /// -/// ## Fairing Types +/// See the [top-level documentation](/rocket/fairing/) for more general +/// information. /// -/// The three types of fairings, launch, request, and response, operate as -/// follows: +/// ## Fairing Callbacks /// -/// * *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 -/// 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. +/// The three callback kinds are as follows: /// -/// * *Request Fairings* +/// * **Launch (`on_launch`)** /// -/// An attached request fairing is called when a request is received. At -/// this point, Rocket has parsed the incoming HTTP into a -/// [Request](/rocket/struct.Request.html) and -/// [Data](/rocket/struct.Data.html) object but has not routed the request. -/// A request fairing 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 +/// A launch callback, represented by the +/// [`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. +/// +/// * **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 -/// fairings. A modified request is routed as if it was the original -/// request. The [`RequestFn`](/rocket/fairing/type.RequestFn.html) -/// documentation contains further information and tips on the function -/// signature. +/// callbacks. A modified request is routed as if it was the original +/// request. /// -/// * *Response Fairings* +/// * **Response (`on_response`)** /// -/// An attached response fairing is called when a response is ready to be -/// sent to the client. At this point, Rocket has completed all routing, -/// including to error catchers, and has generated the would-be final -/// response. A response fairing can modify the response at will. A response -/// fairing, can, for example, provide a default response when the user -/// fails to handle the request by checking for 404 responses. The -/// [`ResponseFn`](/rocket/fairing/type.ResponseFn.html) documentation -/// contains further information and tips on the function signature. +/// A response callback is called when a response is ready to be sent to the +/// client. At this point, Rocket has completed all routing, including to +/// error catchers, and has generated the would-be final response. A +/// response callback can modify the response at will. For exammple, a +/// response callback can provide a default response when the user fails to +/// handle the request by checking for 404 responses. /// -/// See the [top-level documentation](/rocket/fairing/) for general information. -pub enum Fairing { - /// A launch fairing. Called just before Rocket launches. - Launch(LaunchFn), - /// A request fairing. Called when a request is received. - Request(RequestFn), - /// A response fairing. Called when a response is ready to be sent. - Response(ResponseFn), -} - -#[derive(Default)] -pub(crate) struct Fairings { - pub launch: Vec, - pub request: Vec, - pub response: Vec, -} - -impl Fairings { - #[inline] - pub fn new() -> Fairings { - Fairings::default() - } - - #[inline(always)] - pub fn attach_all(&mut self, fairings: Vec) { - for fairing in fairings { - self.attach(fairing) - } - } - - #[inline] - pub fn attach(&mut self, fairing: Fairing) { - match fairing { - Fairing::Launch(f) => self.launch.push(f), - Fairing::Request(f) => self.request.push(f), - Fairing::Response(f) => self.response.push(f), - } - } - - #[inline(always)] - pub fn handle_launch(&mut self, mut rocket: Rocket) -> Option { - let mut success = Some(()); - let launch_fairings = ::std::mem::replace(&mut self.launch, vec![]); - for fairing in launch_fairings { - rocket = fairing(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(req, data); - } - } - - #[inline(always)] - pub fn handle_response(&self, request: &Request, response: &mut Response) { - for fairing in &self.response { - fairing(request, response); - } - } - - fn num_attached(&self) -> usize { - self.launch.len() + self.request.len() + self.response.len() - } - - pub fn pretty_print_counts(&self) { - use term_painter::ToStyle; - use term_painter::Color::{White, Magenta}; - - if self.num_attached() > 0 { - info!("📡 {}:", Magenta.paint("Fairings")); - } - - if !self.launch.is_empty() { - info_!("{} launch", White.paint(self.launch.len())); - } - - if !self.request.is_empty() { - info_!("{} request", White.paint(self.request.len())); - } - - if !self.response.is_empty() { - info_!("{} response", White.paint(self.response.len())); - } - } +/// # 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. +/// +/// 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 +/// 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](/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. +/// +/// The `Counter` fairing below records the number of all `GET` and `POST` +/// requests received. It makes these counts available at a special `'/counts'` +/// path. +/// +/// ```rust +/// use std::io::Cursor; +/// use std::sync::atomic::{AtomicUsize, Ordering}; +/// +/// use rocket::{Request, Data, Response}; +/// use rocket::fairing::{Fairing, Info, Kind}; +/// use rocket::http::{Method, ContentType, Status}; +/// +/// #[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) { +/// // Don't change a successful user's response, ever. +/// 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)); +/// } +/// } +/// } +/// ``` +pub trait Fairing: Send + Sync + 'static { + /// Returns an [`Info`](/rocket/fairing/struct.Info.html) structure + /// 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. + /// + /// This is the only required method of a `Fairing`. All other methods have + /// no-op default implementations. + /// + /// Rocket will only dispatch callbacks to this fairing for the kinds in the + /// `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 { 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) { } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 6c9bd4b5..6f894c4c 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -126,12 +126,12 @@ pub mod outcome; pub mod config; pub mod data; pub mod handler; -pub mod error; pub mod fairing; mod router; mod rocket; mod codegen; +mod error; mod catcher; mod ext; @@ -141,7 +141,6 @@ mod ext; #[doc(hidden)] pub use codegen::{StaticRouteInfo, StaticCatchInfo}; #[doc(inline)] pub use outcome::Outcome; #[doc(inline)] pub use data::Data; -#[doc(inline)] pub use fairing::Fairing; pub use router::Route; pub use request::{Request, State}; pub use error::{Error, LaunchError}; diff --git a/lib/src/rocket.rs b/lib/src/rocket.rs index 6bf54446..2b5c8104 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -11,7 +11,7 @@ use state::Container; #[cfg(feature = "tls")] use hyper_rustls::TlsServer; use {logger, handler}; -use ext::{ReadExt, IntoCollection}; +use ext::ReadExt; use config::{self, Config, LoggedValue}; use request::{Request, FormItems}; use data::Data; @@ -587,21 +587,18 @@ impl Rocket { 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 - /// a `Vec`, `&[Fairing]`, or simply `Fairing`. In all cases, all - /// supplied fairings are attached. - /// - /// # Examples + /// # Example /// /// ```rust /// # #![feature(plugin)] /// # #![plugin(rocket_codegen)] /// # extern crate rocket; - /// use rocket::{Rocket, Fairing}; + /// use rocket::Rocket; + /// use rocket::fairing::AdHoc; /// - /// fn launch_fairing(rocket: Rocket) -> Result { + /// fn youll_see(rocket: Rocket) -> Result { /// println!("Rocket is about to launch! You just see..."); /// Ok(rocket) /// } @@ -609,15 +606,14 @@ impl Rocket { /// fn main() { /// # if false { // We don't actually want to launch the server in an example. /// rocket::ignite() - /// .attach(Fairing::Launch(Box::new(launch_fairing))) + /// .attach(AdHoc::on_launch(youll_see)) /// .launch(); /// # } /// } /// ``` #[inline] - pub fn attach>(mut self, fairings: C) -> Self { - let fairings = fairings.into_collection::<[Fairing; 1]>().into_vec(); - self.fairings.attach_all(fairings); + pub fn attach(mut self, fairing: F) -> Self { + self.fairings.attach(Box::new(fairing)); self }