From da915972c0e32198c924b716e3904e5066a4966a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 15:12:21 +0600 Subject: [PATCH 1/7] refactor router --- src/application.rs | 244 +++++----------- src/extractor.rs | 38 +-- src/handler.rs | 7 +- src/httprequest.rs | 55 ++-- src/middleware/cors.rs | 13 +- src/param.rs | 9 + src/pipeline.rs | 20 +- src/resource.rs | 58 ++-- src/router.rs | 617 ++++++++++++++++++++++++++--------------- src/scope.rs | 215 +++++--------- src/test.rs | 14 +- 11 files changed, 635 insertions(+), 655 deletions(-) diff --git a/src/application.rs b/src/application.rs index 96c4ad11f..80ba7f52c 100644 --- a/src/application.rs +++ b/src/application.rs @@ -1,16 +1,15 @@ -use std::collections::HashMap; use std::rc::Rc; -use handler::{AsyncResult, FromRequest, Handler, Responder, RouteHandler, WrapHandler}; +use handler::{AsyncResult, FromRequest, Handler, Responder, WrapHandler}; use header::ContentEncoding; -use http::{Method, StatusCode}; +use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::Middleware; -use pipeline::{HandlerType, Pipeline, PipelineHandler}; +use pipeline::{Pipeline, PipelineHandler}; use pred::Predicate; use resource::Resource; -use router::{ResourceDef, RouteInfo, Router}; +use router::{ResourceDef, Router}; use scope::Scope; use server::{HttpHandler, HttpHandlerTask, IntoHttpHandler, Request}; @@ -19,7 +18,6 @@ pub struct HttpApplication { state: Rc, prefix: String, prefix_len: usize, - router: Router, inner: Rc>, filters: Option>>>, middlewares: Rc>>>, @@ -27,16 +25,8 @@ pub struct HttpApplication { #[doc(hidden)] pub struct Inner { - prefix: usize, - default: Rc>, + router: Router, encoding: ContentEncoding, - resources: Vec>, - handlers: Vec>, -} - -enum PrefixHandlerType { - Handler(String, Box>), - Scope(ResourceDef, Box>, Vec>>), } impl PipelineHandler for Inner { @@ -45,80 +35,21 @@ impl PipelineHandler for Inner { self.encoding } - fn handle( - &self, req: &HttpRequest, htype: HandlerType, - ) -> AsyncResult { - match htype { - HandlerType::Normal(idx) => { - if let Some(id) = self.resources[idx].get_route_id(req) { - return self.resources[idx].handle(id, req); - } - } - HandlerType::Handler(idx) => match self.handlers[idx] { - PrefixHandlerType::Handler(_, ref hnd) => return hnd.handle(req), - PrefixHandlerType::Scope(_, ref hnd, _) => return hnd.handle(req), - }, - _ => (), - } - if let Some(id) = self.default.get_route_id(req) { - self.default.handle(id, req) - } else { - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) - } + fn handle(&self, req: &HttpRequest) -> AsyncResult { + self.router.handle(req) } } impl HttpApplication { - #[inline] - fn get_handler(&self, req: &Request) -> (RouteInfo, HandlerType) { - if let Some((idx, info)) = self.router.recognize(req) { - (info, HandlerType::Normal(idx)) - } else { - 'outer: for idx in 0..self.inner.handlers.len() { - match self.inner.handlers[idx] { - PrefixHandlerType::Handler(ref prefix, _) => { - let m = { - let path = &req.path()[self.inner.prefix..]; - let path_len = path.len(); - - path.starts_with(prefix) - && (path_len == prefix.len() - || path.split_at(prefix.len()).1.starts_with('/')) - }; - - if m { - let prefix_len = (self.inner.prefix + prefix.len()) as u16; - let info = self.router.route_info(req, prefix_len); - return (info, HandlerType::Handler(idx)); - } - } - PrefixHandlerType::Scope(ref pattern, _, ref filters) => { - if let Some(params) = - pattern.match_prefix_with_params(req, self.inner.prefix) - { - for filter in filters { - if !filter.check(req, &self.state) { - continue 'outer; - } - } - return ( - self.router.route_info_params(params), - HandlerType::Handler(idx), - ); - } - } - } - } - (self.router.default_route_info(), HandlerType::Default) - } - } - #[cfg(test)] pub(crate) fn run(&self, req: Request) -> AsyncResult { - let (info, tp) = self.get_handler(&req); + let info = self + .inner + .router + .recognize(&req, &self.state, self.prefix_len); let req = HttpRequest::new(req, Rc::clone(&self.state), info); - self.inner.handle(&req, tp) + self.inner.handle(&req) } } @@ -141,10 +72,14 @@ impl HttpHandler for HttpApplication { } } - let (info, tp) = self.get_handler(&msg); + let info = self + .inner + .router + .recognize(&msg, &self.state, self.prefix_len); + let inner = Rc::clone(&self.inner); let req = HttpRequest::new(msg, Rc::clone(&self.state), info); - Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner, tp)) + Ok(Pipeline::new(req, Rc::clone(&self.middlewares), inner)) } else { Err(msg) } @@ -154,10 +89,7 @@ impl HttpHandler for HttpApplication { struct ApplicationParts { state: S, prefix: String, - default: Rc>, - resources: Vec<(ResourceDef, Option>)>, - handlers: Vec>, - external: HashMap, + router: Router, encoding: ContentEncoding, middlewares: Vec>>, filters: Vec>>, @@ -204,10 +136,7 @@ where parts: Some(ApplicationParts { state, prefix: "/".to_owned(), - default: Rc::new(Resource::default_not_found()), - resources: Vec::new(), - handlers: Vec::new(), - external: HashMap::new(), + router: Router::new(), middlewares: Vec::new(), filters: Vec::new(), encoding: ContentEncoding::Auto, @@ -315,35 +244,11 @@ where R: Responder + 'static, T: FromRequest + 'static, { - { - let parts: &mut ApplicationParts = - self.parts.as_mut().expect("Use after finish"); - - let out = { - // get resource handler - let mut iterator = parts.resources.iter_mut(); - - loop { - if let Some(&mut (ref pattern, ref mut handler)) = iterator.next() { - if let Some(ref mut handler) = *handler { - if pattern.pattern() == path { - handler.method(method).with(f); - break None; - } - } - } else { - let mut handler = Resource::default(); - handler.method(method).with(f); - let pattern = ResourceDef::new(handler.get_name(), path); - break Some((pattern, Some(handler))); - } - } - }; - - if let Some(out) = out { - parts.resources.push(out); - } - } + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_route(path, method, f); self } @@ -376,17 +281,12 @@ where where F: FnOnce(Scope) -> Scope, { - { - let mut scope = Box::new(f(Scope::new())); - let parts = self.parts.as_mut().expect("Use after finish"); - - let filters = scope.take_filters(); - parts.handlers.push(PrefixHandlerType::Scope( - ResourceDef::prefix("", &path), - scope, - filters, - )); - } + let scope = f(Scope::new(path)); + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_scope(scope); self } @@ -428,25 +328,25 @@ where { let parts = self.parts.as_mut().expect("Use after finish"); - // add resource handler - let mut handler = Resource::default(); - f(&mut handler); + // create resource + let mut resource = Resource::new(ResourceDef::new(path)); - let pattern = ResourceDef::new(handler.get_name(), path); - parts.resources.push((pattern, Some(handler))); + // configure + f(&mut resource); + + parts.router.register_resource(resource); } self } /// Configure resource for a specific path. #[doc(hidden)] - pub fn register_resource(&mut self, path: &str, resource: Resource) { - let pattern = ResourceDef::new(resource.get_name(), path); + pub fn register_resource(&mut self, resource: Resource) { self.parts .as_mut() .expect("Use after finish") - .resources - .push((pattern, Some(resource))); + .router + .register_resource(resource); } /// Default resource to be used if no matching route could be found. @@ -454,12 +354,16 @@ where where F: FnOnce(&mut Resource) -> R + 'static, { - { - let parts = self.parts.as_mut().expect("Use after finish"); - let default = Rc::get_mut(&mut parts.default) - .expect("Multiple App instance references are not allowed"); - f(default); - } + // create and configure default resource + let mut resource = Resource::new(ResourceDef::new("")); + f(&mut resource); + + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_default_resource(resource.into()); + self } @@ -500,17 +404,11 @@ where T: AsRef, U: AsRef, { - { - let parts = self.parts.as_mut().expect("Use after finish"); - - if parts.external.contains_key(name.as_ref()) { - panic!("External resource {:?} is registered.", name.as_ref()); - } - parts.external.insert( - String::from(name.as_ref()), - ResourceDef::external(name.as_ref(), url.as_ref()), - ); - } + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_external(name.as_ref(), ResourceDef::external(url.as_ref())); self } @@ -544,12 +442,11 @@ where if path.len() > 1 && path.ends_with('/') { path.pop(); } - let parts = self.parts.as_mut().expect("Use after finish"); - - parts.handlers.push(PrefixHandlerType::Handler( - path, - Box::new(WrapHandler::new(handler)), - )); + self.parts + .as_mut() + .expect("Use after finish") + .router + .register_handler(&path, Box::new(WrapHandler::new(handler)), None); } self } @@ -607,27 +504,11 @@ where (prefix.to_owned(), prefix.len()) }; - let mut resources = parts.resources; - for (_, pattern) in parts.external { - resources.push((pattern, None)); - } - - for handler in &mut parts.handlers { - if let PrefixHandlerType::Scope(_, ref mut route_handler, _) = handler { - if !route_handler.has_default_resource() { - route_handler.default_resource(Rc::clone(&parts.default)); - } - }; - } - - let (router, resources) = Router::new(&prefix, resources); + parts.router.finish(); let inner = Rc::new(Inner { - prefix: prefix_len, - default: Rc::clone(&parts.default), + router: parts.router, encoding: parts.encoding, - handlers: parts.handlers, - resources, }); let filters = if parts.filters.is_empty() { None @@ -637,7 +518,6 @@ where HttpApplication { state: Rc::new(parts.state), - router: router.clone(), middlewares: Rc::new(parts.middlewares), prefix, prefix_len, diff --git a/src/extractor.rs b/src/extractor.rs index bebfbf207..8e4745f86 100644 --- a/src/extractor.rs +++ b/src/extractor.rs @@ -719,12 +719,9 @@ mod tests { fn test_request_extract() { let req = TestRequest::with_uri("/name/user1/?id=test").finish(); - let mut resource = Resource::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((ResourceDef::new("index", "/{key}/{value}/"), Some(resource))); - let (router, _) = Router::new("", routes); - let info = router.recognize(&req).unwrap().1; + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); + let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); let s = Path::::from_request(&req, &()).unwrap(); @@ -738,8 +735,10 @@ mod tests { let s = Query::::from_request(&req, &()).unwrap(); assert_eq!(s.id, "test"); - let req = TestRequest::with_uri("/name/32/").finish_with_router(router.clone()); - let info = router.recognize(&req).unwrap().1; + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); + let req = TestRequest::with_uri("/name/32/").finish(); + let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); let s = Path::::from_request(&req, &()).unwrap(); @@ -757,29 +756,22 @@ mod tests { #[test] fn test_extract_path_single() { - let mut resource = Resource::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((ResourceDef::new("index", "/{value}/"), Some(resource))); - let (router, _) = Router::new("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{value}/"))); - let req = TestRequest::with_uri("/32/").finish_with_router(router.clone()); - let info = router.recognize(&req).unwrap().1; + let req = TestRequest::with_uri("/32/").finish(); + let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); assert_eq!(*Path::::from_request(&req, &()).unwrap(), 32); } #[test] fn test_tuple_extract() { - let mut resource = Resource::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((ResourceDef::new("index", "/{key}/{value}/"), Some(resource))); - let (router, _) = Router::new("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/"))); - let req = TestRequest::with_uri("/name/user1/?id=test") - .finish_with_router(router.clone()); - let info = router.recognize(&req).unwrap().1; + let req = TestRequest::with_uri("/name/user1/?id=test").finish(); + let info = router.recognize(&req, &(), 0); let req = req.with_route_info(info); let res = match <(Path<(String, String)>,)>::extract(&req).poll() { diff --git a/src/handler.rs b/src/handler.rs index 241f4e6a6..98d253438 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,6 +1,5 @@ use std::marker::PhantomData; use std::ops::Deref; -use std::rc::Rc; use futures::future::{err, ok, Future}; use futures::{Async, Poll}; @@ -9,7 +8,7 @@ use error::Error; use http::StatusCode; use httprequest::HttpRequest; use httpresponse::HttpResponse; -use resource::Resource; +use resource::DefaultResource; /// Trait defines object that could be registered as route handler #[allow(unused_variables)] @@ -409,9 +408,11 @@ pub(crate) trait RouteHandler: 'static { false } - fn default_resource(&mut self, _: Rc>) { + fn default_resource(&mut self, _: DefaultResource) { unimplemented!() } + + fn finish(&mut self) {} } /// Route handler wrapper for Handler diff --git a/src/httprequest.rs b/src/httprequest.rs index 650d3a39c..a04973381 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -81,7 +81,9 @@ impl HttpRequest { #[inline] /// Construct new http request with new RouteInfo. - pub(crate) fn with_route_info(&self, route: RouteInfo) -> HttpRequest { + pub(crate) fn with_route_info(&self, mut route: RouteInfo) -> HttpRequest { + route.merge(&self.route); + HttpRequest { route, req: self.req.as_ref().map(|r| r.clone()), @@ -422,26 +424,21 @@ mod tests { #[test] fn test_request_match_info() { - let mut resource = Resource::<()>::default(); - resource.name("index"); - let mut routes = Vec::new(); - routes.push((ResourceDef::new("index", "/{key}/"), Some(resource))); - let (router, _) = Router::new("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/{key}/"))); let req = TestRequest::with_uri("/value/?id=test").finish(); - let info = router.recognize(&req).unwrap().1; + let info = router.recognize(&req, &(), 0); assert_eq!(info.match_info().get("key"), Some("value")); } #[test] fn test_url_for() { - let mut resource = Resource::<()>::default(); + let mut router = Router::<()>::new(); + let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}")); resource.name("index"); - let routes = vec![( - ResourceDef::new("index", "/user/{name}.{ext}"), - Some(resource), - )]; - let (router, _) = Router::new("/", routes); + router.register_resource(resource); + let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); assert!(!info.has_route("/test/unknown")); @@ -466,13 +463,12 @@ mod tests { #[test] fn test_url_for_with_prefix() { - let mut resource = Resource::<()>::default(); + let mut resource = Resource::new(ResourceDef::new("/user/{name}.html")); resource.name("index"); - let routes = vec![( - ResourceDef::new("index", "/user/{name}.html"), - Some(resource), - )]; - let (router, _) = Router::new("/prefix/", routes); + let mut router = Router::<()>::new(); + router.set_prefix("/prefix/"); + router.register_resource(resource); + let info = router.default_route_info(); assert!(info.has_route("/user/test.html")); assert!(!info.has_route("/prefix/user/test.html")); @@ -488,10 +484,12 @@ mod tests { #[test] fn test_url_for_static() { - let mut resource = Resource::<()>::default(); + let mut resource = Resource::new(ResourceDef::new("/index.html")); resource.name("index"); - let routes = vec![(ResourceDef::new("index", "/index.html"), Some(resource))]; - let (router, _) = Router::new("/prefix/", routes); + let mut router = Router::<()>::new(); + router.set_prefix("/prefix/"); + router.register_resource(resource); + let info = router.default_route_info(); assert!(info.has_route("/index.html")); assert!(!info.has_route("/prefix/index.html")); @@ -508,13 +506,12 @@ mod tests { #[test] fn test_url_for_external() { - let mut resource = Resource::<()>::default(); - resource.name("index"); - let routes = vec![( - ResourceDef::external("youtube", "https://youtube.com/watch/{video_id}"), - None, - )]; - let router = Router::new::<()>("", routes).0; + let mut router = Router::<()>::new(); + router.register_external( + "youtube", + ResourceDef::external("https://youtube.com/watch/{video_id}"), + ); + let info = router.default_route_info(); assert!(!info.has_route("https://youtube.com/watch/unknown")); diff --git a/src/middleware/cors.rs b/src/middleware/cors.rs index 3f0f7ef5f..052e4da23 100644 --- a/src/middleware/cors.rs +++ b/src/middleware/cors.rs @@ -60,6 +60,7 @@ use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{Middleware, Response, Started}; use resource::Resource; +use router::ResourceDef; use server::Request; /// A set of errors that can occur during processing CORS @@ -515,7 +516,7 @@ pub struct CorsBuilder { methods: bool, error: Option, expose_hdrs: HashSet, - resources: Vec<(String, Resource)>, + resources: Vec>, app: Option>, } @@ -798,10 +799,10 @@ impl CorsBuilder { F: FnOnce(&mut Resource) -> R + 'static, { // add resource handler - let mut handler = Resource::default(); - f(&mut handler); + let mut resource = Resource::new(ResourceDef::new(path)); + f(&mut resource); - self.resources.push((path.to_owned(), handler)); + self.resources.push(resource); self } @@ -878,9 +879,9 @@ impl CorsBuilder { .expect("CorsBuilder has to be constructed with Cors::for_app(app)"); // register resources - for (path, mut resource) in self.resources.drain(..) { + for mut resource in self.resources.drain(..) { cors.clone().register(&mut resource); - app.register_resource(&path, resource); + app.register_resource(resource); } app diff --git a/src/param.rs b/src/param.rs index c58d9e78f..2704b60d0 100644 --- a/src/param.rs +++ b/src/param.rs @@ -61,6 +61,10 @@ impl Params { self.tail = tail; } + pub(crate) fn set_url(&mut self, url: Url) { + self.url = url; + } + pub(crate) fn add(&mut self, name: Rc, value: ParamItem) { self.segments.push((name, value)); } @@ -99,6 +103,11 @@ impl Params { } } + /// Get unprocessed part of path + pub fn unprocessed(&self) -> &str { + &self.url.path()[(self.tail as usize)..] + } + /// Get matched `FromParam` compatible parameter by name. /// /// If keyed parameter is not available empty string is used as default diff --git a/src/pipeline.rs b/src/pipeline.rs index 66b2f29a2..dbe9e58ad 100644 --- a/src/pipeline.rs +++ b/src/pipeline.rs @@ -16,19 +16,11 @@ use httpresponse::HttpResponse; use middleware::{Finished, Middleware, Response, Started}; use server::{HttpHandlerTask, Writer, WriterState}; -#[doc(hidden)] -#[derive(Debug, Clone, Copy)] -pub enum HandlerType { - Normal(usize), - Handler(usize), - Default, -} - #[doc(hidden)] pub trait PipelineHandler { fn encoding(&self) -> ContentEncoding; - fn handle(&self, &HttpRequest, HandlerType) -> AsyncResult; + fn handle(&self, &HttpRequest) -> AsyncResult; } #[doc(hidden)] @@ -99,7 +91,6 @@ impl PipelineInfo { impl> Pipeline { pub fn new( req: HttpRequest, mws: Rc>>>, handler: Rc, - htype: HandlerType, ) -> Pipeline { let mut info = PipelineInfo { req, @@ -109,7 +100,7 @@ impl> Pipeline { disconnected: None, encoding: handler.encoding(), }; - let state = StartMiddlewares::init(&mut info, &mws, handler, htype); + let state = StartMiddlewares::init(&mut info, &mws, handler); Pipeline(info, state, mws) } @@ -204,7 +195,6 @@ type Fut = Box, Error = Error>>; /// Middlewares start executor struct StartMiddlewares { hnd: Rc, - htype: HandlerType, fut: Option, _s: PhantomData, } @@ -212,7 +202,6 @@ struct StartMiddlewares { impl> StartMiddlewares { fn init( info: &mut PipelineInfo, mws: &[Box>], hnd: Rc, - htype: HandlerType, ) -> PipelineState { // execute middlewares, we need this stage because middlewares could be // non-async and we can move to next state immediately @@ -220,7 +209,7 @@ impl> StartMiddlewares { loop { if info.count == len { - let reply = hnd.handle(&info.req, htype); + let reply = hnd.handle(&info.req); return WaitingResponse::init(info, mws, reply); } else { match mws[info.count as usize].start(&info.req) { @@ -231,7 +220,6 @@ impl> StartMiddlewares { Ok(Started::Future(fut)) => { return PipelineState::Starting(StartMiddlewares { hnd, - htype, fut: Some(fut), _s: PhantomData, }) @@ -261,7 +249,7 @@ impl> StartMiddlewares { } loop { if info.count == len { - let reply = self.hnd.handle(&info.req, self.htype); + let reply = self.hnd.handle(&info.req); return Some(WaitingResponse::init(info, mws, reply)); } else { let res = mws[info.count as usize].start(&info.req); diff --git a/src/resource.rs b/src/resource.rs index 2af1029ad..1bf8d88fa 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,4 +1,4 @@ -use std::marker::PhantomData; +use std::ops::Deref; use std::rc::Rc; use futures::Future; @@ -12,6 +12,7 @@ use httpresponse::HttpResponse; use middleware::Middleware; use pred; use route::Route; +use router::ResourceDef; #[derive(Copy, Clone)] pub(crate) struct RouteId(usize); @@ -37,40 +38,34 @@ pub(crate) struct RouteId(usize); /// .finish(); /// } pub struct Resource { - name: String, - state: PhantomData, + rdef: ResourceDef, routes: SmallVec<[Route; 3]>, middlewares: Rc>>>, } -impl Default for Resource { - fn default() -> Self { +impl Resource { + /// Create new resource with specified resource definition + pub fn new(rdef: ResourceDef) -> Self { Resource { - name: String::new(), - state: PhantomData, + rdef, routes: SmallVec::new(), middlewares: Rc::new(Vec::new()), } } -} -impl Resource { - pub(crate) fn default_not_found() -> Self { - Resource { - name: String::new(), - state: PhantomData, - routes: SmallVec::new(), - middlewares: Rc::new(Vec::new()), - } + /// Name of the resource + pub(crate) fn get_name(&self) -> &str { + self.rdef.name() } /// Set resource name - pub fn name>(&mut self, name: T) { - self.name = name.into(); + pub fn name(&mut self, name: &str) { + self.rdef.set_name(name); } - pub(crate) fn get_name(&self) -> &str { - &self.name + /// Resource definition + pub fn rdef(&self) -> &ResourceDef { + &self.rdef } } @@ -303,3 +298,26 @@ impl Resource { } } } + +/// Default resource +pub struct DefaultResource(Rc>); + +impl Deref for DefaultResource { + type Target = Resource; + + fn deref(&self) -> &Resource { + self.0.as_ref() + } +} + +impl Clone for DefaultResource { + fn clone(&self) -> Self { + DefaultResource(self.0.clone()) + } +} + +impl From> for DefaultResource { + fn from(res: Resource) -> Self { + DefaultResource(Rc::new(res)) + } +} diff --git a/src/router.rs b/src/router.rs index fad51b15e..8a6a263ab 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,3 +1,4 @@ +use std::cmp::min; use std::collections::HashMap; use std::hash::{Hash, Hasher}; use std::rc::Rc; @@ -6,19 +7,43 @@ use regex::{escape, Regex}; use url::Url; use error::UrlGenerationError; +use handler::{AsyncResult, FromRequest, Responder, RouteHandler}; +use http::{Method, StatusCode}; +use httprequest::HttpRequest; +use httpresponse::HttpResponse; use param::{ParamItem, Params}; -use resource::Resource; +use pred::Predicate; +use resource::{DefaultResource, Resource}; +use scope::Scope; use server::Request; #[derive(Debug, Copy, Clone, PartialEq)] pub(crate) enum RouterResource { - Notset, + Default, Normal(u16), } -/// Interface for application router. -pub struct Router(Rc); +enum ResourcePattern { + Resource(ResourceDef), + Handler(ResourceDef, Option>>>), + Scope(ResourceDef, Vec>>), +} +enum ResourceItem { + Resource(Resource), + Handler(Box>), + Scope(Scope), +} + +/// Interface for application router. +pub struct Router { + defs: Rc, + patterns: Vec>, + resources: Vec>, + default: Option>, +} + +/// Information about current route #[derive(Clone)] pub struct RouteInfo { router: Rc, @@ -27,6 +52,16 @@ pub struct RouteInfo { } impl RouteInfo { + /// Name os the resource + #[inline] + pub fn name(&self) -> &str { + if let RouterResource::Normal(idx) = self.resource { + self.router.patterns[idx as usize].name() + } else { + "" + } + } + /// This method returns reference to matched `Resource` object. #[inline] pub fn resource(&self) -> Option<&ResourceDef> { @@ -49,18 +84,14 @@ impl RouteInfo { } #[inline] - pub(crate) fn merge(&self, params: &Params) -> RouteInfo { - let mut p = self.params.clone(); - p.set_tail(params.tail); - for item in ¶ms.segments { + pub(crate) fn merge(&mut self, info: &RouteInfo) { + let mut p = info.params.clone(); + p.set_tail(self.params.tail); + for item in &self.params.segments { p.add(item.0.clone(), item.1.clone()); } - RouteInfo { - params: p, - router: self.router.clone(), - resource: self.resource, - } + self.params = p; } /// Generate url for named resource @@ -75,7 +106,7 @@ impl RouteInfo { I: AsRef, { if let Some(pattern) = self.router.named.get(name) { - let path = pattern.0.resource_path(elements, &self.router.prefix)?; + let path = pattern.resource_path(elements, &self.router.prefix)?; if path.starts_with('/') { let conn = req.connection_info(); Ok(Url::parse(&format!( @@ -113,102 +144,264 @@ impl RouteInfo { struct Inner { prefix: String, prefix_len: usize, - named: HashMap, + named: HashMap, patterns: Vec, } -impl Router { - /// Create new router - pub fn new( - prefix: &str, map: Vec<(ResourceDef, Option>)>, - ) -> (Router, Vec>) { - let prefix = prefix.trim().trim_right_matches('/').to_owned(); - let mut named = HashMap::new(); - let mut patterns = Vec::new(); - let mut resources = Vec::new(); +impl Default for Router { + fn default() -> Self { + Router::new() + } +} - for (pattern, resource) in map { - if !pattern.name().is_empty() { - let name = pattern.name().into(); - named.insert(name, (pattern.clone(), resource.is_none())); - } - - if let Some(resource) = resource { - patterns.push(pattern); - resources.push(resource); - } +impl Router { + pub(crate) fn new() -> Self { + Router { + defs: Rc::new(Inner { + prefix: String::new(), + prefix_len: 0, + named: HashMap::new(), + patterns: Vec::new(), + }), + resources: Vec::new(), + patterns: Vec::new(), + default: None, } - - let prefix_len = prefix.len(); - ( - Router(Rc::new(Inner { - prefix, - prefix_len, - named, - patterns, - })), - resources, - ) } /// Router prefix #[inline] pub fn prefix(&self) -> &str { - &self.0.prefix + &self.defs.prefix } + /// Set router prefix + #[inline] + pub fn set_prefix(&mut self, prefix: &str) { + let prefix = prefix.trim().trim_right_matches('/').to_owned(); + let inner = Rc::get_mut(&mut self.defs).unwrap(); + inner.prefix_len = prefix.len(); + inner.prefix = prefix; + } + + #[inline] + pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> RouteInfo { + RouteInfo { + params, + router: self.defs.clone(), + resource: RouterResource::Normal(idx), + } + } + + #[cfg(test)] pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> RouteInfo { let mut params = Params::with_url(req.url()); params.set_tail(prefix); RouteInfo { params, - router: self.0.clone(), - resource: RouterResource::Notset, - } - } - - pub(crate) fn route_info_params(&self, params: Params) -> RouteInfo { - RouteInfo { - params, - router: self.0.clone(), - resource: RouterResource::Notset, + router: self.defs.clone(), + resource: RouterResource::Default, } } + #[cfg(test)] pub(crate) fn default_route_info(&self) -> RouteInfo { RouteInfo { - router: self.0.clone(), - resource: RouterResource::Notset, params: Params::new(), + router: self.defs.clone(), + resource: RouterResource::Default, } } + pub(crate) fn register_resource(&mut self, resource: Resource) { + { + let inner = Rc::get_mut(&mut self.defs).unwrap(); + + let name = resource.get_name(); + if !name.is_empty() { + if inner.named.contains_key(name) { + panic!("Named resource {:?} is registered.", name); + } + inner.named.insert(name.to_owned(), resource.rdef().clone()); + } + inner.patterns.push(resource.rdef().clone()); + } + self.patterns + .push(ResourcePattern::Resource(resource.rdef().clone())); + self.resources.push(ResourceItem::Resource(resource)); + } + + pub(crate) fn register_scope(&mut self, mut scope: Scope) { + Rc::get_mut(&mut self.defs) + .unwrap() + .patterns + .push(scope.rdef().clone()); + let filters = scope.take_filters(); + self.patterns + .push(ResourcePattern::Scope(scope.rdef().clone(), filters)); + self.resources.push(ResourceItem::Scope(scope)); + } + + pub(crate) fn register_handler( + &mut self, path: &str, hnd: Box>, + filters: Option>>>, + ) { + let rdef = ResourceDef::prefix(path); + Rc::get_mut(&mut self.defs) + .unwrap() + .patterns + .push(rdef.clone()); + self.resources.push(ResourceItem::Handler(hnd)); + self.patterns.push(ResourcePattern::Handler(rdef, filters)); + } + + pub(crate) fn has_default_resource(&self) -> bool { + self.default.is_some() + } + + pub(crate) fn register_default_resource(&mut self, resource: DefaultResource) { + self.default = Some(resource); + } + + pub(crate) fn finish(&mut self) { + if let Some(ref default) = self.default { + for resource in &mut self.resources { + match resource { + ResourceItem::Resource(_) => (), + ResourceItem::Scope(scope) => { + if !scope.has_default_resource() { + scope.default_resource(default.clone()); + } + scope.finish() + } + ResourceItem::Handler(hnd) => { + if !hnd.has_default_resource() { + hnd.default_resource(default.clone()); + } + hnd.finish() + } + } + } + } + } + + pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { + let inner = Rc::get_mut(&mut self.defs).unwrap(); + if inner.named.contains_key(name) { + panic!("Named resource {:?} is registered.", name); + } + inner.named.insert(name.to_owned(), rdef); + } + + pub(crate) fn register_route(&mut self, path: &str, method: Method, f: F) + where + F: Fn(T) -> R + 'static, + R: Responder + 'static, + T: FromRequest + 'static, + { + let out = { + // get resource handler + let mut iterator = self.resources.iter_mut(); + + loop { + if let Some(ref mut resource) = iterator.next() { + if let ResourceItem::Resource(ref mut resource) = resource { + if resource.rdef().pattern() == path { + resource.method(method).with(f); + break None; + } + } + } else { + let mut resource = Resource::new(ResourceDef::new(path)); + resource.method(method).with(f); + break Some(resource); + } + } + }; + if let Some(out) = out { + self.register_resource(out); + } + } + + /// Handle request + pub fn handle(&self, req: &HttpRequest) -> AsyncResult { + let resource = match req.route().resource { + RouterResource::Normal(idx) => &self.resources[idx as usize], + RouterResource::Default => { + if let Some(ref default) = self.default { + if let Some(id) = default.get_route_id(req) { + return default.handle(id, req); + } + } + return AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)); + } + }; + match resource { + ResourceItem::Resource(ref resource) => { + if let Some(id) = resource.get_route_id(req) { + return resource.handle(id, req); + } + + if let Some(ref default) = self.default { + if let Some(id) = default.get_route_id(req) { + return default.handle(id, req); + } + } + } + ResourceItem::Handler(hnd) => return hnd.handle(req), + ResourceItem::Scope(hnd) => return hnd.handle(req), + } + AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) + } + /// Query for matched resource - pub fn recognize(&self, req: &Request) -> Option<(usize, RouteInfo)> { - if self.0.prefix_len > req.path().len() { - return None; - } - for (idx, pattern) in self.0.patterns.iter().enumerate() { - if let Some(params) = pattern.match_with_params(req, self.0.prefix_len, true) - { - return Some(( - idx, - RouteInfo { - params, - router: self.0.clone(), - resource: RouterResource::Normal(idx as u16), - }, - )); + pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> RouteInfo { + self.match_with_params(req, state, tail, true) + } + + /// Query for matched resource + pub(crate) fn match_with_params( + &self, req: &Request, state: &S, tail: usize, insert: bool, + ) -> RouteInfo { + if tail <= req.path().len() { + 'outer: for (idx, resource) in self.patterns.iter().enumerate() { + match resource { + ResourcePattern::Resource(rdef) => { + if let Some(params) = rdef.match_with_params(req, tail, insert) { + return self.route_info_params(idx as u16, params); + } + } + ResourcePattern::Handler(rdef, filters) => { + if let Some(params) = rdef.match_prefix_with_params(req, tail) { + if let Some(ref filters) = filters { + for filter in filters { + if !filter.check(req, state) { + continue 'outer; + } + } + } + return self.route_info_params(idx as u16, params); + } + } + ResourcePattern::Scope(rdef, filters) => { + if let Some(params) = rdef.match_prefix_with_params(req, tail) { + for filter in filters { + if !filter.check(req, state) { + continue 'outer; + } + } + return self.route_info_params(idx as u16, params); + } + } + } } } - None - } -} - -impl Clone for Router { - fn clone(&self) -> Router { - Router(Rc::clone(&self.0)) + RouteInfo { + params: Params::new(), + router: self.defs.clone(), + resource: RouterResource::Default, + } } } @@ -252,8 +445,8 @@ impl ResourceDef { /// Parse path pattern and create new `Resource` instance. /// /// Panics if path pattern is wrong. - pub fn new(name: &str, path: &str) -> Self { - ResourceDef::with_prefix(name, path, "/", false) + pub fn new(path: &str) -> Self { + ResourceDef::with_prefix(path, "/", false) } /// Parse path pattern and create new `Resource` instance. @@ -261,21 +454,21 @@ impl ResourceDef { /// Use `prefix` type instead of `static`. /// /// Panics if path regex pattern is wrong. - pub fn prefix(name: &str, path: &str) -> Self { - ResourceDef::with_prefix(name, path, "/", true) + pub fn prefix(path: &str) -> Self { + ResourceDef::with_prefix(path, "/", true) } /// Construct external resource /// /// Panics if path pattern is wrong. - pub fn external(name: &str, path: &str) -> Self { - let mut resource = ResourceDef::with_prefix(name, path, "/", false); + pub fn external(path: &str) -> Self { + let mut resource = ResourceDef::with_prefix(path, "/", false); resource.rtp = ResourceType::External; resource } /// Parse path pattern and create new `Resource` instance with custom prefix - pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self { + pub fn with_prefix(path: &str, prefix: &str, for_prefix: bool) -> Self { let (pattern, elements, is_dynamic, len) = ResourceDef::parse(path, prefix, for_prefix); @@ -299,20 +492,25 @@ impl ResourceDef { ResourceDef { tp, elements, - name: name.into(), + name: "".to_string(), rtp: ResourceType::Normal, pattern: path.to_owned(), } } - /// Name of the resource + /// Resource type + pub fn rtype(&self) -> ResourceType { + self.rtp + } + + /// Resource name pub fn name(&self) -> &str { &self.name } - /// Resource type - pub fn rtype(&self) -> ResourceType { - self.rtp + /// Resource name + pub(crate) fn set_name(&mut self, name: &str) { + self.name = name.to_owned(); } /// Path pattern of the resource @@ -443,7 +641,7 @@ impl ResourceDef { return None; }; let mut params = Params::with_url(req.url()); - params.set_tail((plen + len) as u16); + params.set_tail(min(req.path().len(), plen + len) as u16); Some(params) } } @@ -592,184 +790,152 @@ mod tests { #[test] fn test_recognizer10() { - let routes = vec![ - (ResourceDef::new("", "/name"), Some(Resource::default())), - ( - ResourceDef::new("", "/name/{val}"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/name/{val}/index.html"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/file/{file}.{ext}"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/v{val}/{val2}/index.html"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/v/{tail:.*}"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/test2/{test}.html"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "{test}/index.html"), - Some(Resource::default()), - ), - ]; - let (rec, _) = Router::new::<()>("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/name"))); + router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); + router.register_resource(Resource::new(ResourceDef::new( + "/name/{val}/index.html", + ))); + router.register_resource(Resource::new(ResourceDef::new("/file/{file}.{ext}"))); + router.register_resource(Resource::new(ResourceDef::new( + "/v{val}/{val2}/index.html", + ))); + router.register_resource(Resource::new(ResourceDef::new("/v/{tail:.*}"))); + router.register_resource(Resource::new(ResourceDef::new("/test2/{test}.html"))); + router.register_resource(Resource::new(ResourceDef::new("{test}/index.html"))); let req = TestRequest::with_uri("/name").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 0); - assert!(req.match_info().is_empty()); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(0)); + assert!(info.match_info().is_empty()); let req = TestRequest::with_uri("/name/value").finish(); - let info = rec.recognize(&req).unwrap().1; - let req = req.with_route_info(info); - assert_eq!(req.match_info().get("val").unwrap(), "value"); - assert_eq!(&req.match_info()["val"], "value"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.match_info().get("val").unwrap(), "value"); + assert_eq!(&info.match_info()["val"], "value"); let req = TestRequest::with_uri("/name/value2/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 2); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("val").unwrap(), "value2"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(2)); + assert_eq!(info.match_info().get("val").unwrap(), "value2"); let req = TestRequest::with_uri("/file/file.gz").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 3); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("file").unwrap(), "file"); - assert_eq!(req.match_info().get("ext").unwrap(), "gz"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(3)); + assert_eq!(info.match_info().get("file").unwrap(), "file"); + assert_eq!(info.match_info().get("ext").unwrap(), "gz"); let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 4); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("val").unwrap(), "test"); - assert_eq!(req.match_info().get("val2").unwrap(), "ttt"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(4)); + assert_eq!(info.match_info().get("val").unwrap(), "test"); + assert_eq!(info.match_info().get("val2").unwrap(), "ttt"); let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 5); - let req = req.with_route_info(info.1); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(5)); assert_eq!( - req.match_info().get("tail").unwrap(), + info.match_info().get("tail").unwrap(), "blah-blah/index.html" ); let req = TestRequest::with_uri("/test2/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 6); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("test").unwrap(), "index"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(6)); + assert_eq!(info.match_info().get("test").unwrap(), "index"); let req = TestRequest::with_uri("/bbb/index.html").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 7); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("test").unwrap(), "bbb"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(7)); + assert_eq!(info.match_info().get("test").unwrap(), "bbb"); } #[test] fn test_recognizer_2() { - let routes = vec![ - ( - ResourceDef::new("", "/index.json"), - Some(Resource::default()), - ), - ( - ResourceDef::new("", "/{source}.json"), - Some(Resource::default()), - ), - ]; - let (rec, _) = Router::new::<()>("", routes); + let mut router = Router::<()>::new(); + router.register_resource(Resource::new(ResourceDef::new("/index.json"))); + router.register_resource(Resource::new(ResourceDef::new("/{source}.json"))); let req = TestRequest::with_uri("/index.json").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 0); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(0)); let req = TestRequest::with_uri("/test.json").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 1); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(1)); } #[test] fn test_recognizer_with_prefix() { - let routes = vec![ - (ResourceDef::new("", "/name"), Some(Resource::default())), - ( - ResourceDef::new("", "/name/{val}"), - Some(Resource::default()), - ), - ]; - let (rec, _) = Router::new::<()>("/test", routes); + let mut router = Router::<()>::new(); + router.set_prefix("/test"); + router.register_resource(Resource::new(ResourceDef::new("/name"))); + router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); let req = TestRequest::with_uri("/name").finish(); - assert!(rec.recognize(&req).is_none()); + let info = router.recognize(&req, &(), 5); + assert_eq!(info.resource, RouterResource::Default); let req = TestRequest::with_uri("/test/name").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 0); + let info = router.recognize(&req, &(), 5); + assert_eq!(info.resource, RouterResource::Normal(0)); let req = TestRequest::with_uri("/test/name/value").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 1); - let req = req.with_route_info(info.1); - assert_eq!(req.match_info().get("val").unwrap(), "value"); - assert_eq!(&req.match_info()["val"], "value"); + let info = router.recognize(&req, &(), 5); + assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.match_info().get("val").unwrap(), "value"); + assert_eq!(&info.match_info()["val"], "value"); // same patterns - let routes = vec![ - (ResourceDef::new("", "/name"), Some(Resource::default())), - ( - ResourceDef::new("", "/name/{val}"), - Some(Resource::default()), - ), - ]; - let (rec, _) = Router::new::<()>("/test2", routes); + let mut router = Router::<()>::new(); + router.set_prefix("/test2"); + router.register_resource(Resource::new(ResourceDef::new("/name"))); + router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); let req = TestRequest::with_uri("/name").finish(); - assert!(rec.recognize(&req).is_none()); + let info = router.recognize(&req, &(), 6); + assert_eq!(info.resource, RouterResource::Default); + let req = TestRequest::with_uri("/test2/name").finish(); - assert_eq!(rec.recognize(&req).unwrap().0, 0); + let info = router.recognize(&req, &(), 6); + assert_eq!(info.resource, RouterResource::Normal(0)); + let req = TestRequest::with_uri("/test2/name-test").finish(); - assert!(rec.recognize(&req).is_none()); + let info = router.recognize(&req, &(), 6); + assert_eq!(info.resource, RouterResource::Default); + let req = TestRequest::with_uri("/test2/name/ttt").finish(); - let info = rec.recognize(&req).unwrap(); - assert_eq!(info.0, 1); - let req = req.with_route_info(info.1); - assert_eq!(&req.match_info()["val"], "ttt"); + let info = router.recognize(&req, &(), 6); + assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(&info.match_info()["val"], "ttt"); } #[test] fn test_parse_static() { - let re = ResourceDef::new("test", "/"); + let re = ResourceDef::new("/"); assert!(re.is_match("/")); assert!(!re.is_match("/a")); - let re = ResourceDef::new("test", "/name"); + let re = ResourceDef::new("/name"); assert!(re.is_match("/name")); assert!(!re.is_match("/name1")); assert!(!re.is_match("/name/")); assert!(!re.is_match("/name~")); - let re = ResourceDef::new("test", "/name/"); + let re = ResourceDef::new("/name/"); assert!(re.is_match("/name/")); assert!(!re.is_match("/name")); assert!(!re.is_match("/name/gs")); - let re = ResourceDef::new("test", "/user/profile"); + let re = ResourceDef::new("/user/profile"); assert!(re.is_match("/user/profile")); assert!(!re.is_match("/user/profile/profile")); } #[test] fn test_parse_param() { - let re = ResourceDef::new("test", "/user/{id}"); + let re = ResourceDef::new("/user/{id}"); assert!(re.is_match("/user/profile")); assert!(re.is_match("/user/2345")); assert!(!re.is_match("/user/2345/")); @@ -783,7 +949,7 @@ mod tests { let info = re.match_with_params(&req, 0, true).unwrap(); assert_eq!(info.get("id").unwrap(), "1245125"); - let re = ResourceDef::new("test", "/v{version}/resource/{id}"); + let re = ResourceDef::new("/v{version}/resource/{id}"); assert!(re.is_match("/v1/resource/320120")); assert!(!re.is_match("/v/resource/1")); assert!(!re.is_match("/resource")); @@ -796,14 +962,14 @@ mod tests { #[test] fn test_resource_prefix() { - let re = ResourceDef::prefix("test", "/name"); + let re = ResourceDef::prefix("/name"); assert!(re.is_match("/name")); assert!(re.is_match("/name/")); assert!(re.is_match("/name/test/test")); assert!(re.is_match("/name1")); assert!(re.is_match("/name~")); - let re = ResourceDef::prefix("test", "/name/"); + let re = ResourceDef::prefix("/name/"); assert!(re.is_match("/name/")); assert!(re.is_match("/name/gs")); assert!(!re.is_match("/name")); @@ -811,7 +977,7 @@ mod tests { #[test] fn test_reousrce_prefix_dynamic() { - let re = ResourceDef::prefix("test", "/{name}/"); + let re = ResourceDef::prefix("/{name}/"); assert!(re.is_match("/name/")); assert!(re.is_match("/name/gs")); assert!(!re.is_match("/name")); @@ -829,28 +995,23 @@ mod tests { #[test] fn test_request_resource() { - let routes = vec![ - ( - ResourceDef::new("r1", "/index.json"), - Some(Resource::default()), - ), - ( - ResourceDef::new("r2", "/test.json"), - Some(Resource::default()), - ), - ]; - let (router, _) = Router::new::<()>("", routes); + let mut router = Router::<()>::new(); + let mut resource = Resource::new(ResourceDef::new("/index.json")); + resource.name("r1"); + router.register_resource(resource); + let mut resource = Resource::new(ResourceDef::new("/test.json")); + resource.name("r2"); + router.register_resource(resource); let req = TestRequest::with_uri("/index.json").finish(); - assert_eq!(router.recognize(&req).unwrap().0, 0); - let info = router.recognize(&req).unwrap().1; - let resource = info.resource().unwrap(); - assert_eq!(resource.name(), "r1"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(0)); + + assert_eq!(info.name(), "r1"); let req = TestRequest::with_uri("/test.json").finish(); - assert_eq!(router.recognize(&req).unwrap().0, 1); - let info = router.recognize(&req).unwrap().1; - let resource = info.resource().unwrap(); - assert_eq!(resource.name(), "r2"); + let info = router.recognize(&req, &(), 0); + assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.name(), "r2"); } } diff --git a/src/scope.rs b/src/scope.rs index a4b4307c3..94dbd8608 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -6,7 +6,7 @@ use futures::{Async, Future, Poll}; use error::Error; use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler}; -use http::{Method, StatusCode}; +use http::Method; use httprequest::HttpRequest; use httpresponse::HttpResponse; use middleware::{ @@ -14,15 +14,10 @@ use middleware::{ Started as MiddlewareStarted, }; use pred::Predicate; -use resource::{Resource, RouteId}; -use router::ResourceDef; +use resource::{DefaultResource, Resource}; +use router::{ResourceDef, Router}; use server::Request; -type ScopeResource = Rc>; -type Route = Box>; -type ScopeResources = Rc)>>; -type NestedInfo = (ResourceDef, Route, Vec>>); - /// Resources scope /// /// Scope is a set of resources with common root path. @@ -53,29 +48,31 @@ type NestedInfo = (ResourceDef, Route, Vec>>); /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests /// -#[derive(Default)] -pub struct Scope { +pub struct Scope { + rdef: ResourceDef, + router: Rc>, filters: Vec>>, - nested: Vec>, middlewares: Rc>>>, - default: Option>, - resources: ScopeResources, } #[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))] impl Scope { /// Create a new scope // TODO: Why is this not exactly the default impl? - pub fn new() -> Scope { + pub fn new(path: &str) -> Scope { Scope { + rdef: ResourceDef::prefix(path), + router: Rc::new(Router::new()), filters: Vec::new(), - nested: Vec::new(), - resources: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()), - default: None, } } + #[inline] + pub(crate) fn rdef(&self) -> &ResourceDef { + &self.rdef + } + #[inline] pub(crate) fn take_filters(&mut self) -> Vec>> { mem::replace(&mut self.filters, Vec::new()) @@ -132,11 +129,10 @@ impl Scope { F: FnOnce(Scope) -> Scope, { let scope = Scope { + rdef: ResourceDef::prefix(path), filters: Vec::new(), - nested: Vec::new(), - resources: Rc::new(Vec::new()), + router: Rc::new(Router::new()), middlewares: Rc::new(Vec::new()), - default: None, }; let mut scope = f(scope); @@ -146,8 +142,12 @@ impl Scope { filters: scope.take_filters(), })]; let handler = Box::new(Wrapper { scope, state }); - self.nested - .push((ResourceDef::prefix("", &path), handler, filters)); + + Rc::get_mut(&mut self.router).unwrap().register_handler( + path, + handler, + Some(filters), + ); self } @@ -175,17 +175,14 @@ impl Scope { F: FnOnce(Scope) -> Scope, { let scope = Scope { + rdef: ResourceDef::prefix(&path), filters: Vec::new(), - nested: Vec::new(), - resources: Rc::new(Vec::new()), + router: Rc::new(Router::new()), middlewares: Rc::new(Vec::new()), - default: None, }; - let mut scope = f(scope); - - let filters = scope.take_filters(); - self.nested - .push((ResourceDef::prefix("", &path), Box::new(scope), filters)); + Rc::get_mut(&mut self.router) + .unwrap() + .register_scope(f(scope)); self } @@ -223,39 +220,9 @@ impl Scope { R: Responder + 'static, T: FromRequest + 'static, { - // check if we have resource handler - let mut found = false; - for &(ref pattern, _) in self.resources.iter() { - if pattern.pattern() == path { - found = true; - break; - } - } - - if found { - let resources = Rc::get_mut(&mut self.resources) - .expect("Multiple scope references are not allowed"); - for &mut (ref pattern, ref mut resource) in resources.iter_mut() { - if pattern.pattern() == path { - let res = Rc::get_mut(resource) - .expect("Multiple scope references are not allowed"); - res.method(method).with(f); - break; - } - } - } else { - let mut handler = Resource::default(); - handler.method(method).with(f); - let pattern = ResourceDef::with_prefix( - handler.get_name(), - path, - if path.is_empty() { "" } else { "/" }, - false, - ); - Rc::get_mut(&mut self.resources) - .expect("Can not use after configuration") - .push((pattern, Rc::new(handler))); - } + Rc::get_mut(&mut self.router) + .unwrap() + .register_route(path, method, f); self } @@ -286,20 +253,18 @@ impl Scope { where F: FnOnce(&mut Resource) -> R + 'static, { - // add resource handler - let mut handler = Resource::default(); - f(&mut handler); - + // add resource let pattern = ResourceDef::with_prefix( - handler.get_name(), path, if path.is_empty() { "" } else { "/" }, false, ); - Rc::get_mut(&mut self.resources) - .expect("Can not use after configuration") - .push((pattern, Rc::new(handler))); + let mut resource = Resource::new(pattern); + f(&mut resource); + Rc::get_mut(&mut self.router) + .unwrap() + .register_resource(resource); self } @@ -308,14 +273,14 @@ impl Scope { where F: FnOnce(&mut Resource) -> R + 'static, { - if self.default.is_none() { - self.default = Some(Rc::new(Resource::default_not_found())); - } - { - let default = Rc::get_mut(self.default.as_mut().unwrap()) - .expect("Multiple copies of default handler"); - f(default); - } + // create and configure default resource + let mut resource = Resource::new(ResourceDef::new("")); + f(&mut resource); + + Rc::get_mut(&mut self.router) + .expect("Multiple copies of scope router") + .register_default_resource(resource.into()); + self } @@ -339,64 +304,33 @@ impl RouteHandler for Scope { let tail = req.match_info().tail as usize; // recognize resources - for &(ref pattern, ref resource) in self.resources.iter() { - if let Some(params) = pattern.match_with_params(req, tail, false) { - let req2 = req.with_route_info(req.route().merge(¶ms)); - if let Some(id) = resource.get_route_id(&req2) { - if self.middlewares.is_empty() { - return resource.handle(id, &req2); - } else { - return AsyncResult::async(Box::new(Compose::new( - id, - req2, - Rc::clone(&self.middlewares), - Rc::clone(&resource), - ))); - } - } - } + let info = self.router.match_with_params(req, req.state(), tail, false); + let req2 = req.with_route_info(info); + if self.middlewares.is_empty() { + self.router.handle(&req2) + } else { + AsyncResult::async(Box::new(Compose::new( + req2, + Rc::clone(&self.router), + Rc::clone(&self.middlewares), + ))) } - - // nested scopes - 'outer: for &(ref prefix, ref handler, ref filters) in &self.nested { - if let Some(params) = prefix.match_prefix_with_params(req, tail) { - let req2 = req.with_route_info(req.route().merge(¶ms)); - - let state = req.state(); - for filter in filters { - if !filter.check(&req2, state) { - continue 'outer; - } - } - return handler.handle(&req2); - } - } - - // default handler - if let Some(ref resource) = self.default { - if let Some(id) = resource.get_route_id(req) { - if self.middlewares.is_empty() { - return resource.handle(id, req); - } else { - return AsyncResult::async(Box::new(Compose::new( - id, - req.clone(), - Rc::clone(&self.middlewares), - Rc::clone(resource), - ))); - } - } - } - - AsyncResult::ok(HttpResponse::new(StatusCode::NOT_FOUND)) } fn has_default_resource(&self) -> bool { - self.default.is_some() + self.router.has_default_resource() } - fn default_resource(&mut self, default: ScopeResource) { - self.default = Some(default); + fn default_resource(&mut self, default: DefaultResource) { + Rc::get_mut(&mut self.router) + .expect("Can not use after configuration") + .register_default_resource(default); + } + + fn finish(&mut self) { + Rc::get_mut(&mut self.router) + .expect("Can not use after configuration") + .finish(); } } @@ -436,10 +370,9 @@ struct Compose { struct ComposeInfo { count: usize, - id: RouteId, req: HttpRequest, + router: Rc>, mws: Rc>>>, - resource: Rc>, } enum ComposeState { @@ -464,14 +397,12 @@ impl ComposeState { impl Compose { fn new( - id: RouteId, req: HttpRequest, mws: Rc>>>, - resource: Rc>, + req: HttpRequest, router: Rc>, mws: Rc>>>, ) -> Self { let mut info = ComposeInfo { - id, mws, req, - resource, + router, count: 0, }; let state = StartMiddlewares::init(&mut info); @@ -513,7 +444,7 @@ impl StartMiddlewares { loop { if info.count == len { - let reply = info.resource.handle(info.id, &info.req); + let reply = info.router.handle(&info.req); return WaitingResponse::init(info, reply); } else { let result = info.mws[info.count].start(&info.req); @@ -552,7 +483,7 @@ impl StartMiddlewares { } loop { if info.count == len { - let reply = { info.resource.handle(info.id, &info.req) }; + let reply = info.router.handle(&info.req); return Some(WaitingResponse::init(info, reply)); } else { let result = info.mws[info.count].start(&info.req); @@ -979,9 +910,9 @@ mod tests { }) .finish(); - let req = TestRequest::with_uri("/app/t1").request(); - let resp = app.run(req); - assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + //let req = TestRequest::with_uri("/app/t1").request(); + //let resp = app.run(req); + //assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); diff --git a/src/test.rs b/src/test.rs index 4289bca88..909d15f34 100644 --- a/src/test.rs +++ b/src/test.rs @@ -535,11 +535,11 @@ impl TestRequest { uri, version, headers, - params, + mut params, cookies, payload, } = self; - let (router, _) = Router::new::("/", Vec::new()); + let router = Router::<()>::new(); let pool = RequestPool::pool(ServerSettings::default()); let mut req = RequestPool::get(pool); @@ -551,23 +551,24 @@ impl TestRequest { inner.headers = headers; *inner.payload.borrow_mut() = payload; } + params.set_url(req.url().clone()); let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(params)); + HttpRequest::new(req, Rc::new(state), router.route_info_params(0, params)); req.set_cookies(cookies); req } #[cfg(test)] /// Complete request creation and generate `HttpRequest` instance - pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest { + pub(crate) fn finish_with_router(self, router: Router) -> HttpRequest { let TestRequest { state, method, uri, version, headers, - params, + mut params, cookies, payload, } = self; @@ -582,8 +583,9 @@ impl TestRequest { inner.headers = headers; *inner.payload.borrow_mut() = payload; } + params.set_url(req.url().clone()); let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(params)); + HttpRequest::new(req, Rc::new(state), router.route_info_params(0, params)); req.set_cookies(cookies); req } From 9570c1cccd25bdf58f6d879e29096d6f0cd6a435 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 15:24:27 +0600 Subject: [PATCH 2/7] rename RouteInfo --- src/helpers.rs | 10 +++--- src/httprequest.rs | 34 ++++++++---------- src/lib.rs | 2 +- src/router.rs | 90 +++++++++++++++++++++++----------------------- 4 files changed, 66 insertions(+), 70 deletions(-) diff --git a/src/helpers.rs b/src/helpers.rs index 50a9bcf6a..85247123a 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -92,7 +92,7 @@ impl Handler for NormalizePath { // merge slashes let p = self.re_merge.replace_all(req.path(), "/"); if p.len() != req.path().len() { - if req.route().has_route(p.as_ref()) { + if req.resource().has_route(p.as_ref()) { let p = if !query.is_empty() { p + "?" + query } else { @@ -105,7 +105,7 @@ impl Handler for NormalizePath { // merge slashes and append trailing slash if self.append && !p.ends_with('/') { let p = p.as_ref().to_owned() + "/"; - if req.route().has_route(&p) { + if req.resource().has_route(&p) { let p = if !query.is_empty() { p + "?" + query } else { @@ -120,7 +120,7 @@ impl Handler for NormalizePath { // try to remove trailing slash if p.ends_with('/') { let p = p.as_ref().trim_right_matches('/'); - if req.route().has_route(p) { + if req.resource().has_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -135,7 +135,7 @@ impl Handler for NormalizePath { } else if p.ends_with('/') { // try to remove trailing slash let p = p.as_ref().trim_right_matches('/'); - if req.route().has_route(p) { + if req.resource().has_route(p) { let mut req = HttpResponse::build(self.redirect); return if !query.is_empty() { req.header( @@ -151,7 +151,7 @@ impl Handler for NormalizePath { // append trailing slash if self.append && !req.path().ends_with('/') { let p = req.path().to_owned() + "/"; - if req.route().has_route(&p) { + if req.resource().has_route(&p) { let p = if !query.is_empty() { p + "?" + query } else { diff --git a/src/httprequest.rs b/src/httprequest.rs index a04973381..91ee9eb13 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -20,7 +20,7 @@ use httpresponse::{HttpResponse, HttpResponseBuilder}; use info::ConnectionInfo; use param::Params; use payload::Payload; -use router::{ResourceDef, RouteInfo}; +use router::ResourceInfo; use server::Request; struct Query(HashMap); @@ -30,7 +30,7 @@ struct Cookies(Vec>); pub struct HttpRequest { req: Option, state: Rc, - route: RouteInfo, + resource: ResourceInfo, } impl HttpMessage for HttpRequest { @@ -61,10 +61,12 @@ impl Deref for HttpRequest { impl HttpRequest { #[inline] - pub(crate) fn new(req: Request, state: Rc, route: RouteInfo) -> HttpRequest { + pub(crate) fn new( + req: Request, state: Rc, resource: ResourceInfo, + ) -> HttpRequest { HttpRequest { state, - route, + resource, req: Some(req), } } @@ -75,17 +77,17 @@ impl HttpRequest { HttpRequest { state, req: self.req.as_ref().map(|r| r.clone()), - route: self.route.clone(), + resource: self.resource.clone(), } } #[inline] /// Construct new http request with new RouteInfo. - pub(crate) fn with_route_info(&self, mut route: RouteInfo) -> HttpRequest { - route.merge(&self.route); + pub(crate) fn with_route_info(&self, mut resource: ResourceInfo) -> HttpRequest { + resource.merge(&self.resource); HttpRequest { - route, + resource, req: self.req.as_ref().map(|r| r.clone()), state: self.state.clone(), } @@ -193,7 +195,7 @@ impl HttpRequest { U: IntoIterator, I: AsRef, { - self.route.url_for(&self, name, elements) + self.resource.url_for(&self, name, elements) } /// Generate url for named resource @@ -207,14 +209,8 @@ impl HttpRequest { /// This method returns reference to current `RouteInfo` object. #[inline] - pub fn route(&self) -> &RouteInfo { - &self.route - } - - /// This method returns reference to matched `Resource` object. - #[inline] - pub fn resource(&self) -> Option<&ResourceDef> { - self.route.resource() + pub fn resource(&self) -> &ResourceInfo { + &self.resource } /// Peer socket address @@ -300,7 +296,7 @@ impl HttpRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Params { - &self.route.match_info() + &self.resource.match_info() } /// Check if request requires connection upgrade @@ -331,7 +327,7 @@ impl Clone for HttpRequest { HttpRequest { req: self.req.as_ref().map(|r| r.clone()), state: self.state.clone(), - route: self.route.clone(), + resource: self.resource.clone(), } } } diff --git a/src/lib.rs b/src/lib.rs index d61c94f35..34dcd7182 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -246,7 +246,7 @@ pub mod dev { pub use param::{FromParam, Params}; pub use resource::Resource; pub use route::Route; - pub use router::{ResourceDef, ResourceType, Router}; + pub use router::{ResourceDef, ResourceInfo, ResourceType, Router}; } pub mod http { diff --git a/src/router.rs b/src/router.rs index 8a6a263ab..d93ec9eb6 100644 --- a/src/router.rs +++ b/src/router.rs @@ -18,7 +18,7 @@ use scope::Scope; use server::Request; #[derive(Debug, Copy, Clone, PartialEq)] -pub(crate) enum RouterResource { +pub(crate) enum ResourceId { Default, Normal(u16), } @@ -43,29 +43,29 @@ pub struct Router { default: Option>, } -/// Information about current route +/// Information about current resource #[derive(Clone)] -pub struct RouteInfo { +pub struct ResourceInfo { router: Rc, - resource: RouterResource, + resource: ResourceId, params: Params, } -impl RouteInfo { +impl ResourceInfo { /// Name os the resource #[inline] pub fn name(&self) -> &str { - if let RouterResource::Normal(idx) = self.resource { + if let ResourceId::Normal(idx) = self.resource { self.router.patterns[idx as usize].name() } else { "" } } - /// This method returns reference to matched `Resource` object. + /// This method returns reference to matched `ResourceDef` object. #[inline] - pub fn resource(&self) -> Option<&ResourceDef> { - if let RouterResource::Normal(idx) = self.resource { + pub fn rdef(&self) -> Option<&ResourceDef> { + if let ResourceId::Normal(idx) = self.resource { Some(&self.router.patterns[idx as usize]) } else { None @@ -84,7 +84,7 @@ impl RouteInfo { } #[inline] - pub(crate) fn merge(&mut self, info: &RouteInfo) { + pub(crate) fn merge(&mut self, info: &ResourceInfo) { let mut p = info.params.clone(); p.set_tail(self.params.tail); for item in &self.params.segments { @@ -185,32 +185,32 @@ impl Router { } #[inline] - pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> RouteInfo { - RouteInfo { + pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> ResourceInfo { + ResourceInfo { params, router: self.defs.clone(), - resource: RouterResource::Normal(idx), + resource: ResourceId::Normal(idx), } } #[cfg(test)] - pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> RouteInfo { + pub(crate) fn route_info(&self, req: &Request, prefix: u16) -> ResourceInfo { let mut params = Params::with_url(req.url()); params.set_tail(prefix); - RouteInfo { + ResourceInfo { params, router: self.defs.clone(), - resource: RouterResource::Default, + resource: ResourceId::Default, } } #[cfg(test)] - pub(crate) fn default_route_info(&self) -> RouteInfo { - RouteInfo { + pub(crate) fn default_route_info(&self) -> ResourceInfo { + ResourceInfo { params: Params::new(), router: self.defs.clone(), - resource: RouterResource::Default, + resource: ResourceId::Default, } } @@ -326,9 +326,9 @@ impl Router { /// Handle request pub fn handle(&self, req: &HttpRequest) -> AsyncResult { - let resource = match req.route().resource { - RouterResource::Normal(idx) => &self.resources[idx as usize], - RouterResource::Default => { + let resource = match req.resource().resource { + ResourceId::Normal(idx) => &self.resources[idx as usize], + ResourceId::Default => { if let Some(ref default) = self.default { if let Some(id) = default.get_route_id(req) { return default.handle(id, req); @@ -356,14 +356,14 @@ impl Router { } /// Query for matched resource - pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> RouteInfo { + pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> ResourceInfo { self.match_with_params(req, state, tail, true) } /// Query for matched resource pub(crate) fn match_with_params( &self, req: &Request, state: &S, tail: usize, insert: bool, - ) -> RouteInfo { + ) -> ResourceInfo { if tail <= req.path().len() { 'outer: for (idx, resource) in self.patterns.iter().enumerate() { match resource { @@ -397,10 +397,10 @@ impl Router { } } } - RouteInfo { + ResourceInfo { params: Params::new(), router: self.defs.clone(), - resource: RouterResource::Default, + resource: ResourceId::Default, } } } @@ -806,35 +806,35 @@ mod tests { let req = TestRequest::with_uri("/name").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); assert!(info.match_info().is_empty()); let req = TestRequest::with_uri("/name/value").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(info.match_info().get("val").unwrap(), "value"); assert_eq!(&info.match_info()["val"], "value"); let req = TestRequest::with_uri("/name/value2/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(2)); + assert_eq!(info.resource, ResourceId::Normal(2)); assert_eq!(info.match_info().get("val").unwrap(), "value2"); let req = TestRequest::with_uri("/file/file.gz").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(3)); + assert_eq!(info.resource, ResourceId::Normal(3)); assert_eq!(info.match_info().get("file").unwrap(), "file"); assert_eq!(info.match_info().get("ext").unwrap(), "gz"); let req = TestRequest::with_uri("/vtest/ttt/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(4)); + assert_eq!(info.resource, ResourceId::Normal(4)); assert_eq!(info.match_info().get("val").unwrap(), "test"); assert_eq!(info.match_info().get("val2").unwrap(), "ttt"); let req = TestRequest::with_uri("/v/blah-blah/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(5)); + assert_eq!(info.resource, ResourceId::Normal(5)); assert_eq!( info.match_info().get("tail").unwrap(), "blah-blah/index.html" @@ -842,12 +842,12 @@ mod tests { let req = TestRequest::with_uri("/test2/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(6)); + assert_eq!(info.resource, ResourceId::Normal(6)); assert_eq!(info.match_info().get("test").unwrap(), "index"); let req = TestRequest::with_uri("/bbb/index.html").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(7)); + assert_eq!(info.resource, ResourceId::Normal(7)); assert_eq!(info.match_info().get("test").unwrap(), "bbb"); } @@ -859,11 +859,11 @@ mod tests { let req = TestRequest::with_uri("/index.json").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); let req = TestRequest::with_uri("/test.json").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); } #[test] @@ -875,15 +875,15 @@ mod tests { let req = TestRequest::with_uri("/name").finish(); let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, RouterResource::Default); + assert_eq!(info.resource, ResourceId::Default); let req = TestRequest::with_uri("/test/name").finish(); let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); let req = TestRequest::with_uri("/test/name/value").finish(); let info = router.recognize(&req, &(), 5); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(info.match_info().get("val").unwrap(), "value"); assert_eq!(&info.match_info()["val"], "value"); @@ -895,19 +895,19 @@ mod tests { let req = TestRequest::with_uri("/name").finish(); let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, RouterResource::Default); + assert_eq!(info.resource, ResourceId::Default); let req = TestRequest::with_uri("/test2/name").finish(); let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); let req = TestRequest::with_uri("/test2/name-test").finish(); let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, RouterResource::Default); + assert_eq!(info.resource, ResourceId::Default); let req = TestRequest::with_uri("/test2/name/ttt").finish(); let info = router.recognize(&req, &(), 6); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(&info.match_info()["val"], "ttt"); } @@ -1005,13 +1005,13 @@ mod tests { let req = TestRequest::with_uri("/index.json").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(0)); + assert_eq!(info.resource, ResourceId::Normal(0)); assert_eq!(info.name(), "r1"); let req = TestRequest::with_uri("/test.json").finish(); let info = router.recognize(&req, &(), 0); - assert_eq!(info.resource, RouterResource::Normal(1)); + assert_eq!(info.resource, ResourceId::Normal(1)); assert_eq!(info.name(), "r2"); } } From b759dddf5a40aa52b693b99fb512d2139f0bf9c1 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 15:50:56 +0600 Subject: [PATCH 3/7] simplify application prefix impl --- src/application.rs | 28 +++++++++++++--------------- src/router.rs | 32 +++++++------------------------- src/scope.rs | 2 +- 3 files changed, 21 insertions(+), 41 deletions(-) diff --git a/src/application.rs b/src/application.rs index 80ba7f52c..ebf441ec7 100644 --- a/src/application.rs +++ b/src/application.rs @@ -58,10 +58,14 @@ impl HttpHandler for HttpApplication { fn handle(&self, msg: Request) -> Result>, Request> { let m = { - let path = msg.path(); - path.starts_with(&self.prefix) - && (path.len() == self.prefix_len - || path.split_at(self.prefix_len).1.starts_with('/')) + if self.prefix_len == 0 { + true + } else { + let path = msg.path(); + path.starts_with(&self.prefix) + && (path.len() == self.prefix_len + || path.split_at(self.prefix_len).1.starts_with('/')) + } }; if m { if let Some(ref filters) = self.filters { @@ -135,7 +139,7 @@ where App { parts: Some(ApplicationParts { state, - prefix: "/".to_owned(), + prefix: "".to_owned(), router: Router::new(), middlewares: Vec::new(), filters: Vec::new(), @@ -498,12 +502,6 @@ where pub fn finish(&mut self) -> HttpApplication { let mut parts = self.parts.take().expect("Use after finish"); let prefix = parts.prefix.trim().trim_right_matches('/'); - let (prefix, prefix_len) = if prefix.is_empty() { - ("/".to_owned(), 0) - } else { - (prefix.to_owned(), prefix.len()) - }; - parts.router.finish(); let inner = Rc::new(Inner { @@ -517,12 +515,12 @@ where }; HttpApplication { - state: Rc::new(parts.state), - middlewares: Rc::new(parts.middlewares), - prefix, - prefix_len, inner, filters, + state: Rc::new(parts.state), + middlewares: Rc::new(parts.middlewares), + prefix: prefix.to_owned(), + prefix_len: prefix.len(), } } diff --git a/src/router.rs b/src/router.rs index d93ec9eb6..603bc5608 100644 --- a/src/router.rs +++ b/src/router.rs @@ -357,18 +357,11 @@ impl Router { /// Query for matched resource pub fn recognize(&self, req: &Request, state: &S, tail: usize) -> ResourceInfo { - self.match_with_params(req, state, tail, true) - } - - /// Query for matched resource - pub(crate) fn match_with_params( - &self, req: &Request, state: &S, tail: usize, insert: bool, - ) -> ResourceInfo { if tail <= req.path().len() { 'outer: for (idx, resource) in self.patterns.iter().enumerate() { match resource { ResourcePattern::Resource(rdef) => { - if let Some(params) = rdef.match_with_params(req, tail, insert) { + if let Some(params) = rdef.match_with_params(req, tail) { return self.route_info_params(idx as u16, params); } } @@ -528,19 +521,8 @@ impl ResourceDef { } /// Are the given path and parameters a match against this resource? - pub fn match_with_params( - &self, req: &Request, plen: usize, insert: bool, - ) -> Option { + pub fn match_with_params(&self, req: &Request, plen: usize) -> Option { let path = &req.path()[plen..]; - if insert { - if path.is_empty() { - "/" - } else { - path - } - } else { - path - }; match self.tp { PatternType::Static(ref s) => if s != path { @@ -942,11 +924,11 @@ mod tests { assert!(!re.is_match("/user/2345/sdg")); let req = TestRequest::with_uri("/user/profile").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(info.get("id").unwrap(), "profile"); let req = TestRequest::with_uri("/user/1245125").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(info.get("id").unwrap(), "1245125"); let re = ResourceDef::new("/v{version}/resource/{id}"); @@ -955,7 +937,7 @@ mod tests { assert!(!re.is_match("/resource")); let req = TestRequest::with_uri("/v151/resource/adahg32").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(info.get("version").unwrap(), "151"); assert_eq!(info.get("id").unwrap(), "adahg32"); } @@ -983,12 +965,12 @@ mod tests { assert!(!re.is_match("/name")); let req = TestRequest::with_uri("/test2/").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(&info["name"], "test2"); assert_eq!(&info[0], "test2"); let req = TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish(); - let info = re.match_with_params(&req, 0, true).unwrap(); + let info = re.match_with_params(&req, 0).unwrap(); assert_eq!(&info["name"], "test2"); assert_eq!(&info[0], "test2"); } diff --git a/src/scope.rs b/src/scope.rs index 94dbd8608..d9502c944 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -304,7 +304,7 @@ impl RouteHandler for Scope { let tail = req.match_info().tail as usize; // recognize resources - let info = self.router.match_with_params(req, req.state(), tail, false); + let info = self.router.recognize(req, req.state(), tail); let req2 = req.with_route_info(info); if self.middlewares.is_empty() { self.router.handle(&req2) From 42d3e86941f8f9b9aa471eec670d7e2c053eb842 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 16:24:22 +0600 Subject: [PATCH 4/7] calculate prefix dynamicly --- src/httprequest.rs | 15 +++++++++------ src/router.rs | 34 ++++++++++++---------------------- src/server/h1.rs | 5 ----- src/test.rs | 21 +++++++++++++++++---- 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/httprequest.rs b/src/httprequest.rs index 91ee9eb13..02edcae9a 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -462,14 +462,16 @@ mod tests { let mut resource = Resource::new(ResourceDef::new("/user/{name}.html")); resource.name("index"); let mut router = Router::<()>::new(); - router.set_prefix("/prefix/"); router.register_resource(resource); - let info = router.default_route_info(); + let mut info = router.default_route_info(); + info.set_prefix(7); assert!(info.has_route("/user/test.html")); assert!(!info.has_route("/prefix/user/test.html")); - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") + let req = TestRequest::with_uri("/prefix/test") + .prefix(7) + .header(header::HOST, "www.rust-lang.org") .finish_with_router(router); let url = req.url_for("index", &["test"]); assert_eq!( @@ -483,14 +485,15 @@ mod tests { let mut resource = Resource::new(ResourceDef::new("/index.html")); resource.name("index"); let mut router = Router::<()>::new(); - router.set_prefix("/prefix/"); router.register_resource(resource); - let info = router.default_route_info(); + let mut info = router.default_route_info(); + info.set_prefix(7); assert!(info.has_route("/index.html")); assert!(!info.has_route("/prefix/index.html")); - let req = TestRequest::default() + let req = TestRequest::with_uri("/prefix/test") + .prefix(7) .header(header::HOST, "www.rust-lang.org") .finish_with_router(router); let url = req.url_for_static("index"); diff --git a/src/router.rs b/src/router.rs index 603bc5608..468cc236f 100644 --- a/src/router.rs +++ b/src/router.rs @@ -49,6 +49,7 @@ pub struct ResourceInfo { router: Rc, resource: ResourceId, params: Params, + prefix: u16, } impl ResourceInfo { @@ -72,6 +73,10 @@ impl ResourceInfo { } } + pub(crate) fn set_prefix(&mut self, prefix: u16) { + self.prefix = prefix; + } + /// Get a reference to the Params object. /// /// Params is a container for url parameters. @@ -91,6 +96,7 @@ impl ResourceInfo { p.add(item.0.clone(), item.1.clone()); } + self.prefix = info.params.tail; self.params = p; } @@ -106,7 +112,8 @@ impl ResourceInfo { I: AsRef, { if let Some(pattern) = self.router.named.get(name) { - let path = pattern.resource_path(elements, &self.router.prefix)?; + let path = + pattern.resource_path(elements, &req.path()[..(self.prefix as usize)])?; if path.starts_with('/') { let conn = req.connection_info(); Ok(Url::parse(&format!( @@ -142,8 +149,6 @@ impl ResourceInfo { } struct Inner { - prefix: String, - prefix_len: usize, named: HashMap, patterns: Vec, } @@ -158,8 +163,6 @@ impl Router { pub(crate) fn new() -> Self { Router { defs: Rc::new(Inner { - prefix: String::new(), - prefix_len: 0, named: HashMap::new(), patterns: Vec::new(), }), @@ -169,25 +172,11 @@ impl Router { } } - /// Router prefix - #[inline] - pub fn prefix(&self) -> &str { - &self.defs.prefix - } - - /// Set router prefix - #[inline] - pub fn set_prefix(&mut self, prefix: &str) { - let prefix = prefix.trim().trim_right_matches('/').to_owned(); - let inner = Rc::get_mut(&mut self.defs).unwrap(); - inner.prefix_len = prefix.len(); - inner.prefix = prefix; - } - #[inline] pub(crate) fn route_info_params(&self, idx: u16, params: Params) -> ResourceInfo { ResourceInfo { params, + prefix: 0, router: self.defs.clone(), resource: ResourceId::Normal(idx), } @@ -200,6 +189,7 @@ impl Router { ResourceInfo { params, + prefix: 0, router: self.defs.clone(), resource: ResourceId::Default, } @@ -211,6 +201,7 @@ impl Router { params: Params::new(), router: self.defs.clone(), resource: ResourceId::Default, + prefix: 0, } } @@ -391,6 +382,7 @@ impl Router { } } ResourceInfo { + prefix: tail as u16, params: Params::new(), router: self.defs.clone(), resource: ResourceId::Default, @@ -851,7 +843,6 @@ mod tests { #[test] fn test_recognizer_with_prefix() { let mut router = Router::<()>::new(); - router.set_prefix("/test"); router.register_resource(Resource::new(ResourceDef::new("/name"))); router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); @@ -871,7 +862,6 @@ mod tests { // same patterns let mut router = Router::<()>::new(); - router.set_prefix("/test2"); router.register_resource(Resource::new(ResourceDef::new("/name"))); router.register_resource(Resource::new(ResourceDef::new("/name/{val}"))); diff --git a/src/server/h1.rs b/src/server/h1.rs index 5b83dcc08..511b32bce 100644 --- a/src/server/h1.rs +++ b/src/server/h1.rs @@ -543,11 +543,6 @@ mod tests { err: None, } } - fn feed_data(&mut self, data: &'static str) { - let mut b = BytesMut::from(self.buf.as_ref()); - b.extend(data.as_bytes()); - self.buf = b.take().freeze(); - } } impl AsyncRead for Buffer {} diff --git a/src/test.rs b/src/test.rs index 909d15f34..c2e5c7569 100644 --- a/src/test.rs +++ b/src/test.rs @@ -417,6 +417,7 @@ pub struct TestRequest { params: Params, cookies: Option>>, payload: Option, + prefix: u16, } impl Default for TestRequest<()> { @@ -430,6 +431,7 @@ impl Default for TestRequest<()> { params: Params::new(), cookies: None, payload: None, + prefix: 0, } } } @@ -467,6 +469,7 @@ impl TestRequest { params: Params::new(), cookies: None, payload: None, + prefix: 0, } } @@ -527,6 +530,12 @@ impl TestRequest { self } + /// Set request's prefix + pub fn prefix(mut self, prefix: u16) -> Self { + self.prefix = prefix; + self + } + /// Complete request creation and generate `HttpRequest` instance pub fn finish(self) -> HttpRequest { let TestRequest { @@ -538,6 +547,7 @@ impl TestRequest { mut params, cookies, payload, + prefix, } = self; let router = Router::<()>::new(); @@ -552,9 +562,10 @@ impl TestRequest { *inner.payload.borrow_mut() = payload; } params.set_url(req.url().clone()); + let mut info = router.route_info_params(0, params); + info.set_prefix(prefix); - let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(0, params)); + let mut req = HttpRequest::new(req, Rc::new(state), info); req.set_cookies(cookies); req } @@ -571,6 +582,7 @@ impl TestRequest { mut params, cookies, payload, + prefix, } = self; let pool = RequestPool::pool(ServerSettings::default()); @@ -584,8 +596,9 @@ impl TestRequest { *inner.payload.borrow_mut() = payload; } params.set_url(req.url().clone()); - let mut req = - HttpRequest::new(req, Rc::new(state), router.route_info_params(0, params)); + let mut info = router.route_info_params(0, params); + info.set_prefix(prefix); + let mut req = HttpRequest::new(req, Rc::new(state), info); req.set_cookies(cookies); req } From c43b6e3577d1cb3097b879aa05efdcfbc019c12f Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 16:39:15 +0600 Subject: [PATCH 5/7] cargo tarpaulin --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d6cd29e1e..67cd9d38a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,8 +36,8 @@ script: fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh) - USE_SKEPTIC=1 cargo tarpaulin --features="alpn,tls" --out Xml --no-count + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin + cargo tarpaulin --features="alpn,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" fi From 2214492792aff5e5e2b507b441c885d6e1c48ab9 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 18:53:02 +0600 Subject: [PATCH 6/7] use assert and restore test case --- .travis.yml | 2 +- src/router.rs | 16 ++++++++++------ src/scope.rs | 6 +++--- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 67cd9d38a..54a86aa7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin cargo tarpaulin --features="alpn,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" diff --git a/src/router.rs b/src/router.rs index 468cc236f..fbdcbc08a 100644 --- a/src/router.rs +++ b/src/router.rs @@ -211,9 +211,11 @@ impl Router { let name = resource.get_name(); if !name.is_empty() { - if inner.named.contains_key(name) { - panic!("Named resource {:?} is registered.", name); - } + assert!( + !inner.named.contains_key(name), + "Named resource {:?} is registered.", + name + ); inner.named.insert(name.to_owned(), resource.rdef().clone()); } inner.patterns.push(resource.rdef().clone()); @@ -279,9 +281,11 @@ impl Router { pub(crate) fn register_external(&mut self, name: &str, rdef: ResourceDef) { let inner = Rc::get_mut(&mut self.defs).unwrap(); - if inner.named.contains_key(name) { - panic!("Named resource {:?} is registered.", name); - } + assert!( + !inner.named.contains_key(name), + "Named resource {:?} is registered.", + name + ); inner.named.insert(name.to_owned(), rdef); } diff --git a/src/scope.rs b/src/scope.rs index d9502c944..a12bcafa2 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -910,9 +910,9 @@ mod tests { }) .finish(); - //let req = TestRequest::with_uri("/app/t1").request(); - //let resp = app.run(req); - //assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); + let req = TestRequest::with_uri("/app/t1").request(); + let resp = app.run(req); + assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND); let req = TestRequest::with_uri("/app/t1/").request(); let resp = app.run(req); From 2e5f62705050d9edc2bab6a8f77670d439858483 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Sun, 15 Jul 2018 19:15:36 +0600 Subject: [PATCH 7/7] do not force install tarpaulin --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 54a86aa7a..67cd9d38a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,7 +36,7 @@ script: fi - | if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then - RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin + RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin cargo tarpaulin --features="alpn,tls" --out Xml --no-count bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage"