1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-30 18:34:36 +01:00

HttpRequest::resource() returns current matched resource

This commit is contained in:
Nikolay Kim 2018-04-01 17:37:22 -07:00
parent b2e771df2c
commit 17c27ef42d
11 changed files with 191 additions and 106 deletions

View File

@ -9,6 +9,8 @@
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()` * Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
* Add `HttpRequest::resource()`, returns current matched resource
* Router cannot parse Non-ASCII characters in URL #137 * Router cannot parse Non-ASCII characters in URL #137
* Fix long client urls #129 * Fix long client urls #129

View File

@ -68,6 +68,7 @@ smallvec = "0.6"
time = "0.1" time = "0.1"
encoding = "0.2" encoding = "0.2"
language-tags = "0.2" language-tags = "0.2"
lazy_static = "1.0"
url = { version="1.7", features=["query_encoding"] } url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] } cookie = { version="0.10", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true } brotli2 = { version="^0.3.2", optional = true }

View File

@ -34,16 +34,14 @@ use db::{CreateUser, DbExecutor};
/// State with DbExecutor address /// State with DbExecutor address
struct State { struct App {
db: Addr<Syn, DbExecutor>, db: Addr<Syn, DbExecutor>,
} }
/// Async request handler /// Async request handler
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> { fn index(name: Path<(String,)>, state: State<App>) -> FutureResponse<HttpResponse> {
let name = &req.match_info()["name"];
// send async `CreateUser` message to a `DbExecutor` // send async `CreateUser` message to a `DbExecutor`
req.state().db.send(CreateUser{name: name.to_owned()}) state.db.send(CreateUser{name: name.into_inner()})
.from_err() .from_err()
.and_then(|res| { .and_then(|res| {
match res { match res {
@ -72,7 +70,7 @@ fn main() {
App::with_state(State{db: addr.clone()}) App::with_state(State{db: addr.clone()})
// enable logger // enable logger
.middleware(middleware::Logger::default()) .middleware(middleware::Logger::default())
.resource("/{name}", |r| r.method(http::Method::GET).a(index))}) .resource("/{name}", |r| r.method(http::Method::GET).with2(index))})
.bind("127.0.0.1:8080").unwrap() .bind("127.0.0.1:8080").unwrap()
.start(); .start();

View File

@ -4,8 +4,8 @@ use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
use handler::Reply; use handler::Reply;
use router::{Router, Pattern}; use router::{Router, Resource};
use resource::Resource; use resource::{ResourceHandler};
use header::ContentEncoding; use header::ContentEncoding;
use handler::{Handler, RouteHandler, WrapHandler}; use handler::{Handler, RouteHandler, WrapHandler};
use httprequest::HttpRequest; use httprequest::HttpRequest;
@ -27,10 +27,10 @@ pub struct HttpApplication<S=()> {
pub(crate) struct Inner<S> { pub(crate) struct Inner<S> {
prefix: usize, prefix: usize,
default: Resource<S>, default: ResourceHandler<S>,
encoding: ContentEncoding, encoding: ContentEncoding,
router: Router, router: Router,
resources: Vec<Resource<S>>, resources: Vec<ResourceHandler<S>>,
handlers: Vec<(String, Box<RouteHandler<S>>)>, handlers: Vec<(String, Box<RouteHandler<S>>)>,
} }
@ -103,10 +103,10 @@ struct ApplicationParts<S> {
state: S, state: S,
prefix: String, prefix: String,
settings: ServerSettings, settings: ServerSettings,
default: Resource<S>, default: ResourceHandler<S>,
resources: Vec<(Pattern, Option<Resource<S>>)>, resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
handlers: Vec<(String, Box<RouteHandler<S>>)>, handlers: Vec<(String, Box<RouteHandler<S>>)>,
external: HashMap<String, Pattern>, external: HashMap<String, Resource>,
encoding: ContentEncoding, encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>, middlewares: Vec<Box<Middleware<S>>>,
} }
@ -126,7 +126,7 @@ impl App<()> {
state: (), state: (),
prefix: "/".to_owned(), prefix: "/".to_owned(),
settings: ServerSettings::default(), settings: ServerSettings::default(),
default: Resource::default_not_found(), default: ResourceHandler::default_not_found(),
resources: Vec::new(), resources: Vec::new(),
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
@ -156,7 +156,7 @@ impl<S> App<S> where S: 'static {
state, state,
prefix: "/".to_owned(), prefix: "/".to_owned(),
settings: ServerSettings::default(), settings: ServerSettings::default(),
default: Resource::default_not_found(), default: ResourceHandler::default_not_found(),
resources: Vec::new(), resources: Vec::new(),
handlers: Vec::new(), handlers: Vec::new(),
external: HashMap::new(), external: HashMap::new(),
@ -236,16 +236,16 @@ impl<S> App<S> where S: 'static {
/// } /// }
/// ``` /// ```
pub fn resource<F>(mut self, path: &str, f: F) -> App<S> pub fn resource<F>(mut self, path: &str, f: F) -> App<S>
where F: FnOnce(&mut Resource<S>) + 'static where F: FnOnce(&mut ResourceHandler<S>) + 'static
{ {
{ {
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
// add resource // add resource
let mut resource = Resource::default(); let mut resource = ResourceHandler::default();
f(&mut resource); f(&mut resource);
let pattern = Pattern::new(resource.get_name(), path); let pattern = Resource::new(resource.get_name(), path);
parts.resources.push((pattern, Some(resource))); parts.resources.push((pattern, Some(resource)));
} }
self self
@ -253,7 +253,7 @@ impl<S> App<S> where S: 'static {
/// Default resource is used if no matched route could be found. /// Default resource is used if no matched route could be found.
pub fn default_resource<F>(mut self, f: F) -> App<S> pub fn default_resource<F>(mut self, f: F) -> App<S>
where F: FnOnce(&mut Resource<S>) + 'static where F: FnOnce(&mut ResourceHandler<S>) + 'static
{ {
{ {
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
@ -305,7 +305,8 @@ impl<S> App<S> where S: 'static {
panic!("External resource {:?} is registered.", name.as_ref()); panic!("External resource {:?} is registered.", name.as_ref());
} }
parts.external.insert( parts.external.insert(
String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref())); String::from(name.as_ref()),
Resource::external(name.as_ref(), url.as_ref()));
} }
self self
} }

View File

@ -554,8 +554,8 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
mod tests { mod tests {
use futures::{Async, Future}; use futures::{Async, Future};
use super::*; use super::*;
use router::{Router, Pattern}; use router::{Router, Resource};
use resource::Resource; use resource::ResourceHandler;
use test::TestRequest; use test::TestRequest;
use server::ServerSettings; use server::ServerSettings;
@ -580,10 +580,10 @@ mod tests {
fn test_request_extract() { fn test_request_extract() {
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.push((Pattern::new("index", "/{key}/{value}/"), Some(resource))); routes.push((Resource::new("index", "/{key}/{value}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes); let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some()); assert!(router.recognize(&mut req).is_some());
@ -639,10 +639,10 @@ mod tests {
#[test] #[test]
fn test_extract_path_signle() { fn test_extract_path_signle() {
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.push((Pattern::new("index", "/{value}/"), Some(resource))); routes.push((Resource::new("index", "/{value}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes); let (router, _) = Router::new("", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/32/").finish(); let mut req = TestRequest::with_uri("/32/").finish();

View File

@ -17,7 +17,7 @@ use percent_encoding::percent_decode;
use body::Body; use body::Body;
use info::ConnectionInfo; use info::ConnectionInfo;
use param::Params; use param::Params;
use router::Router; use router::{Router, Resource};
use payload::Payload; use payload::Payload;
use handler::FromRequest; use handler::FromRequest;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
@ -39,6 +39,7 @@ pub struct HttpInnerMessage {
pub addr: Option<SocketAddr>, pub addr: Option<SocketAddr>,
pub payload: Option<Payload>, pub payload: Option<Payload>,
pub info: Option<ConnectionInfo<'static>>, pub info: Option<ConnectionInfo<'static>>,
pub resource: i16,
} }
impl Default for HttpInnerMessage { impl Default for HttpInnerMessage {
@ -57,6 +58,7 @@ impl Default for HttpInnerMessage {
payload: None, payload: None,
extensions: Extensions::new(), extensions: Extensions::new(),
info: None, info: None,
resource: -1,
} }
} }
} }
@ -93,9 +95,15 @@ impl HttpInnerMessage {
self.addr = None; self.addr = None;
self.info = None; self.info = None;
self.payload = None; self.payload = None;
self.resource = -1;
} }
} }
lazy_static!{
static ref RESOURCE: Resource = Resource::default();
}
/// An HTTP Request /// An HTTP Request
pub struct HttpRequest<S=()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>); pub struct HttpRequest<S=()>(SharedHttpInnerMessage, Option<Rc<S>>, Option<Router>);
@ -120,6 +128,7 @@ impl HttpRequest<()> {
addr: None, addr: None,
extensions: Extensions::new(), extensions: Extensions::new(),
info: None, info: None,
resource: -1,
}), }),
None, None,
None, None,
@ -318,6 +327,22 @@ impl<S> HttpRequest<S> {
self.2.as_ref() self.2.as_ref()
} }
/// This method returns reference to matched `Resource` object.
#[inline]
pub fn resource(&self) -> &Resource {
let idx = self.as_ref().resource;
if idx >= 0 {
if let Some(ref router) = self.2 {
return router.get_resource(idx as usize)
}
}
&*RESOURCE
}
pub(crate) fn set_resource(&mut self, idx: usize) {
self.as_mut().resource = idx as i16;
}
/// Peer socket address /// Peer socket address
/// ///
/// Peer address is actual socket address, if proxy is used in front of /// Peer address is actual socket address, if proxy is used in front of
@ -544,8 +569,8 @@ impl<S> fmt::Debug for HttpRequest<S> {
mod tests { mod tests {
use super::*; use super::*;
use http::{Uri, HttpTryFrom}; use http::{Uri, HttpTryFrom};
use router::Pattern; use router::Resource;
use resource::Resource; use resource::ResourceHandler;
use test::TestRequest; use test::TestRequest;
use server::ServerSettings; use server::ServerSettings;
@ -607,10 +632,10 @@ mod tests {
fn test_request_match_info() { fn test_request_match_info() {
let mut req = TestRequest::with_uri("/value/?id=test").finish(); let mut req = TestRequest::with_uri("/value/?id=test").finish();
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let mut routes = Vec::new(); let mut routes = Vec::new();
routes.push((Pattern::new("index", "/{key}/"), Some(resource))); routes.push((Resource::new("index", "/{key}/"), Some(resource)));
let (router, _) = Router::new("", ServerSettings::default(), routes); let (router, _) = Router::new("", ServerSettings::default(), routes);
assert!(router.recognize(&mut req).is_some()); assert!(router.recognize(&mut req).is_some());
@ -623,9 +648,9 @@ mod tests {
assert_eq!(req2.url_for("unknown", &["test"]), assert_eq!(req2.url_for("unknown", &["test"]),
Err(UrlGenerationError::RouterNotAvailable)); Err(UrlGenerationError::RouterNotAvailable));
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec!((Pattern::new("index", "/user/{name}.{ext}"), Some(resource))); let routes = vec!((Resource::new("index", "/user/{name}.{ext}"), Some(resource)));
let (router, _) = Router::new("/", ServerSettings::default(), routes); let (router, _) = Router::new("/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html")); assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/test/unknown")); assert!(!router.has_route("/test/unknown"));
@ -645,26 +670,27 @@ mod tests {
fn test_url_for_with_prefix() { fn test_url_for_with_prefix() {
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish(); let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec![(Pattern::new("index", "/user/{name}.{ext}"), Some(resource))]; let routes = vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes); let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html")); assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/prefix/user/test.html")); assert!(!router.has_route("/prefix/user/test.html"));
let req = req.with_state(Rc::new(()), router); let req = req.with_state(Rc::new(()), router);
let url = req.url_for("index", &["test", "html"]); let url = req.url_for("index", &["test", "html"]);
assert_eq!(url.ok().unwrap().as_str(), "http://www.rust-lang.org/prefix/user/test.html"); assert_eq!(url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/user/test.html");
} }
#[test] #[test]
fn test_url_for_external() { fn test_url_for_external() {
let req = HttpRequest::default(); let req = HttpRequest::default();
let mut resource = Resource::<()>::default(); let mut resource = ResourceHandler::<()>::default();
resource.name("index"); resource.name("index");
let routes = vec![ let routes = vec![
(Pattern::new("youtube", "https://youtube.com/watch/{video_id}"), None)]; (Resource::external("youtube", "https://youtube.com/watch/{video_id}"), None)];
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes); let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
assert!(!router.has_route("https://youtube.com/watch/unknown")); assert!(!router.has_route("https://youtube.com/watch/unknown"));

View File

@ -60,6 +60,8 @@ extern crate bitflags;
#[macro_use] #[macro_use]
extern crate failure; extern crate failure;
#[macro_use] #[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate futures; extern crate futures;
extern crate futures_cpupool; extern crate futures_cpupool;
extern crate tokio_io; extern crate tokio_io;
@ -174,12 +176,12 @@ pub mod dev {
pub use body::BodyStream; pub use body::BodyStream;
pub use context::Drain; pub use context::Drain;
pub use json::JsonBody;
pub use info::ConnectionInfo; pub use info::ConnectionInfo;
pub use handler::{Handler, Reply, FromRequest}; pub use handler::{Handler, Reply, FromRequest};
pub use route::Route; pub use route::Route;
pub use resource::Resource; pub use router::{Router, Resource};
pub use json::JsonBody; pub use resource::ResourceHandler;
pub use router::{Router, Pattern};
pub use param::{FromParam, Params}; pub use param::{FromParam, Params};
pub use httpmessage::{UrlEncoded, MessageBody}; pub use httpmessage::{UrlEncoded, MessageBody};
pub use httpresponse::HttpResponseBuilder; pub use httpresponse::HttpResponseBuilder;

View File

@ -10,7 +10,7 @@
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend. //! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the constructed backend.
//! //!
//! Cors middleware could be used as parameter for `App::middleware()` or //! Cors middleware could be used as parameter for `App::middleware()` or
//! `Resource::middleware()` methods. But you have to use `Cors::register()` method to //! `ResourceHandler::middleware()` methods. But you have to use `Cors::register()` method to
//! support *preflight* OPTIONS request. //! support *preflight* OPTIONS request.
//! //!
//! //!
@ -52,7 +52,7 @@ use http::{self, Method, HttpTryFrom, Uri, StatusCode};
use http::header::{self, HeaderName, HeaderValue}; use http::header::{self, HeaderName, HeaderValue};
use error::{Result, ResponseError}; use error::{Result, ResponseError};
use resource::Resource; use resource::ResourceHandler;
use httpmessage::HttpMessage; use httpmessage::HttpMessage;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -212,10 +212,10 @@ impl Cors {
/// This method register cors middleware with resource and /// This method register cors middleware with resource and
/// adds route for *OPTIONS* preflight requests. /// adds route for *OPTIONS* preflight requests.
/// ///
/// It is possible to register *Cors* middleware with `Resource::middleware()` /// It is possible to register *Cors* middleware with `ResourceHandler::middleware()`
/// method, but in that case *Cors* middleware wont be able to handle *OPTIONS* /// method, but in that case *Cors* middleware wont be able to handle *OPTIONS*
/// requests. /// requests.
pub fn register<S: 'static>(self, resource: &mut Resource<S>) { pub fn register<S: 'static>(self, resource: &mut ResourceHandler<S>) {
resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok()); resource.method(Method::OPTIONS).h(|_| HttpResponse::Ok());
resource.middleware(self); resource.middleware(self);
} }

View File

@ -31,16 +31,16 @@ use httpresponse::HttpResponse;
/// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok())) /// "/", |r| r.method(http::Method::GET).f(|r| HttpResponse::Ok()))
/// .finish(); /// .finish();
/// } /// }
pub struct Resource<S=()> { pub struct ResourceHandler<S=()> {
name: String, name: String,
state: PhantomData<S>, state: PhantomData<S>,
routes: SmallVec<[Route<S>; 3]>, routes: SmallVec<[Route<S>; 3]>,
middlewares: Rc<Vec<Box<Middleware<S>>>>, middlewares: Rc<Vec<Box<Middleware<S>>>>,
} }
impl<S> Default for Resource<S> { impl<S> Default for ResourceHandler<S> {
fn default() -> Self { fn default() -> Self {
Resource { ResourceHandler {
name: String::new(), name: String::new(),
state: PhantomData, state: PhantomData,
routes: SmallVec::new(), routes: SmallVec::new(),
@ -48,10 +48,10 @@ impl<S> Default for Resource<S> {
} }
} }
impl<S> Resource<S> { impl<S> ResourceHandler<S> {
pub(crate) fn default_not_found() -> Self { pub(crate) fn default_not_found() -> Self {
Resource { ResourceHandler {
name: String::new(), name: String::new(),
state: PhantomData, state: PhantomData,
routes: SmallVec::new(), routes: SmallVec::new(),
@ -68,7 +68,7 @@ impl<S> Resource<S> {
} }
} }
impl<S: 'static> Resource<S> { impl<S: 'static> ResourceHandler<S> {
/// Register a new route and return mutable reference to *Route* object. /// Register a new route and return mutable reference to *Route* object.
/// *Route* is used for route configuration, i.e. adding predicates, setting up handler. /// *Route* is used for route configuration, i.e. adding predicates, setting up handler.
@ -97,7 +97,7 @@ impl<S: 'static> Resource<S> {
/// This is shortcut for: /// This is shortcut for:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// Resource::resource("/", |r| r.route().filter(pred::Get()).f(index) /// Application::resource("/", |r| r.route().filter(pred::Get()).f(index)
/// ``` /// ```
pub fn method(&mut self, method: Method) -> &mut Route<S> { pub fn method(&mut self, method: Method) -> &mut Route<S> {
self.routes.push(Route::default()); self.routes.push(Route::default());
@ -109,7 +109,7 @@ impl<S: 'static> Resource<S> {
/// This is shortcut for: /// This is shortcut for:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// Resource::resource("/", |r| r.route().h(handler) /// Application::resource("/", |r| r.route().h(handler)
/// ``` /// ```
pub fn h<H: Handler<S>>(&mut self, handler: H) { pub fn h<H: Handler<S>>(&mut self, handler: H) {
self.routes.push(Route::default()); self.routes.push(Route::default());
@ -121,7 +121,7 @@ impl<S: 'static> Resource<S> {
/// This is shortcut for: /// This is shortcut for:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// Resource::resource("/", |r| r.route().f(index) /// Application::resource("/", |r| r.route().f(index)
/// ``` /// ```
pub fn f<F, R>(&mut self, handler: F) pub fn f<F, R>(&mut self, handler: F)
where F: Fn(HttpRequest<S>) -> R + 'static, where F: Fn(HttpRequest<S>) -> R + 'static,
@ -136,7 +136,7 @@ impl<S: 'static> Resource<S> {
/// This is shortcut for: /// This is shortcut for:
/// ///
/// ```rust,ignore /// ```rust,ignore
/// Resource::resource("/", |r| r.route().with(index) /// Application::resource("/", |r| r.route().with(index)
/// ``` /// ```
pub fn with<T, F, R>(&mut self, handler: F) pub fn with<T, F, R>(&mut self, handler: F)
where F: Fn(T) -> R + 'static, where F: Fn(T) -> R + 'static,
@ -147,7 +147,7 @@ impl<S: 'static> Resource<S> {
self.routes.last_mut().unwrap().with(handler) self.routes.last_mut().unwrap().with(handler)
} }
/// Register a middleware /// Register a resource middleware
/// ///
/// This is similar to `App's` middlewares, but /// This is similar to `App's` middlewares, but
/// middlewares get invoked on resource level. /// middlewares get invoked on resource level.
@ -157,7 +157,7 @@ impl<S: 'static> Resource<S> {
pub(crate) fn handle(&mut self, pub(crate) fn handle(&mut self,
mut req: HttpRequest<S>, mut req: HttpRequest<S>,
default: Option<&mut Resource<S>>) -> Reply default: Option<&mut ResourceHandler<S>>) -> Reply
{ {
for route in &mut self.routes { for route in &mut self.routes {
if route.check(&mut req) { if route.check(&mut req) {

View File

@ -6,9 +6,9 @@ use std::collections::HashMap;
use regex::{Regex, escape}; use regex::{Regex, escape};
use percent_encoding::percent_decode; use percent_encoding::percent_decode;
use error::UrlGenerationError;
use param::Params; use param::Params;
use resource::Resource; use error::UrlGenerationError;
use resource::ResourceHandler;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use server::ServerSettings; use server::ServerSettings;
@ -19,8 +19,8 @@ pub struct Router(Rc<Inner>);
struct Inner { struct Inner {
prefix: String, prefix: String,
prefix_len: usize, prefix_len: usize,
named: HashMap<String, (Pattern, bool)>, named: HashMap<String, (Resource, bool)>,
patterns: Vec<Pattern>, patterns: Vec<Resource>,
srv: ServerSettings, srv: ServerSettings,
} }
@ -28,7 +28,8 @@ impl Router {
/// Create new router /// Create new router
pub fn new<S>(prefix: &str, pub fn new<S>(prefix: &str,
settings: ServerSettings, settings: ServerSettings,
map: Vec<(Pattern, Option<Resource<S>>)>) -> (Router, Vec<Resource<S>>) map: Vec<(Resource, Option<ResourceHandler<S>>)>)
-> (Router, Vec<ResourceHandler<S>>)
{ {
let prefix = prefix.trim().trim_right_matches('/').to_owned(); let prefix = prefix.trim().trim_right_matches('/').to_owned();
let mut named = HashMap::new(); let mut named = HashMap::new();
@ -64,6 +65,10 @@ impl Router {
&self.0.srv &self.0.srv
} }
pub(crate) fn get_resource(&self, idx: usize) -> &Resource {
&self.0.patterns[idx]
}
/// Query for matched resource /// Query for matched resource
pub fn recognize<S>(&self, req: &mut HttpRequest<S>) -> Option<usize> { pub fn recognize<S>(&self, req: &mut HttpRequest<S>) -> Option<usize> {
if self.0.prefix_len > req.path().len() { if self.0.prefix_len > req.path().len() {
@ -75,6 +80,7 @@ impl Router {
for (idx, pattern) in self.0.patterns.iter().enumerate() { for (idx, pattern) in self.0.patterns.iter().enumerate() {
if pattern.match_with_params(p.as_ref(), req.match_info_mut()) { if pattern.match_with_params(p.as_ref(), req.match_info_mut()) {
req.set_resource(idx);
return Some(idx) return Some(idx)
} }
} }
@ -108,11 +114,7 @@ impl Router {
I: AsRef<str>, I: AsRef<str>,
{ {
if let Some(pattern) = self.0.named.get(name) { if let Some(pattern) = self.0.named.get(name) {
if pattern.1 { pattern.0.resource_path(self, elements)
pattern.0.path(None, elements)
} else {
pattern.0.path(Some(&self.0.prefix), elements)
}
} else { } else {
Err(UrlGenerationError::ResourceNotFound) Err(UrlGenerationError::ResourceNotFound)
} }
@ -125,6 +127,7 @@ impl Clone for Router {
} }
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
enum PatternElement { enum PatternElement {
Str(String), Str(String),
@ -137,25 +140,48 @@ enum PatternType {
Dynamic(Regex, Vec<String>), Dynamic(Regex, Vec<String>),
} }
/// Reslource type describes an entry in resources table
#[derive(Clone)] #[derive(Clone)]
pub struct Pattern { pub struct Resource {
tp: PatternType, tp: PatternType,
name: String, name: String,
pattern: String, pattern: String,
elements: Vec<PatternElement>, elements: Vec<PatternElement>,
external: bool,
} }
impl Pattern { impl Default for Resource {
/// Parse path pattern and create new `Pattern` instance. fn default() -> Resource {
Resource {
tp: PatternType::Static("".to_owned()),
name: "".to_owned(),
pattern: "".to_owned(),
elements: Vec::new(),
external: false,
}
}
}
impl Resource {
/// Parse path pattern and create new `Resource` instance.
/// ///
/// Panics if path pattern is wrong. /// Panics if path pattern is wrong.
pub fn new(name: &str, path: &str) -> Self { pub fn new(name: &str, path: &str) -> Self {
Pattern::with_prefix(name, path, "/") Resource::with_prefix(name, path, "/")
} }
/// Parse path pattern and create new `Pattern` instance with custom prefix /// Construct external resource
///
/// Panics if path pattern is wrong.
pub fn external(name: &str, path: &str) -> Self {
let mut resource = Resource::with_prefix(name, path, "/");
resource.external = true;
resource
}
/// Parse path pattern and create new `Resource` instance with custom prefix
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self { pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self {
let (pattern, elements, is_dynamic) = Pattern::parse(path, prefix); let (pattern, elements, is_dynamic) = Resource::parse(path, prefix);
let tp = if is_dynamic { let tp = if is_dynamic {
let re = match Regex::new(&pattern) { let re = match Regex::new(&pattern) {
@ -170,20 +196,21 @@ impl Pattern {
PatternType::Static(pattern.clone()) PatternType::Static(pattern.clone())
}; };
Pattern { Resource {
tp, tp,
pattern,
elements, elements,
name: name.into(), name: name.into(),
pattern: path.to_owned(),
external: false,
} }
} }
/// Returns name of the pattern /// Name of the resource
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.name &self.name
} }
/// Returns path of the pattern /// Path pattern of the resource
pub fn pattern(&self) -> &str { pub fn pattern(&self) -> &str {
&self.pattern &self.pattern
} }
@ -219,14 +246,15 @@ impl Pattern {
} }
} }
/// Build pattern path. /// Build reousrce path.
pub fn path<U, I>(&self, prefix: Option<&str>, elements: U) -> Result<String, UrlGenerationError> pub fn resource_path<U, I>(&self, router: &Router, elements: U)
-> Result<String, UrlGenerationError>
where U: IntoIterator<Item=I>, where U: IntoIterator<Item=I>,
I: AsRef<str>, I: AsRef<str>,
{ {
let mut iter = elements.into_iter(); let mut iter = elements.into_iter();
let mut path = if let Some(prefix) = prefix { let mut path = if !self.external {
format!("{}/", prefix) format!("{}/", router.prefix())
} else { } else {
String::new() String::new()
}; };
@ -309,15 +337,15 @@ impl Pattern {
} }
} }
impl PartialEq for Pattern { impl PartialEq for Resource {
fn eq(&self, other: &Pattern) -> bool { fn eq(&self, other: &Resource) -> bool {
self.pattern == other.pattern self.pattern == other.pattern
} }
} }
impl Eq for Pattern {} impl Eq for Resource {}
impl Hash for Pattern { impl Hash for Resource {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.pattern.hash(state); self.pattern.hash(state);
} }
@ -331,13 +359,20 @@ mod tests {
#[test] #[test]
fn test_recognizer() { fn test_recognizer() {
let routes = vec![ let routes = vec![
(Pattern::new("", "/name"), Some(Resource::default())), (Resource::new("", "/name"),
(Pattern::new("", "/name/{val}"), Some(Resource::default())), Some(ResourceHandler::default())),
(Pattern::new("", "/name/{val}/index.html"), Some(Resource::default())), (Resource::new("", "/name/{val}"),
(Pattern::new("", "/file/{file}.{ext}"), Some(Resource::default())), Some(ResourceHandler::default())),
(Pattern::new("", "/v{val}/{val2}/index.html"), Some(Resource::default())), (Resource::new("", "/name/{val}/index.html"),
(Pattern::new("", "/v/{tail:.*}"), Some(Resource::default())), Some(ResourceHandler::default())),
(Pattern::new("", "{test}/index.html"), Some(Resource::default()))]; (Resource::new("", "/file/{file}.{ext}"),
Some(ResourceHandler::default())),
(Resource::new("", "/v{val}/{val2}/index.html"),
Some(ResourceHandler::default())),
(Resource::new("", "/v/{tail:.*}"),
Some(ResourceHandler::default())),
(Resource::new("", "{test}/index.html"),
Some(ResourceHandler::default()))];
let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/name").finish(); let mut req = TestRequest::with_uri("/name").finish();
@ -375,8 +410,8 @@ mod tests {
#[test] #[test]
fn test_recognizer_2() { fn test_recognizer_2() {
let routes = vec![ let routes = vec![
(Pattern::new("", "/index.json"), Some(Resource::default())), (Resource::new("", "/index.json"), Some(ResourceHandler::default())),
(Pattern::new("", "/{source}.json"), Some(Resource::default()))]; (Resource::new("", "/{source}.json"), Some(ResourceHandler::default()))];
let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes); let (rec, _) = Router::new::<()>("", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/index.json").finish(); let mut req = TestRequest::with_uri("/index.json").finish();
@ -389,8 +424,8 @@ mod tests {
#[test] #[test]
fn test_recognizer_with_prefix() { fn test_recognizer_with_prefix() {
let routes = vec![ let routes = vec![
(Pattern::new("", "/name"), Some(Resource::default())), (Resource::new("", "/name"), Some(ResourceHandler::default())),
(Pattern::new("", "/name/{val}"), Some(Resource::default()))]; (Resource::new("", "/name/{val}"), Some(ResourceHandler::default()))];
let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes); let (rec, _) = Router::new::<()>("/test", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/name").finish(); let mut req = TestRequest::with_uri("/name").finish();
@ -406,8 +441,8 @@ mod tests {
// same patterns // same patterns
let routes = vec![ let routes = vec![
(Pattern::new("", "/name"), Some(Resource::default())), (Resource::new("", "/name"), Some(ResourceHandler::default())),
(Pattern::new("", "/name/{val}"), Some(Resource::default()))]; (Resource::new("", "/name/{val}"), Some(ResourceHandler::default()))];
let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes); let (rec, _) = Router::new::<()>("/test2", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/name").finish(); let mut req = TestRequest::with_uri("/name").finish();
@ -423,22 +458,22 @@ mod tests {
#[test] #[test]
fn test_parse_static() { fn test_parse_static() {
let re = Pattern::new("test", "/"); let re = Resource::new("test", "/");
assert!(re.is_match("/")); assert!(re.is_match("/"));
assert!(!re.is_match("/a")); assert!(!re.is_match("/a"));
let re = Pattern::new("test", "/name"); let re = Resource::new("test", "/name");
assert!(re.is_match("/name")); assert!(re.is_match("/name"));
assert!(!re.is_match("/name1")); assert!(!re.is_match("/name1"));
assert!(!re.is_match("/name/")); assert!(!re.is_match("/name/"));
assert!(!re.is_match("/name~")); assert!(!re.is_match("/name~"));
let re = Pattern::new("test", "/name/"); let re = Resource::new("test", "/name/");
assert!(re.is_match("/name/")); assert!(re.is_match("/name/"));
assert!(!re.is_match("/name")); assert!(!re.is_match("/name"));
assert!(!re.is_match("/name/gs")); assert!(!re.is_match("/name/gs"));
let re = Pattern::new("test", "/user/profile"); let re = Resource::new("test", "/user/profile");
assert!(re.is_match("/user/profile")); assert!(re.is_match("/user/profile"));
assert!(!re.is_match("/user/profile/profile")); assert!(!re.is_match("/user/profile/profile"));
} }
@ -447,7 +482,7 @@ mod tests {
fn test_parse_param() { fn test_parse_param() {
let mut req = HttpRequest::default(); let mut req = HttpRequest::default();
let re = Pattern::new("test", "/user/{id}"); let re = Resource::new("test", "/user/{id}");
assert!(re.is_match("/user/profile")); assert!(re.is_match("/user/profile"));
assert!(re.is_match("/user/2345")); assert!(re.is_match("/user/2345"));
assert!(!re.is_match("/user/2345/")); assert!(!re.is_match("/user/2345/"));
@ -461,7 +496,7 @@ mod tests {
assert!(re.match_with_params("/user/1245125", req.match_info_mut())); assert!(re.match_with_params("/user/1245125", req.match_info_mut()));
assert_eq!(req.match_info().get("id").unwrap(), "1245125"); assert_eq!(req.match_info().get("id").unwrap(), "1245125");
let re = Pattern::new("test", "/v{version}/resource/{id}"); let re = Resource::new("test", "/v{version}/resource/{id}");
assert!(re.is_match("/v1/resource/320120")); assert!(re.is_match("/v1/resource/320120"));
assert!(!re.is_match("/v/resource/1")); assert!(!re.is_match("/v/resource/1"));
assert!(!re.is_match("/resource")); assert!(!re.is_match("/resource"));
@ -471,4 +506,24 @@ mod tests {
assert_eq!(req.match_info().get("version").unwrap(), "151"); assert_eq!(req.match_info().get("version").unwrap(), "151");
assert_eq!(req.match_info().get("id").unwrap(), "adahg32"); assert_eq!(req.match_info().get("id").unwrap(), "adahg32");
} }
#[test]
fn test_request_resource() {
let routes = vec![
(Resource::new("r1", "/index.json"), Some(ResourceHandler::default())),
(Resource::new("r2", "/test.json"), Some(ResourceHandler::default()))];
let (router, _) = Router::new::<()>("", ServerSettings::default(), routes);
let mut req = TestRequest::with_uri("/index.json")
.finish_with_router(router.clone());
assert_eq!(router.recognize(&mut req), Some(0));
let resource = req.resource();
assert_eq!(resource.name(), "r1");
let mut req = TestRequest::with_uri("/test.json")
.finish_with_router(router.clone());
assert_eq!(router.recognize(&mut req), Some(1));
let resource = req.resource();
assert_eq!(resource.name(), "r2");
}
} }

View File

@ -27,7 +27,7 @@ use application::{App, HttpApplication};
use param::Params; use param::Params;
use router::Router; use router::Router;
use payload::Payload; use payload::Payload;
use resource::Resource; use resource::ResourceHandler;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use server::{HttpServer, IntoHttpHandler, ServerSettings}; use server::{HttpServer, IntoHttpHandler, ServerSettings};
@ -352,7 +352,7 @@ impl<S: 'static> TestApp<S> {
/// Register resource. This method is similar /// Register resource. This method is similar
/// to `App::resource()` method. /// to `App::resource()` method.
pub fn resource<F>(&mut self, path: &str, f: F) -> &mut TestApp<S> pub fn resource<F>(&mut self, path: &str, f: F) -> &mut TestApp<S>
where F: FnOnce(&mut Resource<S>) + 'static where F: FnOnce(&mut ResourceHandler<S>) + 'static
{ {
self.app = Some(self.app.take().unwrap().resource(path, f)); self.app = Some(self.app.take().unwrap().resource(path, f));
self self