From ac0c78a0cd52042237cd5bb085548b0ae64bdb40 Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Thu, 20 Apr 2017 13:43:01 -0700 Subject: [PATCH] Initial implementation of fairings: structured middleware for Rocket. Closes #55. --- Cargo.toml | 1 + examples/fairings/Cargo.toml | 11 ++ examples/fairings/src/main.rs | 38 +++++++ examples/fairings/src/tests.rs | 11 ++ lib/Cargo.toml | 2 +- lib/src/config/config.rs | 1 + lib/src/config/custom_values.rs | 3 + lib/src/error.rs | 14 ++- lib/src/ext.rs | 46 ++++++++ lib/src/fairing/mod.rs | 192 ++++++++++++++++++++++++++++++++ lib/src/http/accept.rs | 3 +- lib/src/http/content_type.rs | 3 +- lib/src/http/media_type.rs | 2 +- lib/src/http/mod.rs | 45 -------- lib/src/lib.rs | 2 + lib/src/rocket.rs | 106 ++++++++++++++---- 16 files changed, 408 insertions(+), 72 deletions(-) create mode 100644 examples/fairings/Cargo.toml create mode 100644 examples/fairings/src/main.rs create mode 100644 examples/fairings/src/tests.rs create mode 100644 lib/src/fairing/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 97ae1a1f..cdd48faa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,4 +38,5 @@ members = [ "examples/session", "examples/raw_sqlite", "examples/hello_tls", + "examples/fairings", ] diff --git a/examples/fairings/Cargo.toml b/examples/fairings/Cargo.toml new file mode 100644 index 00000000..c45bc723 --- /dev/null +++ b/examples/fairings/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "fairings" +version = "0.0.0" +workspace = "../../" + +[dependencies] +rocket = { path = "../../lib" } +rocket_codegen = { path = "../../codegen" } + +[dev-dependencies] +rocket = { path = "../../lib", features = ["testing"] } diff --git a/examples/fairings/src/main.rs b/examples/fairings/src/main.rs new file mode 100644 index 00000000..f28ebbcb --- /dev/null +++ b/examples/fairings/src/main.rs @@ -0,0 +1,38 @@ +#![feature(plugin)] +#![plugin(rocket_codegen)] + +extern crate rocket; + +use std::io::Cursor; + +use rocket::Fairing; +use rocket::http::Method; + +#[cfg(test)] mod tests; + +#[put("/")] +fn hello() -> &'static str { + "Hello, world!" +} + +fn rocket() -> rocket::Rocket { + rocket::ignite() + .mount("/", routes![hello]) + .attach(Fairing::Launch(Box::new(|rocket| { + println!("Rocket is about to launch! Exciting! Here we go..."); + Ok(rocket) + }))) + .attach(Fairing::Request(Box::new(|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!")); + }))) +} + +fn main() { + rocket().launch(); +} diff --git a/examples/fairings/src/tests.rs b/examples/fairings/src/tests.rs new file mode 100644 index 00000000..197cefd2 --- /dev/null +++ b/examples/fairings/src/tests.rs @@ -0,0 +1,11 @@ +use super::rocket; +use rocket::testing::MockRequest; +use rocket::http::Method::*; + +#[test] +fn fairings() { + 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())); +} diff --git a/lib/Cargo.toml b/lib/Cargo.toml index 2a8741cd..8dcecad5 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -32,7 +32,7 @@ smallvec = "0.3.3" pear = "0.0.8" pear_codegen = "0.0.8" rustls = { version = "0.5.8", optional = true } -cookie = { version = "0.7.4", features = ["percent-encode", "secure"] } +cookie = { version = "0.7.5", features = ["percent-encode", "secure"] } hyper = { version = "0.10.9", default-features = false } [dependencies.hyper-rustls] diff --git a/lib/src/config/config.rs b/lib/src/config/config.rs index e51fb90d..6b88f056 100644 --- a/lib/src/config/config.rs +++ b/lib/src/config/config.rs @@ -28,6 +28,7 @@ use http::Key; /// .workers(12) /// .unwrap(); /// ``` +#[derive(Clone)] pub struct Config { /// The environment that this configuration corresponds to. pub environment: Environment, diff --git a/lib/src/config/custom_values.rs b/lib/src/config/custom_values.rs index 948bdacf..c534e669 100644 --- a/lib/src/config/custom_values.rs +++ b/lib/src/config/custom_values.rs @@ -6,6 +6,7 @@ use logger::LoggingLevel; use config::{Result, Config, Value, ConfigError}; use http::Key; +#[derive(Clone)] pub enum SessionKey { Generated(Key), Provided(Key) @@ -29,12 +30,14 @@ impl SessionKey { } #[cfg(feature = "tls")] +#[derive(Clone)] pub struct TlsConfig { pub certs: Vec, pub key: PrivateKey } #[cfg(not(feature = "tls"))] +#[derive(Clone)] pub struct TlsConfig; // Size limit configuration. We cache those used by Rocket internally but don't diff --git a/lib/src/error.rs b/lib/src/error.rs index 4cecd2be..0210d886 100644 --- a/lib/src/error.rs +++ b/lib/src/error.rs @@ -24,13 +24,15 @@ pub enum Error { /// /// In almost every instance, a launch error occurs because of an I/O error; /// this is represented by the `Io` variant. A launch error may also occur -/// because of ill-defined routes that lead to collisions; this is represented -/// by the `Collision` variant. The `Unknown` variant captures all other kinds -/// of launch errors. +/// because of ill-defined routes that lead to collisions or because a launch +/// fairing encounted an error; these are represented by the `Collision` and +/// `FailedFairing` variants, respectively. The `Unknown` variant captures all +/// other kinds of launch errors. #[derive(Debug)] pub enum LaunchErrorKind { Io(io::Error), Collision, + FailedFairing, Unknown(Box<::std::error::Error + Send + Sync>) } @@ -143,6 +145,7 @@ impl fmt::Display for LaunchErrorKind { match *self { LaunchErrorKind::Io(ref e) => write!(f, "I/O error: {}", e), LaunchErrorKind::Collision => write!(f, "route collisions detected"), + LaunchErrorKind::FailedFairing => write!(f, "a launch fairing failed"), LaunchErrorKind::Unknown(ref e) => write!(f, "unknown error: {}", e) } } @@ -171,6 +174,7 @@ impl ::std::error::Error for LaunchError { match *self.kind() { LaunchErrorKind::Io(_) => "an I/O error occured during launch", LaunchErrorKind::Collision => "route collisions were detected", + LaunchErrorKind::FailedFairing => "a launch fairing reported an error", LaunchErrorKind::Unknown(_) => "an unknown error occured during launch" } } @@ -191,6 +195,10 @@ impl Drop for LaunchError { error!("Rocket failed to launch due to routing collisions."); panic!("route collisions detected"); } + LaunchErrorKind::FailedFairing => { + error!("Rocket failed to launch due to a failing launch fairing."); + panic!("launch fairing failure"); + } LaunchErrorKind::Unknown(ref e) => { error!("Rocket failed to launch due to an unknown error."); panic!("{}", e); diff --git a/lib/src/ext.rs b/lib/src/ext.rs index 8813b741..394d0997 100644 --- a/lib/src/ext.rs +++ b/lib/src/ext.rs @@ -1,4 +1,5 @@ use std::io; +use smallvec::{Array, SmallVec}; pub trait ReadExt: io::Read { fn read_max(&mut self, mut buf: &mut [u8]) -> io::Result { @@ -17,3 +18,48 @@ pub trait ReadExt: io::Read { } impl ReadExt for T { } + +// TODO: It would be nice if we could somehow have one trait that could give us +// either SmallVec or Vec. +pub trait IntoCollection { + fn into_collection>(self) -> SmallVec; + fn mapped U, A: Array>(self, f: F) -> SmallVec; +} + +impl IntoCollection for T { + #[inline] + fn into_collection>(self) -> SmallVec { + let mut vec = SmallVec::new(); + vec.push(self); + vec + } + + #[inline(always)] + fn mapped U, A: Array>(self, mut f: F) -> SmallVec { + f(self).into_collection() + } +} + +impl IntoCollection for Vec { + #[inline(always)] + fn into_collection>(self) -> SmallVec { + SmallVec::from_vec(self) + } + + #[inline] + fn mapped U, A: Array>(self, mut f: F) -> SmallVec { + self.into_iter().map(|item| f(item)).collect() + } +} + +impl<'a, T: Clone> IntoCollection for &'a [T] { + #[inline(always)] + fn into_collection>(self) -> SmallVec { + self.iter().cloned().collect() + } + + #[inline] + fn mapped U, A: Array>(self, mut f: F) -> SmallVec { + self.iter().cloned().map(|item| f(item)).collect() + } +} diff --git a/lib/src/fairing/mod.rs b/lib/src/fairing/mod.rs new file mode 100644 index 00000000..e2069a65 --- /dev/null +++ b/lib/src/fairing/mod.rs @@ -0,0 +1,192 @@ +//! Fairings: structured interposition at 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 +//! "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. +//! +//! ## 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: +//! +//! ```rust +//! # use rocket::Fairing; +//! # let req_fairing = Fairing::Request(Box::new(|_, _| ())); +//! # let res_fairing = Fairing::Response(Box::new(|_, _| ())); +//! # #[allow(unused_variables)] +//! let rocket = rocket::ignite() +//! .attach(vec![req_fairing, res_fairing]); +//! ``` +//! +//! Once a fairing is attached, Rocket will execute it at the appropiate time, +//! which varies depending on the fairing type. + +use {Rocket, Request, Response, Data}; + +// 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 +// go this direction because I feel like request guards are the correct +// mechanism to use here. In other words, enabling this at the fairing level +// encourages implicit handling, a bad practice. Fairings can still, however, +// return a default `Response` if routing fails via a response fairing. For +// instance, to automatically handle preflight in CORS, a response fairing can +// check that the user didn't handle the `OPTIONS` request (404) and return an +// 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. +/// +/// The `Rocket` parameter is the `Rocket` instance being built. The launch +/// fairing can modify the `Rocket` instance arbitrarily. +/// +/// TODO: Document fully with examples before 0.3. +pub type LaunchFn = Box Result + Send + Sync + 'static>; +/// The type of a **request** fairing callback. +/// +/// The `&mut Request` parameter is the incoming request, and the `&Data` +/// parameter is the incoming data in the request. +/// +/// TODO: Document fully with examples before 0.3. +pub type RequestFn = Box; +/// The type of a **response** fairing callback. +/// +/// The `&Request` parameter is the request that was routed, and the `&mut +/// Response` parameter is the result response. +/// +/// TODO: Document fully with examples before 0.3. +pub type ResponseFn = Box; + +/// An enum representing the three fairing types: launch, request, and response. +/// +/// ## Fairing Types +/// +/// The three types of fairings, launch, request, and response, operate as +/// follows: +/// +/// * *Launch Fairings* +/// +/// 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. +/// +/// * *Request Fairings* +/// +/// 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 +/// 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. +/// +/// * *Response Fairings* +/// +/// 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. +/// +/// 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); + } + } + + pub fn pretty_print_counts(&self) { + use term_painter::ToStyle; + use term_painter::Color::White; + + 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())); + } + } +} diff --git a/lib/src/http/accept.rs b/lib/src/http/accept.rs index dbb811e6..577709d1 100644 --- a/lib/src/http/accept.rs +++ b/lib/src/http/accept.rs @@ -4,7 +4,8 @@ use std::fmt; use smallvec::SmallVec; -use http::{Header, IntoCollection, MediaType}; +use ext::IntoCollection; +use http::{Header, MediaType}; use http::parse::parse_accept; #[derive(Debug, Clone, PartialEq)] diff --git a/lib/src/http/content_type.rs b/lib/src/http/content_type.rs index 276dc2c0..ccc06791 100644 --- a/lib/src/http/content_type.rs +++ b/lib/src/http/content_type.rs @@ -3,7 +3,8 @@ use std::ops::Deref; use std::str::FromStr; use std::fmt; -use http::{IntoCollection, Header, MediaType}; +use ext::IntoCollection; +use http::{Header, MediaType}; use http::hyper::mime::Mime; /// Representation of HTTP Content-Types. diff --git a/lib/src/http/media_type.rs b/lib/src/http/media_type.rs index 081b330d..dd685de0 100644 --- a/lib/src/http/media_type.rs +++ b/lib/src/http/media_type.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use std::fmt; use std::hash::{Hash, Hasher}; -use http::IntoCollection; +use ext::IntoCollection; use http::uncased::{uncased_eq, UncasedStr}; use http::parse::{IndexedStr, parse_media_type}; diff --git a/lib/src/http/mod.rs b/lib/src/http/mod.rs index 1225a01f..faf34c4d 100644 --- a/lib/src/http/mod.rs +++ b/lib/src/http/mod.rs @@ -38,48 +38,3 @@ pub use self::raw_str::RawStr; pub use self::media_type::MediaType; pub use self::cookies::*; pub use self::session::*; - -use smallvec::{Array, SmallVec}; - -pub trait IntoCollection { - fn into_collection>(self) -> SmallVec; - fn mapped U, A: Array>(self, f: F) -> SmallVec; -} - -impl IntoCollection for T { - #[inline] - fn into_collection>(self) -> SmallVec { - let mut vec = SmallVec::new(); - vec.push(self); - vec - } - - #[inline(always)] - fn mapped U, A: Array>(self, mut f: F) -> SmallVec { - f(self).into_collection() - } -} - -impl IntoCollection for Vec { - #[inline(always)] - fn into_collection>(self) -> SmallVec { - SmallVec::from_vec(self) - } - - #[inline] - fn mapped U, A: Array>(self, mut f: F) -> SmallVec { - self.into_iter().map(|item| f(item)).collect() - } -} - -impl<'a, T: Clone> IntoCollection for &'a [T] { - #[inline(always)] - fn into_collection>(self) -> SmallVec { - self.iter().cloned().collect() - } - - #[inline] - fn mapped U, A: Array>(self, mut f: F) -> SmallVec { - self.iter().cloned().map(|item| f(item)).collect() - } -} diff --git a/lib/src/lib.rs b/lib/src/lib.rs index b2842639..318d1ad6 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -126,6 +126,7 @@ pub mod config; pub mod data; pub mod handler; pub mod error; +pub mod fairing; mod router; mod rocket; @@ -139,6 +140,7 @@ 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 b298578b..4940e665 100644 --- a/lib/src/rocket.rs +++ b/lib/src/rocket.rs @@ -3,6 +3,7 @@ use std::str::from_utf8_unchecked; use std::cmp::min; use std::net::SocketAddr; use std::io::{self, Write}; +use std::mem; use term_painter::Color::*; use term_painter::ToStyle; @@ -10,7 +11,7 @@ use state::Container; #[cfg(feature = "tls")] use hyper_rustls::TlsServer; use {logger, handler}; -use ext::ReadExt; +use ext::{ReadExt, IntoCollection}; use config::{self, Config, LoggedValue}; use request::{Request, FormItems}; use data::Data; @@ -19,6 +20,7 @@ use router::{Router, Route}; use catcher::{self, Catcher}; use outcome::Outcome; use error::{Error, LaunchError, LaunchErrorKind}; +use fairing::{Fairing, Fairings}; use http::{Method, Status, Header, Session}; use http::hyper::{self, header}; @@ -31,7 +33,8 @@ pub struct Rocket { router: Router, default_catchers: HashMap, catchers: HashMap, - state: Container + state: Container, + fairings: Fairings } #[doc(hidden)] @@ -69,6 +72,7 @@ impl hyper::Handler for Rocket { }; // Dispatch the request to get a response, then write that response out. + // let req = UnsafeCell::new(req); let response = self.dispatch(&mut req, data); self.issue_response(response, res) } @@ -105,11 +109,7 @@ macro_rules! serve { impl Rocket { #[inline] - fn issue_response(&self, mut response: Response, hyp_res: hyper::FreshResponse) { - // Add the 'rocket' server header, and write out the response. - // TODO: If removing Hyper, write out `Date` header too. - response.set_header(Header::new("Server", "Rocket")); - + fn issue_response(&self, response: Response, hyp_res: hyper::FreshResponse) { match self.write_response(response, hyp_res) { Ok(_) => info_!("{}", Green.paint("Response succeeded.")), Err(e) => error_!("Failed to write response: {:?}.", e) @@ -193,6 +193,8 @@ impl Rocket { let (min_len, max_len) = ("_method=get".len(), "_method=delete".len()); let is_form = req.content_type().map_or(false, |ct| ct.is_form()); if is_form && req.method() == Method::Post && data_len >= min_len { + // We're only using this for comparison and throwing it away + // afterwards, so it doesn't matter if we have invalid UTF8. let form = unsafe { from_utf8_unchecked(&data.peek()[..min(data_len, max_len)]) }; @@ -207,19 +209,22 @@ impl Rocket { } } + // TODO: Explain this `UnsafeCell` business at a macro level. #[inline] - pub(crate) fn dispatch<'s, 'r>(&'s self, request: &'r mut Request<'s>, data: Data) - -> Response<'r> { + pub(crate) fn dispatch<'s, 'r>(&'s self, + request: &'r mut Request<'s>, + data: Data) -> Response<'r> { info!("{}:", request); // Inform the request about all of the precomputed state. request.set_preset_state(&self.config.session_key(), &self.state); - // Do a bit of preprocessing before routing. + // Do a bit of preprocessing before routing; run the attached fairings. self.preprocess_request(request, &data); + self.fairings.handle_request(request, &data); // Route the request to get a response. - match self.route(request, data) { + let mut response = match self.route(request, data) { Outcome::Success(mut response) => { // A user's route responded! Set the regular cookies. for cookie in request.cookies().delta() { @@ -234,17 +239,16 @@ impl Rocket { response } Outcome::Forward(data) => { - // There was no matching route. // Rust thinks `request` is still borrowed here, but it's // obviously not (data has nothing to do with it), so we // convince it to give us another mutable reference. - // FIXME: Pay the cost to copy Request into UnsafeCell? Pay the - // cost to use RefCell? Move the call to `issue_response` here - // to move Request and move directly into an UnsafeCell? - let request: &'r mut Request = unsafe { - &mut *(request as *const Request as *mut Request) + // TODO: Use something that is well defined, like UnsafeCell. + // But that causes variance issues...so wait for NLL. + let request: &'r mut Request<'s> = unsafe { + (&mut *(request as *const _ as *mut _)) }; + // There was no matching route. if request.method() == Method::Head { info_!("Autohandling {} request.", White.paint("HEAD")); request.set_method(Method::Get); @@ -256,7 +260,14 @@ impl Rocket { } } Outcome::Failure(status) => self.handle_error(status, request), - } + }; + + // Add the 'rocket' server header to the response and run fairings. + // TODO: If removing Hyper, write out `Date` header too. + response.set_header(Header::new("Server", "Rocket")); + self.fairings.handle_response(request, &mut response); + + response } /// Tries to find a `Responder` for a given `request`. It does this by @@ -406,7 +417,8 @@ impl Rocket { router: Router::new(), default_catchers: catcher::defaults::get(), catchers: catcher::defaults::get(), - state: Container::new() + state: Container::new(), + fairings: Fairings::new() } } @@ -574,6 +586,40 @@ impl Rocket { self } + /// Attaches zero or more fairings 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 + /// + /// ```rust + /// # #![feature(plugin)] + /// # #![plugin(rocket_codegen)] + /// # extern crate rocket; + /// use rocket::{Rocket, Fairing}; + /// + /// fn launch_fairing(rocket: Rocket) -> Result { + /// 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(Fairing::Launch(Box::new(launch_fairing))) + /// .launch(); + /// # } + /// } + /// ``` + #[inline] + pub fn attach>(mut self, fairings: C) -> Self { + let fairings = fairings.into_collection::<[Fairing; 1]>().into_vec(); + self.fairings.attach_all(fairings); + self + } + /// 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. @@ -594,11 +640,14 @@ impl Rocket { /// rocket::ignite().launch(); /// # } /// ``` - pub fn launch(self) -> LaunchError { + pub fn launch(mut self) -> LaunchError { if self.router.has_collisions() { return LaunchError::from(LaunchErrorKind::Collision); } + info!("📡 {}:", Magenta.paint("Fairings")); + self.fairings.pretty_print_counts(); + let full_addr = format!("{}:{}", self.config.address, self.config.port); serve!(self, &full_addr, |server, proto| { let mut server = match server { @@ -606,11 +655,22 @@ impl Rocket { Err(e) => return LaunchError::from(e) }; + // Determine the port we actually binded to. let (addr, port) = match server.local_addr() { Ok(server_addr) => (&self.config.address, server_addr.port()), 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; + launch_info!("🚀 {} {}{}", White.paint("Rocket has launched from"), White.bold().paint(proto), @@ -630,4 +690,10 @@ impl Rocket { pub fn routes<'a>(&'a self) -> impl Iterator + 'a { self.router.routes() } + + /// Retrieve the configuration. + #[inline(always)] + pub fn config(&self) -> &Config { + self.config + } }