mirror of
https://github.com/actix/actix-web.git
synced 2024-12-30 03:50:42 +00:00
new router recognizer
This commit is contained in:
parent
35107f64e7
commit
f59f68eded
10 changed files with 229 additions and 42 deletions
|
@ -37,8 +37,6 @@ regex = "0.2"
|
||||||
slab = "0.4"
|
slab = "0.4"
|
||||||
sha1 = "0.2"
|
sha1 = "0.2"
|
||||||
url = "1.5"
|
url = "1.5"
|
||||||
lazy_static = "0.2"
|
|
||||||
route-recognizer = "0.1"
|
|
||||||
|
|
||||||
# tokio
|
# tokio
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
|
|
|
@ -2,13 +2,12 @@ use std::rc::Rc;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use route_recognizer::Router;
|
|
||||||
|
|
||||||
use task::Task;
|
use task::Task;
|
||||||
|
use payload::Payload;
|
||||||
use route::{RouteHandler, FnHandler};
|
use route::{RouteHandler, FnHandler};
|
||||||
use router::Handler;
|
use router::Handler;
|
||||||
use resource::Resource;
|
use resource::Resource;
|
||||||
use payload::Payload;
|
use recognizer::{RouteRecognizer, check_pattern};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
|
@ -24,13 +23,12 @@ pub struct Application<S=()> {
|
||||||
impl<S> Application<S> where S: 'static
|
impl<S> Application<S> where S: 'static
|
||||||
{
|
{
|
||||||
pub(crate) fn prepare(self, prefix: String) -> Box<Handler> {
|
pub(crate) fn prepare(self, prefix: String) -> Box<Handler> {
|
||||||
let mut router = Router::new();
|
|
||||||
let mut handlers = HashMap::new();
|
let mut handlers = HashMap::new();
|
||||||
let prefix = if prefix.ends_with('/') {prefix } else { prefix + "/" };
|
let prefix = if prefix.ends_with('/') { prefix } else { prefix + "/" };
|
||||||
|
|
||||||
|
let mut routes = Vec::new();
|
||||||
for (path, handler) in self.resources {
|
for (path, handler) in self.resources {
|
||||||
let path = prefix.clone() + path.trim_left_matches('/');
|
routes.push((path, handler))
|
||||||
router.add(path.as_str(), handler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (path, mut handler) in self.handlers {
|
for (path, mut handler) in self.handlers {
|
||||||
|
@ -43,7 +41,7 @@ impl<S> Application<S> where S: 'static
|
||||||
state: Rc::new(self.state),
|
state: Rc::new(self.state),
|
||||||
default: self.default,
|
default: self.default,
|
||||||
handlers: handlers,
|
handlers: handlers,
|
||||||
router: router }
|
router: RouteRecognizer::new(prefix, routes) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,6 +93,7 @@ impl<S> Application<S> where S: 'static {
|
||||||
|
|
||||||
// add resource
|
// add resource
|
||||||
if !self.resources.contains_key(&path) {
|
if !self.resources.contains_key(&path) {
|
||||||
|
check_pattern(&path);
|
||||||
self.resources.insert(path.clone(), Resource::default());
|
self.resources.insert(path.clone(), Resource::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,6 +212,7 @@ impl<S> ApplicationBuilder<S> where S: 'static {
|
||||||
// add resource
|
// add resource
|
||||||
let path = path.to_string();
|
let path = path.to_string();
|
||||||
if !parts.resources.contains_key(&path) {
|
if !parts.resources.contains_key(&path) {
|
||||||
|
check_pattern(&path);
|
||||||
parts.resources.insert(path.clone(), Resource::default());
|
parts.resources.insert(path.clone(), Resource::default());
|
||||||
}
|
}
|
||||||
f(parts.resources.get_mut(&path).unwrap());
|
f(parts.resources.get_mut(&path).unwrap());
|
||||||
|
@ -286,16 +286,20 @@ struct InnerApplication<S> {
|
||||||
state: Rc<S>,
|
state: Rc<S>,
|
||||||
default: Resource<S>,
|
default: Resource<S>,
|
||||||
handlers: HashMap<String, Box<RouteHandler<S>>>,
|
handlers: HashMap<String, Box<RouteHandler<S>>>,
|
||||||
router: Router<Resource<S>>,
|
router: RouteRecognizer<Resource<S>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<S: 'static> Handler for InnerApplication<S> {
|
impl<S: 'static> Handler for InnerApplication<S> {
|
||||||
|
|
||||||
fn handle(&self, req: HttpRequest, payload: Payload) -> Task {
|
fn handle(&self, req: HttpRequest, payload: Payload) -> Task {
|
||||||
if let Ok(h) = self.router.recognize(req.path()) {
|
if let Some((params, h)) = self.router.recognize(req.path()) {
|
||||||
h.handler.handle(
|
if let Some(params) = params {
|
||||||
req.with_match_info(h.params), payload, Rc::clone(&self.state))
|
h.handle(
|
||||||
|
req.with_match_info(params), payload, Rc::clone(&self.state))
|
||||||
|
} else {
|
||||||
|
h.handle(req, payload, Rc::clone(&self.state))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (prefix, handler) in &self.handlers {
|
for (prefix, handler) in &self.handlers {
|
||||||
if req.path().starts_with(prefix) {
|
if req.path().starts_with(prefix) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub use payload::{Payload, PayloadItem, PayloadError};
|
||||||
pub use router::RoutingMap;
|
pub use router::RoutingMap;
|
||||||
pub use resource::{Reply, Resource};
|
pub use resource::{Reply, Resource};
|
||||||
pub use route::{Route, RouteFactory, RouteHandler};
|
pub use route::{Route, RouteFactory, RouteHandler};
|
||||||
|
pub use recognizer::Params;
|
||||||
pub use server::HttpServer;
|
pub use server::HttpServer;
|
||||||
pub use context::HttpContext;
|
pub use context::HttpContext;
|
||||||
pub use staticfiles::StaticFiles;
|
pub use staticfiles::StaticFiles;
|
||||||
|
@ -25,7 +26,6 @@ pub use staticfiles::StaticFiles;
|
||||||
pub use http::{Method, StatusCode};
|
pub use http::{Method, StatusCode};
|
||||||
pub use cookie::{Cookie, CookieBuilder};
|
pub use cookie::{Cookie, CookieBuilder};
|
||||||
pub use cookie::{ParseError as CookieParseError};
|
pub use cookie::{ParseError as CookieParseError};
|
||||||
pub use route_recognizer::Params;
|
|
||||||
pub use http_range::{HttpRange, HttpRangeParseError};
|
pub use http_range::{HttpRange, HttpRangeParseError};
|
||||||
|
|
||||||
// dev specific
|
// dev specific
|
||||||
|
|
|
@ -3,10 +3,10 @@ use std::str;
|
||||||
use url::form_urlencoded;
|
use url::form_urlencoded;
|
||||||
use http::{header, Method, Version, Uri, HeaderMap};
|
use http::{header, Method, Version, Uri, HeaderMap};
|
||||||
|
|
||||||
use Params;
|
|
||||||
use {Cookie, CookieParseError};
|
use {Cookie, CookieParseError};
|
||||||
use {HttpRange, HttpRangeParseError};
|
use {HttpRange, HttpRangeParseError};
|
||||||
use error::ParseError;
|
use error::ParseError;
|
||||||
|
use recognizer::Params;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -29,7 +29,7 @@ impl HttpRequest {
|
||||||
uri: uri,
|
uri: uri,
|
||||||
version: version,
|
version: version,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
params: Params::new(),
|
params: Params::empty(),
|
||||||
cookies: Vec::new(),
|
cookies: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
src/lib.rs
12
src/lib.rs
|
@ -9,9 +9,7 @@ extern crate log;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate bytes;
|
extern crate bytes;
|
||||||
extern crate sha1;
|
extern crate sha1;
|
||||||
// extern crate regex;
|
extern crate regex;
|
||||||
// #[macro_use]
|
|
||||||
// extern crate lazy_static;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
|
@ -23,7 +21,6 @@ extern crate http;
|
||||||
extern crate httparse;
|
extern crate httparse;
|
||||||
extern crate http_range;
|
extern crate http_range;
|
||||||
extern crate mime_guess;
|
extern crate mime_guess;
|
||||||
extern crate route_recognizer;
|
|
||||||
extern crate url;
|
extern crate url;
|
||||||
extern crate actix;
|
extern crate actix;
|
||||||
|
|
||||||
|
@ -36,10 +33,11 @@ mod httprequest;
|
||||||
mod httpresponse;
|
mod httpresponse;
|
||||||
mod payload;
|
mod payload;
|
||||||
mod resource;
|
mod resource;
|
||||||
|
mod recognizer;
|
||||||
mod route;
|
mod route;
|
||||||
mod router;
|
mod router;
|
||||||
mod task;
|
|
||||||
mod reader;
|
mod reader;
|
||||||
|
mod task;
|
||||||
mod staticfiles;
|
mod staticfiles;
|
||||||
mod server;
|
mod server;
|
||||||
mod wsframe;
|
mod wsframe;
|
||||||
|
@ -54,8 +52,9 @@ pub use httprequest::HttpRequest;
|
||||||
pub use httpresponse::{Body, HttpResponse, HttpResponseBuilder};
|
pub use httpresponse::{Body, HttpResponse, HttpResponseBuilder};
|
||||||
pub use payload::{Payload, PayloadItem, PayloadError};
|
pub use payload::{Payload, PayloadItem, PayloadError};
|
||||||
pub use router::{Router, RoutingMap};
|
pub use router::{Router, RoutingMap};
|
||||||
pub use resource::{Reply, Resource};
|
|
||||||
pub use route::{Route, RouteFactory, RouteHandler};
|
pub use route::{Route, RouteFactory, RouteHandler};
|
||||||
|
pub use resource::{Reply, Resource};
|
||||||
|
pub use recognizer::{Params, RouteRecognizer};
|
||||||
pub use server::HttpServer;
|
pub use server::HttpServer;
|
||||||
pub use context::HttpContext;
|
pub use context::HttpContext;
|
||||||
pub use staticfiles::StaticFiles;
|
pub use staticfiles::StaticFiles;
|
||||||
|
@ -64,5 +63,4 @@ pub use staticfiles::StaticFiles;
|
||||||
pub use http::{Method, StatusCode};
|
pub use http::{Method, StatusCode};
|
||||||
pub use cookie::{Cookie, CookieBuilder};
|
pub use cookie::{Cookie, CookieBuilder};
|
||||||
pub use cookie::{ParseError as CookieParseError};
|
pub use cookie::{ParseError as CookieParseError};
|
||||||
pub use route_recognizer::Params;
|
|
||||||
pub use http_range::{HttpRange, HttpRangeParseError};
|
pub use http_range::{HttpRange, HttpRangeParseError};
|
||||||
|
|
|
@ -18,6 +18,7 @@ impl Route for MyRoute {
|
||||||
type State = ();
|
type State = ();
|
||||||
|
|
||||||
fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self> {
|
fn request(req: HttpRequest, payload: Payload, ctx: &mut HttpContext<Self>) -> Reply<Self> {
|
||||||
|
println!("PARAMS: {:?} {:?}", req.match_info().get("name"), req.match_info());
|
||||||
if !payload.eof() {
|
if !payload.eof() {
|
||||||
ctx.add_stream(payload);
|
ctx.add_stream(payload);
|
||||||
Reply::stream(MyRoute{req: Some(req)})
|
Reply::stream(MyRoute{req: Some(req)})
|
||||||
|
@ -105,7 +106,7 @@ fn main() {
|
||||||
HttpServer::new(
|
HttpServer::new(
|
||||||
RoutingMap::default()
|
RoutingMap::default()
|
||||||
.app("/blah", Application::default()
|
.app("/blah", Application::default()
|
||||||
.resource("/test", |r| {
|
.resource("/test/{name}", |r| {
|
||||||
r.get::<MyRoute>();
|
r.get::<MyRoute>();
|
||||||
r.post::<MyRoute>();
|
r.post::<MyRoute>();
|
||||||
})
|
})
|
||||||
|
|
164
src/recognizer.rs
Normal file
164
src/recognizer.rs
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use regex::{Regex, RegexSet, Captures};
|
||||||
|
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct RouteRecognizer<T> {
|
||||||
|
prefix: usize,
|
||||||
|
patterns: RegexSet,
|
||||||
|
routes: Vec<(Pattern, T)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RouteRecognizer<T> {
|
||||||
|
pub fn new(prefix: String, routes: Vec<(String, T)>) -> Self {
|
||||||
|
let mut paths = Vec::new();
|
||||||
|
let mut handlers = Vec::new();
|
||||||
|
for item in routes {
|
||||||
|
let pat = parse(&item.0);
|
||||||
|
handlers.push((Pattern::new(&pat), item.1));
|
||||||
|
paths.push(pat);
|
||||||
|
};
|
||||||
|
let regset = RegexSet::new(&paths);
|
||||||
|
|
||||||
|
RouteRecognizer {
|
||||||
|
prefix: prefix.len() - 1,
|
||||||
|
patterns: regset.unwrap(),
|
||||||
|
routes: handlers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recognize(&self, path: &str) -> Option<(Option<Params>, &T)> {
|
||||||
|
if let Some(idx) = self.patterns.matches(&path[self.prefix..]).into_iter().next()
|
||||||
|
{
|
||||||
|
let (ref pattern, ref route) = self.routes[idx];
|
||||||
|
Some((pattern.match_info(&path[self.prefix..]), route))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Pattern {
|
||||||
|
re: Regex,
|
||||||
|
names: Rc<HashMap<String, usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pattern {
|
||||||
|
fn new(pattern: &str) -> Self {
|
||||||
|
let re = Regex::new(pattern).unwrap();
|
||||||
|
let names = re.capture_names()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(i, name)| name.map(|name| (name.to_owned(), i)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Pattern {
|
||||||
|
re,
|
||||||
|
names: Rc::new(names),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_info(&self, text: &str) -> Option<Params> {
|
||||||
|
let captures = match self.re.captures(text) {
|
||||||
|
Some(captures) => captures,
|
||||||
|
None => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Params::new(Rc::clone(&self.names), text, captures))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn check_pattern(path: &str) {
|
||||||
|
if let Err(err) = Regex::new(&parse(path)) {
|
||||||
|
panic!("Wrong path pattern: \"{}\" {}", path, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(pattern: &str) -> String {
|
||||||
|
const DEFAULT_PATTERN: &'static str = "[^/]+";
|
||||||
|
|
||||||
|
let mut re = String::from("^/");
|
||||||
|
let mut in_param = false;
|
||||||
|
let mut in_param_pattern = false;
|
||||||
|
let mut param_name = String::new();
|
||||||
|
let mut param_pattern = String::from(DEFAULT_PATTERN);
|
||||||
|
|
||||||
|
for (index, ch) in pattern.chars().enumerate() {
|
||||||
|
// All routes must have a leading slash so its optional to have one
|
||||||
|
if index == 0 && ch == '/' {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_param {
|
||||||
|
// In parameter segment: `{....}`
|
||||||
|
if ch == '}' {
|
||||||
|
re.push_str(&format!(r"(?P<{}>{})", ¶m_name, ¶m_pattern));
|
||||||
|
|
||||||
|
param_name.clear();
|
||||||
|
param_pattern = String::from(DEFAULT_PATTERN);
|
||||||
|
|
||||||
|
in_param_pattern = false;
|
||||||
|
in_param = false;
|
||||||
|
} else if ch == ':' {
|
||||||
|
// The parameter name has been determined; custom pattern land
|
||||||
|
in_param_pattern = true;
|
||||||
|
param_pattern.clear();
|
||||||
|
} else if in_param_pattern {
|
||||||
|
// Ignore leading whitespace for pattern
|
||||||
|
if !(ch == ' ' && param_pattern.is_empty()) {
|
||||||
|
param_pattern.push(ch);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
param_name.push(ch);
|
||||||
|
}
|
||||||
|
} else if ch == '{' {
|
||||||
|
in_param = true;
|
||||||
|
} else {
|
||||||
|
re.push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
re.push('$');
|
||||||
|
re
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Params {
|
||||||
|
text: String,
|
||||||
|
matches: Vec<Option<(usize, usize)>>,
|
||||||
|
names: Rc<HashMap<String, usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Params {
|
||||||
|
pub(crate) fn new(names: Rc<HashMap<String, usize>>, text: &str, captures: Captures) -> Self
|
||||||
|
{
|
||||||
|
Params {
|
||||||
|
names,
|
||||||
|
text: text.into(),
|
||||||
|
matches: captures
|
||||||
|
.iter()
|
||||||
|
.map(|capture| capture.map(|m| (m.start(), m.end())))
|
||||||
|
.collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn empty() -> Self
|
||||||
|
{
|
||||||
|
Params {
|
||||||
|
text: String::new(),
|
||||||
|
names: Rc::new(HashMap::new()),
|
||||||
|
matches: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn by_idx(&self, index: usize) -> Option<&str> {
|
||||||
|
self.matches
|
||||||
|
.get(index + 1)
|
||||||
|
.and_then(|m| m.map(|(start, end)| &self.text[start..end]))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, key: &str) -> Option<&str> {
|
||||||
|
self.names.get(key).and_then(|&i| self.by_idx(i - 1))
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ use httpcodes::HTTPMethodNotAllowed;
|
||||||
/// .finish();
|
/// .finish();
|
||||||
/// }
|
/// }
|
||||||
pub struct Resource<S=()> {
|
pub struct Resource<S=()> {
|
||||||
|
name: String,
|
||||||
state: PhantomData<S>,
|
state: PhantomData<S>,
|
||||||
routes: HashMap<Method, Box<RouteHandler<S>>>,
|
routes: HashMap<Method, Box<RouteHandler<S>>>,
|
||||||
default: Box<RouteHandler<S>>,
|
default: Box<RouteHandler<S>>,
|
||||||
|
@ -40,6 +41,7 @@ pub struct Resource<S=()> {
|
||||||
impl<S> Default for Resource<S> {
|
impl<S> Default for Resource<S> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Resource {
|
Resource {
|
||||||
|
name: String::new(),
|
||||||
state: PhantomData,
|
state: PhantomData,
|
||||||
routes: HashMap::new(),
|
routes: HashMap::new(),
|
||||||
default: Box::new(HTTPMethodNotAllowed)}
|
default: Box::new(HTTPMethodNotAllowed)}
|
||||||
|
@ -49,6 +51,11 @@ impl<S> Default for Resource<S> {
|
||||||
|
|
||||||
impl<S> Resource<S> where S: 'static {
|
impl<S> Resource<S> where S: 'static {
|
||||||
|
|
||||||
|
/// Set resource name
|
||||||
|
pub fn set_name<T: ToString>(&mut self, name: T) {
|
||||||
|
self.name = name.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
/// Register handler for specified method.
|
/// Register handler for specified method.
|
||||||
pub fn handler<F, R>(&mut self, method: Method, handler: F)
|
pub fn handler<F, R>(&mut self, method: Method, handler: F)
|
||||||
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
|
where F: Fn(HttpRequest, Payload, &S) -> R + 'static,
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::string::ToString;
|
use std::string::ToString;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use route_recognizer::{Router as Recognizer};
|
|
||||||
|
|
||||||
use task::Task;
|
use task::Task;
|
||||||
use payload::Payload;
|
use payload::Payload;
|
||||||
use route::RouteHandler;
|
use route::RouteHandler;
|
||||||
use resource::Resource;
|
use resource::Resource;
|
||||||
|
use recognizer::{RouteRecognizer, check_pattern};
|
||||||
use application::Application;
|
use application::Application;
|
||||||
use httpcodes::HTTPNotFound;
|
use httpcodes::HTTPNotFound;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
@ -18,15 +18,20 @@ pub(crate) trait Handler: 'static {
|
||||||
/// Server routing map
|
/// Server routing map
|
||||||
pub struct Router {
|
pub struct Router {
|
||||||
apps: HashMap<String, Box<Handler>>,
|
apps: HashMap<String, Box<Handler>>,
|
||||||
resources: Recognizer<Resource>,
|
resources: RouteRecognizer<Resource>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Router {
|
impl Router {
|
||||||
|
|
||||||
pub(crate) fn call(&self, req: HttpRequest, payload: Payload) -> Task
|
pub(crate) fn call(&self, req: HttpRequest, payload: Payload) -> Task
|
||||||
{
|
{
|
||||||
if let Ok(h) = self.resources.recognize(req.path()) {
|
if let Some((params, h)) = self.resources.recognize(req.path()) {
|
||||||
h.handler.handle(req.with_match_info(h.params), payload, Rc::new(()))
|
if let Some(params) = params {
|
||||||
|
h.handle(
|
||||||
|
req.with_match_info(params), payload, Rc::new(()))
|
||||||
|
} else {
|
||||||
|
h.handle(req, payload, Rc::new(()))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
for (prefix, app) in &self.apps {
|
for (prefix, app) in &self.apps {
|
||||||
if req.path().starts_with(prefix) {
|
if req.path().starts_with(prefix) {
|
||||||
|
@ -40,17 +45,26 @@ impl Router {
|
||||||
|
|
||||||
/// Request routing map builder
|
/// Request routing map builder
|
||||||
///
|
///
|
||||||
/// Route supports glob patterns: * for a single wildcard segment and :param
|
/// Resource may have variable path also. For instance, a resource with
|
||||||
/// for matching storing that segment of the request url in the Params object,
|
/// the path '/a/{name}/c' would match all incoming requests with paths
|
||||||
/// which is stored in the request.
|
/// such as '/a/b/c', '/a/1/c', and '/a/etc/c'.
|
||||||
///
|
///
|
||||||
/// For instance, to route Get requests on any route matching /users/:userid/:friend and
|
/// A variable part is specified in the form {identifier}, where
|
||||||
|
/// the identifier can be used later in a request handler to access the matched
|
||||||
|
/// value for that part. This is done by looking up the identifier
|
||||||
|
/// in the Params object returned by `Request.match_info()` method.
|
||||||
|
///
|
||||||
|
/// By default, each part matches the regular expression [^{}/]+.
|
||||||
|
///
|
||||||
|
/// You can also specify a custom regex in the form {identifier:regex}:
|
||||||
|
///
|
||||||
|
/// For instance, to route Get requests on any route matching /users/{userid}/{friend} and
|
||||||
/// store userid and friend in the exposed Params object:
|
/// store userid and friend in the exposed Params object:
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// let mut map = RoutingMap::default();
|
/// let mut map = RoutingMap::default();
|
||||||
///
|
///
|
||||||
/// map.resource("/users/:userid/:friendid", |r| r.get::<MyRoute>());
|
/// map.resource("/users/{userid}/{friend}", |r| r.get::<MyRoute>());
|
||||||
/// ```
|
/// ```
|
||||||
pub struct RoutingMap {
|
pub struct RoutingMap {
|
||||||
parts: Option<RoutingMapParts>,
|
parts: Option<RoutingMapParts>,
|
||||||
|
@ -134,6 +148,7 @@ impl RoutingMap {
|
||||||
// add resource
|
// add resource
|
||||||
let path = path.to_string();
|
let path = path.to_string();
|
||||||
if !parts.resources.contains_key(&path) {
|
if !parts.resources.contains_key(&path) {
|
||||||
|
check_pattern(&path);
|
||||||
parts.resources.insert(path.clone(), Resource::default());
|
parts.resources.insert(path.clone(), Resource::default());
|
||||||
}
|
}
|
||||||
// configure resource
|
// configure resource
|
||||||
|
@ -147,15 +162,14 @@ impl RoutingMap {
|
||||||
{
|
{
|
||||||
let parts = self.parts.take().expect("Use after finish");
|
let parts = self.parts.take().expect("Use after finish");
|
||||||
|
|
||||||
let mut router = Recognizer::new();
|
let mut routes = Vec::new();
|
||||||
|
|
||||||
for (path, resource) in parts.resources {
|
for (path, resource) in parts.resources {
|
||||||
router.add(path.as_str(), resource);
|
routes.push((path, resource))
|
||||||
}
|
}
|
||||||
|
|
||||||
Router {
|
Router {
|
||||||
apps: parts.apps,
|
apps: parts.apps,
|
||||||
resources: router,
|
resources: RouteRecognizer::new("/".to_owned(), routes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,14 +79,15 @@ fn test_request_query() {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_request_match_info() {
|
fn test_request_match_info() {
|
||||||
let req = HttpRequest::new(Method::GET, Uri::try_from("/?id=test").unwrap(),
|
let req = HttpRequest::new(Method::GET, Uri::try_from("/value/?id=test").unwrap(),
|
||||||
Version::HTTP_11, HeaderMap::new());
|
Version::HTTP_11, HeaderMap::new());
|
||||||
|
|
||||||
let mut params = Params::new();
|
let rec = RouteRecognizer::new("/".to_owned(), vec![("/{key}/".to_owned(), 1)]);
|
||||||
params.insert("key".to_owned(), "value".to_owned());
|
let (params, _) = rec.recognize(req.path()).unwrap();
|
||||||
|
let params = params.unwrap();
|
||||||
|
|
||||||
let req = req.with_match_info(params);
|
let req = req.with_match_info(params);
|
||||||
assert_eq!(req.match_info().find("key"), Some("value"));
|
assert_eq!(req.match_info().get("key"), Some("value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in a new issue