Initial implementation of fairings: structured middleware for Rocket.

Closes #55.
This commit is contained in:
Sergio Benitez 2017-04-20 13:43:01 -07:00
parent f2d054c4a2
commit ac0c78a0cd
16 changed files with 408 additions and 72 deletions

View File

@ -38,4 +38,5 @@ members = [
"examples/session",
"examples/raw_sqlite",
"examples/hello_tls",
"examples/fairings",
]

View File

@ -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"] }

View File

@ -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();
}

View File

@ -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()));
}

View File

@ -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]

View File

@ -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,

View File

@ -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<Certificate>,
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

View File

@ -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);

View File

@ -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<usize> {
@ -17,3 +18,48 @@ pub trait ReadExt: io::Read {
}
impl<T: io::Read> 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<T> {
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A>;
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, f: F) -> SmallVec<A>;
}
impl<T> IntoCollection<T> for T {
#[inline]
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A> {
let mut vec = SmallVec::new();
vec.push(self);
vec
}
#[inline(always)]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, mut f: F) -> SmallVec<A> {
f(self).into_collection()
}
}
impl<T> IntoCollection<T> for Vec<T> {
#[inline(always)]
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A> {
SmallVec::from_vec(self)
}
#[inline]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, mut f: F) -> SmallVec<A> {
self.into_iter().map(|item| f(item)).collect()
}
}
impl<'a, T: Clone> IntoCollection<T> for &'a [T] {
#[inline(always)]
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A> {
self.iter().cloned().collect()
}
#[inline]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, mut f: F) -> SmallVec<A> {
self.iter().cloned().map(|item| f(item)).collect()
}
}

192
lib/src/fairing/mod.rs Normal file
View File

@ -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<Fn(Rocket) -> Result<Rocket, Rocket> + 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<Fn(&mut Request, &Data) + Send + Sync + 'static>;
/// 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<Fn(&Request, &mut Response) + Send + Sync + 'static>;
/// 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<LaunchFn>,
pub request: Vec<RequestFn>,
pub response: Vec<ResponseFn>,
}
impl Fairings {
#[inline]
pub fn new() -> Fairings {
Fairings::default()
}
#[inline(always)]
pub fn attach_all(&mut self, fairings: Vec<Fairing>) {
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<Rocket> {
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()));
}
}
}

View File

@ -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)]

View File

@ -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.

View File

@ -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};

View File

@ -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<T> {
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A>;
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, f: F) -> SmallVec<A>;
}
impl<T> IntoCollection<T> for T {
#[inline]
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A> {
let mut vec = SmallVec::new();
vec.push(self);
vec
}
#[inline(always)]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, mut f: F) -> SmallVec<A> {
f(self).into_collection()
}
}
impl<T> IntoCollection<T> for Vec<T> {
#[inline(always)]
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A> {
SmallVec::from_vec(self)
}
#[inline]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, mut f: F) -> SmallVec<A> {
self.into_iter().map(|item| f(item)).collect()
}
}
impl<'a, T: Clone> IntoCollection<T> for &'a [T] {
#[inline(always)]
fn into_collection<A: Array<Item=T>>(self) -> SmallVec<A> {
self.iter().cloned().collect()
}
#[inline]
fn mapped<U, F: FnMut(T) -> U, A: Array<Item=U>>(self, mut f: F) -> SmallVec<A> {
self.iter().cloned().map(|item| f(item)).collect()
}
}

View File

@ -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};

View File

@ -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<u16, Catcher>,
catchers: HashMap<u16, Catcher>,
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>`, `&[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<Rocket, Rocket> {
/// println!("Rocket is about to launch! You just see...");
/// Ok(rocket)
/// }
///
/// fn main() {
/// # if false { // We don't actually want to launch the server in an example.
/// rocket::ignite()
/// .attach(Fairing::Launch(Box::new(launch_fairing)))
/// .launch();
/// # }
/// }
/// ```
#[inline]
pub fn attach<C: IntoCollection<Fairing>>(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<Item=&'a Route> + 'a {
self.router.routes()
}
/// Retrieve the configuration.
#[inline(always)]
pub fn config(&self) -> &Config {
self.config
}
}