mirror of https://github.com/rwf2/Rocket.git
Add 'Request::route' method to get active 'Route'.
This commit also adds the `base` field to `Route` which allows the base mount point to be retrieved. Finally, this commits adds an implementation of `FromRequest` for `Route` which returns the active route if one is available or forwards otherwise. This commit is a breaking change: it makes `Request` and `MockRequest` invariant over the lifetime `'r`. While this shouldn't affect most applications, it may affect some. Resolves #108.
This commit is contained in:
parent
a4292ba666
commit
6a9421935e
|
@ -7,10 +7,10 @@ use rocket_contrib::Template;
|
||||||
|
|
||||||
const TEMPLATE_ROOT: &'static str = "templates/";
|
const TEMPLATE_ROOT: &'static str = "templates/";
|
||||||
|
|
||||||
macro_rules! run_test {
|
macro_rules! dispatch {
|
||||||
($req:expr, $test_fn:expr) => ({
|
($method:expr, $path:expr, $test_fn:expr) => ({
|
||||||
let rocket = rocket();
|
let rocket = rocket();
|
||||||
let mut req = $req;
|
let mut req = MockRequest::new($method, $path);
|
||||||
$test_fn(req.dispatch_with(&rocket));
|
$test_fn(req.dispatch_with(&rocket));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,7 @@ macro_rules! run_test {
|
||||||
fn test_root() {
|
fn test_root() {
|
||||||
// Check that the redirect works.
|
// Check that the redirect works.
|
||||||
for method in &[Get, Head] {
|
for method in &[Get, Head] {
|
||||||
let req = MockRequest::new(*method, "/");
|
dispatch!(*method, "/", |mut response: Response| {
|
||||||
run_test!(req, |mut response: Response| {
|
|
||||||
assert_eq!(response.status(), Status::SeeOther);
|
assert_eq!(response.status(), Status::SeeOther);
|
||||||
assert!(response.body().is_none());
|
assert!(response.body().is_none());
|
||||||
|
|
||||||
|
@ -31,8 +30,7 @@ fn test_root() {
|
||||||
|
|
||||||
// Check that other request methods are not accepted (and instead caught).
|
// Check that other request methods are not accepted (and instead caught).
|
||||||
for method in &[Post, Put, Delete, Options, Trace, Connect, Patch] {
|
for method in &[Post, Put, Delete, Options, Trace, Connect, Patch] {
|
||||||
let req = MockRequest::new(*method, "/");
|
dispatch!(*method, "/", |mut response: Response| {
|
||||||
run_test!(req, |mut response: Response| {
|
|
||||||
let mut map = ::std::collections::HashMap::new();
|
let mut map = ::std::collections::HashMap::new();
|
||||||
map.insert("path", "/");
|
map.insert("path", "/");
|
||||||
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
|
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
|
||||||
|
@ -46,16 +44,14 @@ fn test_root() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_name() {
|
fn test_name() {
|
||||||
// Check that the /hello/<name> route works.
|
// Check that the /hello/<name> route works.
|
||||||
let req = MockRequest::new(Get, "/hello/Jack");
|
dispatch!(Get, "/hello/Jack", |mut response: Response| {
|
||||||
run_test!(req, |mut response: Response| {
|
|
||||||
assert_eq!(response.status(), Status::Ok);
|
|
||||||
|
|
||||||
let context = super::TemplateContext {
|
let context = super::TemplateContext {
|
||||||
name: "Jack".to_string(),
|
name: "Jack".to_string(),
|
||||||
items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect()
|
items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect()
|
||||||
};
|
};
|
||||||
|
|
||||||
let expected = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
|
let expected = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
|
||||||
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.body_string(), Some(expected));
|
assert_eq!(response.body_string(), Some(expected));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -63,14 +59,12 @@ fn test_name() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_404() {
|
fn test_404() {
|
||||||
// Check that the error catcher works.
|
// Check that the error catcher works.
|
||||||
let req = MockRequest::new(Get, "/hello/");
|
dispatch!(Get, "/hello/", |mut response: Response| {
|
||||||
run_test!(req, |mut response: Response| {
|
|
||||||
assert_eq!(response.status(), Status::NotFound);
|
|
||||||
|
|
||||||
let mut map = ::std::collections::HashMap::new();
|
let mut map = ::std::collections::HashMap::new();
|
||||||
map.insert("path", "/hello/");
|
map.insert("path", "/hello/");
|
||||||
|
|
||||||
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
|
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
|
||||||
|
assert_eq!(response.status(), Status::NotFound);
|
||||||
assert_eq!(response.body_string(), Some(expected));
|
assert_eq!(response.body_string(), Some(expected));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use router::Route;
|
||||||
use request::Request;
|
use request::Request;
|
||||||
use outcome::{self, IntoOutcome};
|
use outcome::{self, IntoOutcome};
|
||||||
use outcome::Outcome::*;
|
use outcome::Outcome::*;
|
||||||
|
@ -74,6 +75,13 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
/// Rocket implements `FromRequest` for several built-in types. Their behavior
|
/// Rocket implements `FromRequest` for several built-in types. Their behavior
|
||||||
/// is documented here.
|
/// is documented here.
|
||||||
///
|
///
|
||||||
|
/// * **Method**
|
||||||
|
///
|
||||||
|
/// Extracts the [Method](/rocket/http/enum.Method.html) from the incoming
|
||||||
|
/// request.
|
||||||
|
///
|
||||||
|
/// _This implementation always returns successfully._
|
||||||
|
///
|
||||||
/// * **&URI**
|
/// * **&URI**
|
||||||
///
|
///
|
||||||
/// Extracts the [URI](/rocket/http/uri/struct.URI.html) from the incoming
|
/// Extracts the [URI](/rocket/http/uri/struct.URI.html) from the incoming
|
||||||
|
@ -81,12 +89,14 @@ impl<S, E> IntoOutcome<S, (Status, E), ()> for Result<S, E> {
|
||||||
///
|
///
|
||||||
/// _This implementation always returns successfully._
|
/// _This implementation always returns successfully._
|
||||||
///
|
///
|
||||||
/// * **Method**
|
/// * **&Route**
|
||||||
///
|
///
|
||||||
/// Extracts the [Method](/rocket/http/enum.Method.html) from the incoming
|
/// Extracts the [Route](/rocket/struct.Route.html) from the request if one
|
||||||
/// request.
|
/// is available. If a route is not available, the request is forwarded.
|
||||||
///
|
///
|
||||||
/// _This implementation always returns successfully._
|
/// For information of when a route is avaiable, see the
|
||||||
|
/// [`Request::route`](/rocket/struct.Request.html#method.route)
|
||||||
|
/// documentation.
|
||||||
///
|
///
|
||||||
/// * **Cookies**
|
/// * **Cookies**
|
||||||
///
|
///
|
||||||
|
@ -191,6 +201,14 @@ pub trait FromRequest<'a, 'r>: Sized {
|
||||||
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error>;
|
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'r> FromRequest<'a, 'r> for Method {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||||
|
Success(request.method())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for &'a URI<'a> {
|
impl<'a, 'r> FromRequest<'a, 'r> for &'a URI<'a> {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
|
@ -199,11 +217,14 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'a URI<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for Method {
|
impl<'a, 'r> FromRequest<'a, 'r> for &'r Route {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
fn from_request(request: &'a Request<'r>) -> Outcome<Self, Self::Error> {
|
||||||
Success(request.method())
|
match request.route() {
|
||||||
|
Some(route) => Success(route),
|
||||||
|
None => Forward(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::cell::RefCell;
|
use std::cell::{Cell, RefCell};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
@ -28,6 +28,7 @@ struct PresetState<'r> {
|
||||||
struct RequestState<'r> {
|
struct RequestState<'r> {
|
||||||
preset: Option<PresetState<'r>>,
|
preset: Option<PresetState<'r>>,
|
||||||
params: RefCell<Vec<(usize, usize)>>,
|
params: RefCell<Vec<(usize, usize)>>,
|
||||||
|
route: Cell<Option<&'r Route>>,
|
||||||
cookies: RefCell<CookieJar>,
|
cookies: RefCell<CookieJar>,
|
||||||
session: RefCell<CookieJar>,
|
session: RefCell<CookieJar>,
|
||||||
accept: Storage<Option<Accept>>,
|
accept: Storage<Option<Accept>>,
|
||||||
|
@ -64,7 +65,7 @@ impl<'r> Request<'r> {
|
||||||
/// let request = Request::new(Method::Get, "/uri");
|
/// let request = Request::new(Method::Get, "/uri");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn new<U: Into<URI<'r>>>(method: Method, uri: U) -> Request<'r> {
|
pub fn new<'s: 'r, U: Into<URI<'s>>>(method: Method, uri: U) -> Request<'r> {
|
||||||
Request {
|
Request {
|
||||||
method: method,
|
method: method,
|
||||||
uri: uri.into(),
|
uri: uri.into(),
|
||||||
|
@ -72,6 +73,7 @@ impl<'r> Request<'r> {
|
||||||
remote: None,
|
remote: None,
|
||||||
state: RequestState {
|
state: RequestState {
|
||||||
preset: None,
|
preset: None,
|
||||||
|
route: Cell::new(None),
|
||||||
params: RefCell::new(Vec::new()),
|
params: RefCell::new(Vec::new()),
|
||||||
cookies: RefCell::new(CookieJar::new()),
|
cookies: RefCell::new(CookieJar::new()),
|
||||||
session: RefCell::new(CookieJar::new()),
|
session: RefCell::new(CookieJar::new()),
|
||||||
|
@ -358,6 +360,18 @@ impl<'r> Request<'r> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the limits.
|
||||||
|
pub fn limits(&self) -> &'r Limits {
|
||||||
|
&self.preset().config.limits
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current route, if any.
|
||||||
|
///
|
||||||
|
/// No route will be avaiable before routing. So not during request fairing.
|
||||||
|
pub fn route(&self) -> Option<&'r Route> {
|
||||||
|
self.state.route.get()
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieves and parses into `T` the 0-indexed `n`th dynamic parameter from
|
/// Retrieves and parses into `T` the 0-indexed `n`th dynamic parameter from
|
||||||
/// the request. Returns `Error::NoKey` if `n` is greater than the number of
|
/// the request. Returns `Error::NoKey` if `n` is greater than the number of
|
||||||
/// params. Returns `Error::BadParse` if the parameter type `T` can't be
|
/// params. Returns `Error::BadParse` if the parameter type `T` can't be
|
||||||
|
@ -450,11 +464,6 @@ impl<'r> Request<'r> {
|
||||||
Some(Segments(&path[i..j]))
|
Some(Segments(&path[i..j]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the limits.
|
|
||||||
pub fn limits(&self) -> &'r Limits {
|
|
||||||
&self.preset().config.limits
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn preset(&self) -> &PresetState<'r> {
|
fn preset(&self) -> &PresetState<'r> {
|
||||||
match self.state.preset {
|
match self.state.preset {
|
||||||
|
@ -471,7 +480,8 @@ impl<'r> Request<'r> {
|
||||||
/// use may result in out of bounds indexing.
|
/// use may result in out of bounds indexing.
|
||||||
/// TODO: Figure out the mount path from here.
|
/// TODO: Figure out the mount path from here.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn set_params(&self, route: &Route) {
|
pub(crate) fn set_route(&self, route: &'r Route) {
|
||||||
|
self.state.route.set(Some(route));
|
||||||
*self.state.params.borrow_mut() = route.get_param_indexes(self.uri());
|
*self.state.params.borrow_mut() = route.get_param_indexes(self.uri());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -276,15 +276,29 @@ impl Rocket {
|
||||||
/// until one of the handlers returns success or failure, or there are no
|
/// until one of the handlers returns success or failure, or there are no
|
||||||
/// additional routes to try (forward). The corresponding outcome for each
|
/// additional routes to try (forward). The corresponding outcome for each
|
||||||
/// condition is returned.
|
/// condition is returned.
|
||||||
|
//
|
||||||
|
// FIXME: We _should_ be able to take an `&mut` here and mutate the request
|
||||||
|
// at any pointer _before_ we pass it to a handler as long as we drop the
|
||||||
|
// outcome. That should be safe. Since no mutable borrow can be held
|
||||||
|
// (ensuring `handler` takes an immutable borrow), any caller to `route`
|
||||||
|
// should be able to supply an `&mut` and retain an `&` after the call.
|
||||||
|
//
|
||||||
|
// Why are we even thinking about this? Because we want to set the route
|
||||||
|
// that the current request is trying to respond to so guards/handlers can
|
||||||
|
// get information about it. But we can't use a RefCell<&'r _> since that
|
||||||
|
// cuases lifetime issues since RefCell contains an UnsafeCell
|
||||||
|
// (weirdness...). IDEA: Have an `AtomicRef` type that does a pointer swap
|
||||||
|
// atomically when the ptr's size can be used for atomics.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn route<'r>(&self, request: &'r Request, mut data: Data)
|
pub(crate) fn route<'s, 'r>(&'s self,
|
||||||
-> handler::Outcome<'r> {
|
request: &'r Request<'s>,
|
||||||
|
mut data: Data) -> handler::Outcome<'r> {
|
||||||
// Go through the list of matching routes until we fail or succeed.
|
// Go through the list of matching routes until we fail or succeed.
|
||||||
let matches = self.router.route(request);
|
let matches = self.router.route(request);
|
||||||
for route in matches {
|
for route in matches {
|
||||||
// Retrieve and set the requests parameters.
|
// Retrieve and set the requests parameters.
|
||||||
info_!("Matched: {}", route);
|
info_!("Matched: {}", route);
|
||||||
request.set_params(route);
|
request.set_route(route);
|
||||||
|
|
||||||
// Dispatch the request to the handler.
|
// Dispatch the request to the handler.
|
||||||
let outcome = (route.handler)(request, data);
|
let outcome = (route.handler)(request, data);
|
||||||
|
@ -486,6 +500,7 @@ impl Rocket {
|
||||||
|
|
||||||
for mut route in routes {
|
for mut route in routes {
|
||||||
let path = format!("{}/{}", base, route.path);
|
let path = format!("{}/{}", base, route.path);
|
||||||
|
route.set_base(base);
|
||||||
route.set_path(path);
|
route.set_path(path);
|
||||||
|
|
||||||
info_!("{}", route);
|
info_!("{}", route);
|
||||||
|
|
|
@ -15,7 +15,10 @@ pub struct Route {
|
||||||
pub method: Method,
|
pub method: Method,
|
||||||
/// A function that should be called when the route matches.
|
/// A function that should be called when the route matches.
|
||||||
pub handler: Handler,
|
pub handler: Handler,
|
||||||
/// The path (in Rocket format) that should be matched against.
|
/// The base mount point of this `Route`.
|
||||||
|
pub base: URI<'static>,
|
||||||
|
/// The path (in Rocket format) that should be matched against. This path
|
||||||
|
/// already includes the base mount point.
|
||||||
pub path: URI<'static>,
|
pub path: URI<'static>,
|
||||||
/// The rank of this route. Lower ranks have higher priorities.
|
/// The rank of this route. Lower ranks have higher priorities.
|
||||||
pub rank: isize,
|
pub rank: isize,
|
||||||
|
@ -48,6 +51,7 @@ impl Route {
|
||||||
method: m,
|
method: m,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
rank: default_rank(&uri),
|
rank: default_rank(&uri),
|
||||||
|
base: URI::from("/"),
|
||||||
path: uri,
|
path: uri,
|
||||||
format: None,
|
format: None,
|
||||||
}
|
}
|
||||||
|
@ -59,13 +63,20 @@ impl Route {
|
||||||
{
|
{
|
||||||
Route {
|
Route {
|
||||||
method: m,
|
method: m,
|
||||||
path: URI::from(path.as_ref().to_string()),
|
|
||||||
handler: handler,
|
handler: handler,
|
||||||
|
base: URI::from("/"),
|
||||||
|
path: URI::from(path.as_ref().to_string()),
|
||||||
rank: rank,
|
rank: rank,
|
||||||
format: None,
|
format: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the base mount point of the route. Does not update the rank or any
|
||||||
|
/// other parameters.
|
||||||
|
pub fn set_base<S>(&mut self, path: S) where S: AsRef<str> {
|
||||||
|
self.base = URI::from(path.as_ref().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the path of the route. Does not update the rank or any other
|
/// Sets the path of the route. Does not update the rank or any other
|
||||||
/// parameters.
|
/// parameters.
|
||||||
pub fn set_path<S>(&mut self, path: S) where S: AsRef<str> {
|
pub fn set_path<S>(&mut self, path: S) where S: AsRef<str> {
|
||||||
|
@ -104,6 +115,7 @@ impl Clone for Route {
|
||||||
method: self.method,
|
method: self.method,
|
||||||
handler: self.handler,
|
handler: self.handler,
|
||||||
rank: self.rank,
|
rank: self.rank,
|
||||||
|
base: self.base.clone(),
|
||||||
path: self.path.clone(),
|
path: self.path.clone(),
|
||||||
format: self.format.clone(),
|
format: self.format.clone(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#![feature(plugin, custom_derive)]
|
||||||
|
#![plugin(rocket_codegen)]
|
||||||
|
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use rocket::Route;
|
||||||
|
|
||||||
|
#[get("/<path..>")]
|
||||||
|
fn files(route: &Route, path: PathBuf) -> String {
|
||||||
|
format!("{}/{}", route.base.path(), path.to_string_lossy())
|
||||||
|
}
|
||||||
|
|
||||||
|
mod route_guard_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use rocket::Rocket;
|
||||||
|
use rocket::testing::MockRequest;
|
||||||
|
use rocket::http::Method::*;
|
||||||
|
|
||||||
|
fn assert_path(rocket: &Rocket, path: &str) {
|
||||||
|
let mut req = MockRequest::new(Get, path);
|
||||||
|
let mut res = req.dispatch_with(&rocket);
|
||||||
|
assert_eq!(res.body_string(), Some(path.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_mount_path() {
|
||||||
|
let rocket = rocket::ignite()
|
||||||
|
.mount("/first", routes![files])
|
||||||
|
.mount("/second", routes![files]);
|
||||||
|
|
||||||
|
assert_path(&rocket, "/first/some/path");
|
||||||
|
assert_path(&rocket, "/second/some/path");
|
||||||
|
assert_path(&rocket, "/first/second/b/c");
|
||||||
|
assert_path(&rocket, "/second/a/b/c");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue