mirror of
https://github.com/actix/actix-extras.git
synced 2024-12-01 02:44:37 +01:00
add variable segments support for scope prefix
This commit is contained in:
parent
44c36e93d1
commit
a817ddb57b
@ -29,7 +29,12 @@ pub(crate) struct Inner<S> {
|
|||||||
default: ResourceHandler<S>,
|
default: ResourceHandler<S>,
|
||||||
encoding: ContentEncoding,
|
encoding: ContentEncoding,
|
||||||
resources: Vec<ResourceHandler<S>>,
|
resources: Vec<ResourceHandler<S>>,
|
||||||
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
handlers: Vec<PrefixHandlerType<S>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PrefixHandlerType<S> {
|
||||||
|
Handler(String, Box<RouteHandler<S>>),
|
||||||
|
Scope(Resource, Box<RouteHandler<S>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
||||||
@ -44,7 +49,10 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
|||||||
HandlerType::Normal(idx) => {
|
HandlerType::Normal(idx) => {
|
||||||
self.resources[idx].handle(req, Some(&mut self.default))
|
self.resources[idx].handle(req, Some(&mut self.default))
|
||||||
}
|
}
|
||||||
HandlerType::Handler(idx) => self.handlers[idx].1.handle(req),
|
HandlerType::Handler(idx) => match self.handlers[idx] {
|
||||||
|
PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req),
|
||||||
|
PrefixHandlerType::Scope(_, ref mut hnd) => hnd.handle(req),
|
||||||
|
},
|
||||||
HandlerType::Default => self.default.handle(req, None),
|
HandlerType::Default => self.default.handle(req, None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,27 +70,49 @@ impl<S: 'static> HttpApplication<S> {
|
|||||||
HandlerType::Normal(idx)
|
HandlerType::Normal(idx)
|
||||||
} else {
|
} else {
|
||||||
let inner = self.as_ref();
|
let inner = self.as_ref();
|
||||||
|
let path: &'static str =
|
||||||
|
unsafe { &*(&req.path()[inner.prefix..] as *const _) };
|
||||||
|
let path_len = path.len();
|
||||||
for idx in 0..inner.handlers.len() {
|
for idx in 0..inner.handlers.len() {
|
||||||
let &(ref prefix, _) = &inner.handlers[idx];
|
match &inner.handlers[idx] {
|
||||||
let m = {
|
PrefixHandlerType::Handler(ref prefix, _) => {
|
||||||
let path = &req.path()[inner.prefix..];
|
let m = {
|
||||||
path.starts_with(prefix)
|
path.starts_with(prefix)
|
||||||
&& (path.len() == prefix.len()
|
&& (path_len == prefix.len()
|
||||||
|| path.split_at(prefix.len()).1.starts_with('/'))
|
|| path.split_at(prefix.len()).1.starts_with('/'))
|
||||||
};
|
};
|
||||||
|
|
||||||
if m {
|
if m {
|
||||||
let prefix_len = inner.prefix + prefix.len();
|
let prefix_len = inner.prefix + prefix.len();
|
||||||
let path: &'static str =
|
let path: &'static str =
|
||||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||||
|
|
||||||
req.set_prefix_len(prefix_len as u16);
|
req.set_prefix_len(prefix_len as u16);
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
req.match_info_mut().add("tail", "/");
|
req.match_info_mut().add("tail", "/");
|
||||||
} else {
|
} else {
|
||||||
req.match_info_mut().add("tail", path);
|
req.match_info_mut().add("tail", path);
|
||||||
|
}
|
||||||
|
return HandlerType::Handler(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrefixHandlerType::Scope(ref pattern, _) => {
|
||||||
|
if let Some(prefix_len) =
|
||||||
|
pattern.match_prefix_with_params(path, req.match_info_mut())
|
||||||
|
{
|
||||||
|
let prefix_len = inner.prefix + prefix_len - 1;
|
||||||
|
let path: &'static str =
|
||||||
|
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||||
|
|
||||||
|
req.set_prefix_len(prefix_len as u16);
|
||||||
|
if path.is_empty() {
|
||||||
|
req.match_info_mut().set("tail", "/");
|
||||||
|
} else {
|
||||||
|
req.match_info_mut().set("tail", path);
|
||||||
|
}
|
||||||
|
return HandlerType::Handler(idx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return HandlerType::Handler(idx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HandlerType::Default
|
HandlerType::Default
|
||||||
@ -131,7 +161,7 @@ struct ApplicationParts<S> {
|
|||||||
settings: ServerSettings,
|
settings: ServerSettings,
|
||||||
default: ResourceHandler<S>,
|
default: ResourceHandler<S>,
|
||||||
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
|
resources: Vec<(Resource, Option<ResourceHandler<S>>)>,
|
||||||
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
handlers: Vec<PrefixHandlerType<S>>,
|
||||||
external: HashMap<String, Resource>,
|
external: HashMap<String, Resource>,
|
||||||
encoding: ContentEncoding,
|
encoding: ContentEncoding,
|
||||||
middlewares: Vec<Box<Middleware<S>>>,
|
middlewares: Vec<Box<Middleware<S>>>,
|
||||||
@ -305,7 +335,7 @@ where
|
|||||||
/// Configure scope for common root path.
|
/// Configure scope for common root path.
|
||||||
///
|
///
|
||||||
/// Scopes collect multiple paths under a common path prefix.
|
/// Scopes collect multiple paths under a common path prefix.
|
||||||
/// Scope path can not contain variable path segments as resources.
|
/// Scope path can contain variable path segments as resources.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
@ -313,7 +343,7 @@ where
|
|||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .scope("/app", |scope| {
|
/// .scope("/{project_id}", |scope| {
|
||||||
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||||
@ -322,9 +352,9 @@ where
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// In the above example, three routes get added:
|
/// In the above example, three routes get added:
|
||||||
/// * /app/path1
|
/// * /{project_id}/path1
|
||||||
/// * /app/path2
|
/// * /{project_id}/path2
|
||||||
/// * /app/path3
|
/// * /{project_id}/path3
|
||||||
///
|
///
|
||||||
pub fn scope<F>(mut self, path: &str, f: F) -> App<S>
|
pub fn scope<F>(mut self, path: &str, f: F) -> App<S>
|
||||||
where
|
where
|
||||||
@ -337,9 +367,15 @@ where
|
|||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
path.insert(0, '/')
|
path.insert(0, '/')
|
||||||
}
|
}
|
||||||
|
if !path.ends_with('/') {
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
let parts = self.parts.as_mut().expect("Use after finish");
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
|
||||||
parts.handlers.push((path, scope));
|
parts.handlers.push(PrefixHandlerType::Scope(
|
||||||
|
Resource::prefix("", &path),
|
||||||
|
scope,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -496,11 +532,15 @@ where
|
|||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
path.insert(0, '/')
|
path.insert(0, '/')
|
||||||
}
|
}
|
||||||
|
if path.len() > 1 && path.ends_with('/') {
|
||||||
|
path.pop();
|
||||||
|
}
|
||||||
let parts = self.parts.as_mut().expect("Use after finish");
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
|
||||||
parts
|
parts.handlers.push(PrefixHandlerType::Handler(
|
||||||
.handlers
|
path,
|
||||||
.push((path, Box::new(WrapHandler::new(handler))));
|
Box::new(WrapHandler::new(handler)),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -850,7 +850,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! from {
|
macro_rules! from {
|
||||||
($from: expr => $error: pat) => {
|
($from:expr => $error:pat) => {
|
||||||
match ParseError::from($from) {
|
match ParseError::from($from) {
|
||||||
e @ $error => {
|
e @ $error => {
|
||||||
assert!(format!("{}", e).len() >= 5);
|
assert!(format!("{}", e).len() >= 5);
|
||||||
@ -861,7 +861,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! from_and_cause {
|
macro_rules! from_and_cause {
|
||||||
($from: expr => $error: pat) => {
|
($from:expr => $error:pat) => {
|
||||||
match ParseError::from($from) {
|
match ParseError::from($from) {
|
||||||
e @ $error => {
|
e @ $error => {
|
||||||
let desc = format!("{}", e.cause().unwrap());
|
let desc = format!("{}", e.cause().unwrap());
|
||||||
|
114
src/router.rs
114
src/router.rs
@ -142,7 +142,8 @@ enum PatternElement {
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum PatternType {
|
enum PatternType {
|
||||||
Static(String),
|
Static(String),
|
||||||
Dynamic(Regex, Vec<String>),
|
Prefix(String),
|
||||||
|
Dynamic(Regex, Vec<String>, usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
@ -173,14 +174,23 @@ impl Resource {
|
|||||||
///
|
///
|
||||||
/// Panics if path pattern is wrong.
|
/// Panics if path pattern is wrong.
|
||||||
pub fn new(name: &str, path: &str) -> Self {
|
pub fn new(name: &str, path: &str) -> Self {
|
||||||
Resource::with_prefix(name, path, "/")
|
Resource::with_prefix(name, path, "/", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse path pattern and create new `Resource` instance.
|
||||||
|
///
|
||||||
|
/// Use `prefix` type instead of `static`.
|
||||||
|
///
|
||||||
|
/// Panics if path regex pattern is wrong.
|
||||||
|
pub fn prefix(name: &str, path: &str) -> Self {
|
||||||
|
Resource::with_prefix(name, path, "/", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct external resource
|
/// Construct external resource
|
||||||
///
|
///
|
||||||
/// Panics if path pattern is wrong.
|
/// Panics if path pattern is wrong.
|
||||||
pub fn external(name: &str, path: &str) -> Self {
|
pub fn external(name: &str, path: &str) -> Self {
|
||||||
let mut resource = Resource::with_prefix(name, path, "/");
|
let mut resource = Resource::with_prefix(name, path, "/", false);
|
||||||
resource.rtp = ResourceType::External;
|
resource.rtp = ResourceType::External;
|
||||||
resource
|
resource
|
||||||
}
|
}
|
||||||
@ -197,8 +207,9 @@ impl Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parse path pattern and create new `Resource` instance with custom prefix
|
/// Parse path pattern and create new `Resource` instance with custom prefix
|
||||||
pub fn with_prefix(name: &str, path: &str, prefix: &str) -> Self {
|
pub fn with_prefix(name: &str, path: &str, prefix: &str, for_prefix: bool) -> Self {
|
||||||
let (pattern, elements, is_dynamic) = Resource::parse(path, prefix);
|
let (pattern, elements, is_dynamic, len) =
|
||||||
|
Resource::parse(path, prefix, for_prefix);
|
||||||
|
|
||||||
let tp = if is_dynamic {
|
let tp = if is_dynamic {
|
||||||
let re = match Regex::new(&pattern) {
|
let re = match Regex::new(&pattern) {
|
||||||
@ -208,7 +219,9 @@ impl Resource {
|
|||||||
let names = re.capture_names()
|
let names = re.capture_names()
|
||||||
.filter_map(|name| name.map(|name| name.to_owned()))
|
.filter_map(|name| name.map(|name| name.to_owned()))
|
||||||
.collect();
|
.collect();
|
||||||
PatternType::Dynamic(re, names)
|
PatternType::Dynamic(re, names, len)
|
||||||
|
} else if for_prefix {
|
||||||
|
PatternType::Prefix(pattern.clone())
|
||||||
} else {
|
} else {
|
||||||
PatternType::Static(pattern.clone())
|
PatternType::Static(pattern.clone())
|
||||||
};
|
};
|
||||||
@ -240,7 +253,8 @@ impl Resource {
|
|||||||
pub fn is_match(&self, path: &str) -> bool {
|
pub fn is_match(&self, path: &str) -> bool {
|
||||||
match self.tp {
|
match self.tp {
|
||||||
PatternType::Static(ref s) => s == path,
|
PatternType::Static(ref s) => s == path,
|
||||||
PatternType::Dynamic(ref re, _) => re.is_match(path),
|
PatternType::Dynamic(ref re, _, _) => re.is_match(path),
|
||||||
|
PatternType::Prefix(ref s) => path.starts_with(s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +263,7 @@ impl Resource {
|
|||||||
) -> bool {
|
) -> bool {
|
||||||
match self.tp {
|
match self.tp {
|
||||||
PatternType::Static(ref s) => s == path,
|
PatternType::Static(ref s) => s == path,
|
||||||
PatternType::Dynamic(ref re, ref names) => {
|
PatternType::Dynamic(ref re, ref names, _) => {
|
||||||
if let Some(captures) = re.captures(path) {
|
if let Some(captures) = re.captures(path) {
|
||||||
let mut idx = 0;
|
let mut idx = 0;
|
||||||
for capture in captures.iter() {
|
for capture in captures.iter() {
|
||||||
@ -265,6 +279,42 @@ impl Resource {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PatternType::Prefix(ref s) => path.starts_with(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_prefix_with_params<'a>(
|
||||||
|
&'a self, path: &'a str, params: &'a mut Params<'a>,
|
||||||
|
) -> Option<usize> {
|
||||||
|
match self.tp {
|
||||||
|
PatternType::Static(ref s) => if s == path {
|
||||||
|
Some(s.len())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
PatternType::Dynamic(ref re, ref names, len) => {
|
||||||
|
if let Some(captures) = re.captures(path) {
|
||||||
|
let mut idx = 0;
|
||||||
|
let mut pos = 0;
|
||||||
|
for capture in captures.iter() {
|
||||||
|
if let Some(ref m) = capture {
|
||||||
|
if idx != 0 {
|
||||||
|
params.add(names[idx - 1].as_str(), m.as_str());
|
||||||
|
}
|
||||||
|
idx += 1;
|
||||||
|
pos = m.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(pos + len)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PatternType::Prefix(ref s) => if path.starts_with(s) {
|
||||||
|
Some(s.len())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +347,9 @@ impl Resource {
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse(pattern: &str, prefix: &str) -> (String, Vec<PatternElement>, bool) {
|
fn parse(
|
||||||
|
pattern: &str, prefix: &str, for_prefix: bool,
|
||||||
|
) -> (String, Vec<PatternElement>, bool, usize) {
|
||||||
const DEFAULT_PATTERN: &str = "[^/]+";
|
const DEFAULT_PATTERN: &str = "[^/]+";
|
||||||
|
|
||||||
let mut re1 = String::from("^") + prefix;
|
let mut re1 = String::from("^") + prefix;
|
||||||
@ -309,6 +361,7 @@ impl Resource {
|
|||||||
let mut param_pattern = String::from(DEFAULT_PATTERN);
|
let mut param_pattern = String::from(DEFAULT_PATTERN);
|
||||||
let mut is_dynamic = false;
|
let mut is_dynamic = false;
|
||||||
let mut elems = Vec::new();
|
let mut elems = Vec::new();
|
||||||
|
let mut len = 0;
|
||||||
|
|
||||||
for (index, ch) in pattern.chars().enumerate() {
|
for (index, ch) in pattern.chars().enumerate() {
|
||||||
// All routes must have a leading slash so its optional to have one
|
// All routes must have a leading slash so its optional to have one
|
||||||
@ -325,6 +378,7 @@ impl Resource {
|
|||||||
param_name.clear();
|
param_name.clear();
|
||||||
param_pattern = String::from(DEFAULT_PATTERN);
|
param_pattern = String::from(DEFAULT_PATTERN);
|
||||||
|
|
||||||
|
len = 0;
|
||||||
in_param_pattern = false;
|
in_param_pattern = false;
|
||||||
in_param = false;
|
in_param = false;
|
||||||
} else if ch == ':' {
|
} else if ch == ':' {
|
||||||
@ -348,16 +402,19 @@ impl Resource {
|
|||||||
re1.push_str(escape(&ch.to_string()).as_str());
|
re1.push_str(escape(&ch.to_string()).as_str());
|
||||||
re2.push(ch);
|
re2.push(ch);
|
||||||
el.push(ch);
|
el.push(ch);
|
||||||
|
len += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let re = if is_dynamic {
|
let re = if is_dynamic {
|
||||||
re1.push('$');
|
if !for_prefix {
|
||||||
|
re1.push('$');
|
||||||
|
}
|
||||||
re1
|
re1
|
||||||
} else {
|
} else {
|
||||||
re2
|
re2
|
||||||
};
|
};
|
||||||
(re, elems, is_dynamic)
|
(re, elems, is_dynamic, len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -570,6 +627,41 @@ mod tests {
|
|||||||
assert_eq!(req.match_info().get("id").unwrap(), "adahg32");
|
assert_eq!(req.match_info().get("id").unwrap(), "adahg32");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resource_prefix() {
|
||||||
|
let re = Resource::prefix("test", "/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 = Resource::prefix("test", "/name/");
|
||||||
|
assert!(re.is_match("/name/"));
|
||||||
|
assert!(re.is_match("/name/gs"));
|
||||||
|
assert!(!re.is_match("/name"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reousrce_prefix_dynamic() {
|
||||||
|
let re = Resource::prefix("test", "/{name}/");
|
||||||
|
assert!(re.is_match("/name/"));
|
||||||
|
assert!(re.is_match("/name/gs"));
|
||||||
|
assert!(!re.is_match("/name"));
|
||||||
|
|
||||||
|
let mut req = TestRequest::with_uri("/test2/").finish();
|
||||||
|
assert!(re.match_with_params("/test2/", req.match_info_mut()));
|
||||||
|
assert_eq!(&req.match_info()["name"], "test2");
|
||||||
|
|
||||||
|
let mut req =
|
||||||
|
TestRequest::with_uri("/test2/subpath1/subpath2/index.html").finish();
|
||||||
|
assert!(re.match_with_params(
|
||||||
|
"/test2/subpath1/subpath2/index.html",
|
||||||
|
req.match_info_mut()
|
||||||
|
));
|
||||||
|
assert_eq!(&req.match_info()["name"], "test2");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_request_resource() {
|
fn test_request_resource() {
|
||||||
let routes = vec![
|
let routes = vec![
|
||||||
|
149
src/scope.rs
149
src/scope.rs
@ -21,7 +21,12 @@ type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>
|
|||||||
///
|
///
|
||||||
/// Scope is a set of resources with common root path.
|
/// Scope is a set of resources with common root path.
|
||||||
/// Scopes collect multiple paths under a common path prefix.
|
/// Scopes collect multiple paths under a common path prefix.
|
||||||
/// Scope path can not contain variable path segments as resources.
|
/// Scope path can contain variable path segments as resources.
|
||||||
|
/// Scope prefix is always complete path segment, i.e `/app` would
|
||||||
|
/// be converted to a `/app/` and it would not match `/app` path.
|
||||||
|
///
|
||||||
|
/// You can use variable path segments with `Path` extractor, also variable
|
||||||
|
/// segments are available in `HttpRequest::match_info()`.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
@ -29,7 +34,7 @@ type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>
|
|||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = App::new()
|
/// let app = App::new()
|
||||||
/// .scope("/app", |scope| {
|
/// .scope("/{project_id}/", |scope| {
|
||||||
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
/// scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
||||||
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
||||||
@ -38,12 +43,12 @@ type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// In the above example three routes get registered:
|
/// In the above example three routes get registered:
|
||||||
/// * /app/path1 - reponds to all http method
|
/// * /{project_id}/path1 - reponds to all http method
|
||||||
/// * /app/path2 - `GET` requests
|
/// * /{project_id}/path2 - `GET` requests
|
||||||
/// * /app/path3 - `HEAD` requests
|
/// * /{project_id}/path3 - `HEAD` requests
|
||||||
///
|
///
|
||||||
pub struct Scope<S: 'static> {
|
pub struct Scope<S: 'static> {
|
||||||
nested: Vec<(String, Route<S>)>,
|
nested: Vec<(Resource, Route<S>)>,
|
||||||
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
default: Rc<UnsafeCell<ResourceHandler<S>>>,
|
default: Rc<UnsafeCell<ResourceHandler<S>>>,
|
||||||
resources: ScopeResources<S>,
|
resources: ScopeResources<S>,
|
||||||
@ -102,12 +107,16 @@ impl<S: 'static> Scope<S> {
|
|||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
path.insert(0, '/')
|
path.insert(0, '/')
|
||||||
}
|
}
|
||||||
|
if !path.ends_with('/') {
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
let handler = UnsafeCell::new(Box::new(Wrapper {
|
let handler = UnsafeCell::new(Box::new(Wrapper {
|
||||||
scope,
|
scope,
|
||||||
state: Rc::new(state),
|
state: Rc::new(state),
|
||||||
}));
|
}));
|
||||||
self.nested.push((path, handler));
|
self.nested
|
||||||
|
.push((Resource::prefix("", &path), handler));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -149,9 +158,14 @@ impl<S: 'static> Scope<S> {
|
|||||||
if !path.is_empty() && !path.starts_with('/') {
|
if !path.is_empty() && !path.starts_with('/') {
|
||||||
path.insert(0, '/')
|
path.insert(0, '/')
|
||||||
}
|
}
|
||||||
|
if !path.ends_with('/') {
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
self.nested
|
self.nested.push((
|
||||||
.push((path, UnsafeCell::new(Box::new(scope))));
|
Resource::prefix("", &path),
|
||||||
|
UnsafeCell::new(Box::new(scope)),
|
||||||
|
));
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -298,17 +312,14 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nested scopes
|
// nested scopes
|
||||||
for &(ref prefix, ref handler) in &self.nested {
|
let len = req.prefix_len() as usize;
|
||||||
let len = req.prefix_len() as usize;
|
let path: &'static str = unsafe { &*(&req.path()[len..] as *const _) };
|
||||||
let m = {
|
|
||||||
let path = &req.path()[len..];
|
|
||||||
path.starts_with(prefix)
|
|
||||||
&& (path.len() == prefix.len()
|
|
||||||
|| path.split_at(prefix.len()).1.starts_with('/'))
|
|
||||||
};
|
|
||||||
|
|
||||||
if m {
|
for &(ref prefix, ref handler) in &self.nested {
|
||||||
let prefix_len = len + prefix.len();
|
if let Some(prefix_len) =
|
||||||
|
prefix.match_prefix_with_params(path, req.match_info_mut())
|
||||||
|
{
|
||||||
|
let prefix_len = len + prefix_len - 1;
|
||||||
let path: &'static str =
|
let path: &'static str =
|
||||||
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
unsafe { &*(&req.path()[prefix_len..] as *const _) };
|
||||||
|
|
||||||
@ -698,7 +709,10 @@ impl<S: 'static> Response<S> {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use bytes::Bytes;
|
||||||
|
|
||||||
use application::App;
|
use application::App;
|
||||||
|
use body::Body;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use test::TestRequest;
|
use test::TestRequest;
|
||||||
@ -716,6 +730,36 @@ mod tests {
|
|||||||
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scope_variable_segment() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/ab-{project}", |scope| {
|
||||||
|
scope.resource("/path1", |r| {
|
||||||
|
r.f(|r| {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.body(format!("project: {}", &r.match_info()["project"]))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/ab-project1/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
match resp.as_msg().body() {
|
||||||
|
Body::Binary(ref b) => {
|
||||||
|
let bytes: Bytes = b.clone().into();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(b"project: project1"));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/aa-project1/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_scope_with_state() {
|
fn test_scope_with_state() {
|
||||||
struct State;
|
struct State;
|
||||||
@ -748,6 +792,73 @@ mod tests {
|
|||||||
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested_scope_with_variable_segment() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.nested("/{project_id}", |scope| {
|
||||||
|
scope.resource("/path1", |r| {
|
||||||
|
r.f(|r| {
|
||||||
|
HttpResponse::Created().body(format!(
|
||||||
|
"project: {}",
|
||||||
|
&r.match_info()["project_id"]
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/project_1/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
|
|
||||||
|
match resp.as_msg().body() {
|
||||||
|
Body::Binary(ref b) => {
|
||||||
|
let bytes: Bytes = b.clone().into();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nested2_scope_with_variable_segment() {
|
||||||
|
let mut app = App::new()
|
||||||
|
.scope("/app", |scope| {
|
||||||
|
scope.nested("/{project}", |scope| {
|
||||||
|
scope.nested("/{id}", |scope| {
|
||||||
|
scope.resource("/path1", |r| {
|
||||||
|
r.f(|r| {
|
||||||
|
HttpResponse::Created().body(format!(
|
||||||
|
"project: {} - {}",
|
||||||
|
&r.match_info()["project"],
|
||||||
|
&r.match_info()["id"],
|
||||||
|
))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/test/1/path1").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
||||||
|
|
||||||
|
match resp.as_msg().body() {
|
||||||
|
Body::Binary(ref b) => {
|
||||||
|
let bytes: Bytes = b.clone().into();
|
||||||
|
assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
|
||||||
|
}
|
||||||
|
_ => panic!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/test/1/path2").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_default_resource() {
|
fn test_default_resource() {
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
|
@ -13,9 +13,9 @@ use context::{ActorHttpContext, Drain, Frame as ContextFrame};
|
|||||||
use error::{Error, ErrorInternalServerError};
|
use error::{Error, ErrorInternalServerError};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
use ws::WsWriter;
|
|
||||||
use ws::frame::Frame;
|
use ws::frame::Frame;
|
||||||
use ws::proto::{CloseReason, OpCode};
|
use ws::proto::{CloseReason, OpCode};
|
||||||
|
use ws::WsWriter;
|
||||||
|
|
||||||
/// Execution context for `WebSockets` actors
|
/// Execution context for `WebSockets` actors
|
||||||
pub struct WebsocketContext<A, S = ()>
|
pub struct WebsocketContext<A, S = ()>
|
||||||
|
@ -551,7 +551,7 @@ fn test_async_middleware_multiple() {
|
|||||||
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
assert_eq!(num1.load(Ordering::Relaxed), 2);
|
||||||
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
assert_eq!(num2.load(Ordering::Relaxed), 2);
|
||||||
|
|
||||||
thread::sleep(Duration::from_millis(30));
|
thread::sleep(Duration::from_millis(50));
|
||||||
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
assert_eq!(num3.load(Ordering::Relaxed), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user