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/";
|
||||
|
||||
macro_rules! run_test {
|
||||
($req:expr, $test_fn:expr) => ({
|
||||
macro_rules! dispatch {
|
||||
($method:expr, $path:expr, $test_fn:expr) => ({
|
||||
let rocket = rocket();
|
||||
let mut req = $req;
|
||||
let mut req = MockRequest::new($method, $path);
|
||||
$test_fn(req.dispatch_with(&rocket));
|
||||
})
|
||||
}
|
||||
|
@ -19,8 +19,7 @@ macro_rules! run_test {
|
|||
fn test_root() {
|
||||
// Check that the redirect works.
|
||||
for method in &[Get, Head] {
|
||||
let req = MockRequest::new(*method, "/");
|
||||
run_test!(req, |mut response: Response| {
|
||||
dispatch!(*method, "/", |mut response: Response| {
|
||||
assert_eq!(response.status(), Status::SeeOther);
|
||||
assert!(response.body().is_none());
|
||||
|
||||
|
@ -31,8 +30,7 @@ fn test_root() {
|
|||
|
||||
// Check that other request methods are not accepted (and instead caught).
|
||||
for method in &[Post, Put, Delete, Options, Trace, Connect, Patch] {
|
||||
let req = MockRequest::new(*method, "/");
|
||||
run_test!(req, |mut response: Response| {
|
||||
dispatch!(*method, "/", |mut response: Response| {
|
||||
let mut map = ::std::collections::HashMap::new();
|
||||
map.insert("path", "/");
|
||||
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
|
||||
|
@ -46,16 +44,14 @@ fn test_root() {
|
|||
#[test]
|
||||
fn test_name() {
|
||||
// Check that the /hello/<name> route works.
|
||||
let req = MockRequest::new(Get, "/hello/Jack");
|
||||
run_test!(req, |mut response: Response| {
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
dispatch!(Get, "/hello/Jack", |mut response: Response| {
|
||||
let context = super::TemplateContext {
|
||||
name: "Jack".to_string(),
|
||||
items: vec!["One", "Two", "Three"].iter().map(|s| s.to_string()).collect()
|
||||
};
|
||||
|
||||
let expected = Template::show(TEMPLATE_ROOT, "index", &context).unwrap();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.body_string(), Some(expected));
|
||||
});
|
||||
}
|
||||
|
@ -63,14 +59,12 @@ fn test_name() {
|
|||
#[test]
|
||||
fn test_404() {
|
||||
// Check that the error catcher works.
|
||||
let req = MockRequest::new(Get, "/hello/");
|
||||
run_test!(req, |mut response: Response| {
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
|
||||
dispatch!(Get, "/hello/", |mut response: Response| {
|
||||
let mut map = ::std::collections::HashMap::new();
|
||||
map.insert("path", "/hello/");
|
||||
|
||||
let expected = Template::show(TEMPLATE_ROOT, "error/404", &map).unwrap();
|
||||
assert_eq!(response.status(), Status::NotFound);
|
||||
assert_eq!(response.body_string(), Some(expected));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::fmt::Debug;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use router::Route;
|
||||
use request::Request;
|
||||
use outcome::{self, IntoOutcome};
|
||||
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
|
||||
/// is documented here.
|
||||
///
|
||||
/// * **Method**
|
||||
///
|
||||
/// Extracts the [Method](/rocket/http/enum.Method.html) from the incoming
|
||||
/// request.
|
||||
///
|
||||
/// _This implementation always returns successfully._
|
||||
///
|
||||
/// * **&URI**
|
||||
///
|
||||
/// 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._
|
||||
///
|
||||
/// * **Method**
|
||||
/// * **&Route**
|
||||
///
|
||||
/// Extracts the [Method](/rocket/http/enum.Method.html) from the incoming
|
||||
/// request.
|
||||
/// Extracts the [Route](/rocket/struct.Route.html) from the request if one
|
||||
/// 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**
|
||||
///
|
||||
|
@ -191,6 +201,14 @@ pub trait FromRequest<'a, 'r>: Sized {
|
|||
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> {
|
||||
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 = ();
|
||||
|
||||
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::fmt;
|
||||
use std::str;
|
||||
|
@ -28,6 +28,7 @@ struct PresetState<'r> {
|
|||
struct RequestState<'r> {
|
||||
preset: Option<PresetState<'r>>,
|
||||
params: RefCell<Vec<(usize, usize)>>,
|
||||
route: Cell<Option<&'r Route>>,
|
||||
cookies: RefCell<CookieJar>,
|
||||
session: RefCell<CookieJar>,
|
||||
accept: Storage<Option<Accept>>,
|
||||
|
@ -64,7 +65,7 @@ impl<'r> Request<'r> {
|
|||
/// let request = Request::new(Method::Get, "/uri");
|
||||
/// ```
|
||||
#[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 {
|
||||
method: method,
|
||||
uri: uri.into(),
|
||||
|
@ -72,6 +73,7 @@ impl<'r> Request<'r> {
|
|||
remote: None,
|
||||
state: RequestState {
|
||||
preset: None,
|
||||
route: Cell::new(None),
|
||||
params: RefCell::new(Vec::new()),
|
||||
cookies: 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
|
||||
/// 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
|
||||
|
@ -450,11 +464,6 @@ impl<'r> Request<'r> {
|
|||
Some(Segments(&path[i..j]))
|
||||
}
|
||||
|
||||
/// Get the limits.
|
||||
pub fn limits(&self) -> &'r Limits {
|
||||
&self.preset().config.limits
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn preset(&self) -> &PresetState<'r> {
|
||||
match self.state.preset {
|
||||
|
@ -471,7 +480,8 @@ impl<'r> Request<'r> {
|
|||
/// use may result in out of bounds indexing.
|
||||
/// TODO: Figure out the mount path from here.
|
||||
#[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());
|
||||
}
|
||||
|
||||
|
|
|
@ -276,15 +276,29 @@ impl Rocket {
|
|||
/// until one of the handlers returns success or failure, or there are no
|
||||
/// additional routes to try (forward). The corresponding outcome for each
|
||||
/// 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]
|
||||
pub(crate) fn route<'r>(&self, request: &'r Request, mut data: Data)
|
||||
-> handler::Outcome<'r> {
|
||||
pub(crate) fn route<'s, 'r>(&'s self,
|
||||
request: &'r Request<'s>,
|
||||
mut data: Data) -> handler::Outcome<'r> {
|
||||
// Go through the list of matching routes until we fail or succeed.
|
||||
let matches = self.router.route(request);
|
||||
for route in matches {
|
||||
// Retrieve and set the requests parameters.
|
||||
info_!("Matched: {}", route);
|
||||
request.set_params(route);
|
||||
request.set_route(route);
|
||||
|
||||
// Dispatch the request to the handler.
|
||||
let outcome = (route.handler)(request, data);
|
||||
|
@ -486,6 +500,7 @@ impl Rocket {
|
|||
|
||||
for mut route in routes {
|
||||
let path = format!("{}/{}", base, route.path);
|
||||
route.set_base(base);
|
||||
route.set_path(path);
|
||||
|
||||
info_!("{}", route);
|
||||
|
|
|
@ -15,7 +15,10 @@ pub struct Route {
|
|||
pub method: Method,
|
||||
/// A function that should be called when the route matches.
|
||||
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>,
|
||||
/// The rank of this route. Lower ranks have higher priorities.
|
||||
pub rank: isize,
|
||||
|
@ -48,6 +51,7 @@ impl Route {
|
|||
method: m,
|
||||
handler: handler,
|
||||
rank: default_rank(&uri),
|
||||
base: URI::from("/"),
|
||||
path: uri,
|
||||
format: None,
|
||||
}
|
||||
|
@ -59,13 +63,20 @@ impl Route {
|
|||
{
|
||||
Route {
|
||||
method: m,
|
||||
path: URI::from(path.as_ref().to_string()),
|
||||
handler: handler,
|
||||
base: URI::from("/"),
|
||||
path: URI::from(path.as_ref().to_string()),
|
||||
rank: rank,
|
||||
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
|
||||
/// parameters.
|
||||
pub fn set_path<S>(&mut self, path: S) where S: AsRef<str> {
|
||||
|
@ -104,6 +115,7 @@ impl Clone for Route {
|
|||
method: self.method,
|
||||
handler: self.handler,
|
||||
rank: self.rank,
|
||||
base: self.base.clone(),
|
||||
path: self.path.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