1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 07:53:00 +01:00

add filters support to scopes

This commit is contained in:
Nikolay Kim 2018-05-07 14:40:04 -07:00
parent a817ddb57b
commit c755d71a8b
2 changed files with 169 additions and 21 deletions

View File

@ -9,6 +9,7 @@ use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::Middleware; use middleware::Middleware;
use pipeline::{HandlerType, Pipeline, PipelineHandler}; use pipeline::{HandlerType, Pipeline, PipelineHandler};
use pred::Predicate;
use resource::ResourceHandler; use resource::ResourceHandler;
use router::{Resource, Router}; use router::{Resource, Router};
use scope::Scope; use scope::Scope;
@ -34,7 +35,7 @@ pub(crate) struct Inner<S> {
enum PrefixHandlerType<S> { enum PrefixHandlerType<S> {
Handler(String, Box<RouteHandler<S>>), Handler(String, Box<RouteHandler<S>>),
Scope(Resource, Box<RouteHandler<S>>), Scope(Resource, Box<RouteHandler<S>>, Vec<Box<Predicate<S>>>),
} }
impl<S: 'static> PipelineHandler<S> for Inner<S> { impl<S: 'static> PipelineHandler<S> for Inner<S> {
@ -51,7 +52,7 @@ impl<S: 'static> PipelineHandler<S> for Inner<S> {
} }
HandlerType::Handler(idx) => match self.handlers[idx] { HandlerType::Handler(idx) => match self.handlers[idx] {
PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req), PrefixHandlerType::Handler(_, ref mut hnd) => hnd.handle(req),
PrefixHandlerType::Scope(_, 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),
} }
@ -73,8 +74,8 @@ impl<S: 'static> HttpApplication<S> {
let path: &'static str = let path: &'static str =
unsafe { &*(&req.path()[inner.prefix..] as *const _) }; unsafe { &*(&req.path()[inner.prefix..] as *const _) };
let path_len = path.len(); let path_len = path.len();
for idx in 0..inner.handlers.len() { 'outer: for idx in 0..inner.handlers.len() {
match &inner.handlers[idx] { match inner.handlers[idx] {
PrefixHandlerType::Handler(ref prefix, _) => { PrefixHandlerType::Handler(ref prefix, _) => {
let m = { let m = {
path.starts_with(prefix) path.starts_with(prefix)
@ -96,10 +97,16 @@ impl<S: 'static> HttpApplication<S> {
return HandlerType::Handler(idx); return HandlerType::Handler(idx);
} }
} }
PrefixHandlerType::Scope(ref pattern, _) => { PrefixHandlerType::Scope(ref pattern, _, ref filters) => {
if let Some(prefix_len) = if let Some(prefix_len) =
pattern.match_prefix_with_params(path, req.match_info_mut()) pattern.match_prefix_with_params(path, req.match_info_mut())
{ {
for filter in filters {
if !filter.check(req) {
continue 'outer;
}
}
let prefix_len = inner.prefix + prefix_len - 1; let prefix_len = inner.prefix + 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 _) };
@ -361,7 +368,7 @@ where
F: FnOnce(Scope<S>) -> Scope<S>, F: FnOnce(Scope<S>) -> Scope<S>,
{ {
{ {
let scope = Box::new(f(Scope::new())); let mut scope = Box::new(f(Scope::new()));
let mut path = path.trim().trim_right_matches('/').to_owned(); let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') { if !path.is_empty() && !path.starts_with('/') {
@ -372,9 +379,11 @@ where
} }
let parts = self.parts.as_mut().expect("Use after finish"); let parts = self.parts.as_mut().expect("Use after finish");
let filters = scope.take_filters();
parts.handlers.push(PrefixHandlerType::Scope( parts.handlers.push(PrefixHandlerType::Scope(
Resource::prefix("", &path), Resource::prefix("", &path),
scope, scope,
filters,
)); ));
} }
self self

View File

@ -1,5 +1,6 @@
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::mem;
use std::rc::Rc; use std::rc::Rc;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
@ -11,11 +12,13 @@ use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use middleware::{Finished as MiddlewareFinished, Middleware, use middleware::{Finished as MiddlewareFinished, Middleware,
Response as MiddlewareResponse, Started as MiddlewareStarted}; Response as MiddlewareResponse, Started as MiddlewareStarted};
use pred::Predicate;
use resource::ResourceHandler; use resource::ResourceHandler;
use router::Resource; use router::Resource;
type Route<S> = UnsafeCell<Box<RouteHandler<S>>>; type Route<S> = UnsafeCell<Box<RouteHandler<S>>>;
type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>; type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>;
type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
/// Resources scope /// Resources scope
/// ///
@ -25,8 +28,8 @@ type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>
/// Scope prefix is always complete path segment, i.e `/app` would /// Scope prefix is always complete path segment, i.e `/app` would
/// be converted to a `/app/` and it would not match `/app` path. /// be converted to a `/app/` and it would not match `/app` path.
/// ///
/// You can use variable path segments with `Path` extractor, also variable /// You can get variable path segments from HttpRequest::match_info()`.
/// segments are available in `HttpRequest::match_info()`. /// `Path` extractor also is able to extract scope level variable segments.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
@ -48,7 +51,8 @@ type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>
/// * /{project_id}/path3 - `HEAD` requests /// * /{project_id}/path3 - `HEAD` requests
/// ///
pub struct Scope<S: 'static> { pub struct Scope<S: 'static> {
nested: Vec<(Resource, Route<S>)>, filters: Vec<Box<Predicate<S>>>,
nested: Vec<NestedInfo<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>,
@ -63,6 +67,7 @@ impl<S: 'static> Default for Scope<S> {
impl<S: 'static> Scope<S> { impl<S: 'static> Scope<S> {
pub fn new() -> Scope<S> { pub fn new() -> Scope<S> {
Scope { Scope {
filters: Vec::new(),
nested: Vec::new(), nested: Vec::new(),
resources: Rc::new(Vec::new()), resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()),
@ -70,6 +75,36 @@ impl<S: 'static> Scope<S> {
} }
} }
#[inline]
pub(crate) fn take_filters(&mut self) -> Vec<Box<Predicate<S>>> {
mem::replace(&mut self.filters, Vec::new())
}
/// Add match predicate to scoupe.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, pred, App, HttpRequest, HttpResponse, Path};
///
/// fn index(data: Path<(String, String)>) -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::new()
/// .scope("/app", |scope| {
/// scope.filter(pred::Header("content-type", "text/plain"))
/// .route("/test1", http::Method::GET, index)
/// .route("/test2", http::Method::POST,
/// |_: HttpRequest| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> Self {
self.filters.push(Box::new(p));
self
}
/// Create nested scope with new state. /// Create nested scope with new state.
/// ///
/// ```rust /// ```rust
@ -96,12 +131,13 @@ impl<S: 'static> Scope<S> {
F: FnOnce(Scope<T>) -> Scope<T>, F: FnOnce(Scope<T>) -> Scope<T>,
{ {
let scope = Scope { let scope = Scope {
filters: Vec::new(),
nested: Vec::new(), nested: Vec::new(),
resources: Rc::new(Vec::new()), resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
}; };
let scope = f(scope); let mut scope = f(scope);
let mut path = path.trim().trim_right_matches('/').to_owned(); let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') { if !path.is_empty() && !path.starts_with('/') {
@ -111,12 +147,14 @@ impl<S: 'static> Scope<S> {
path.push('/'); path.push('/');
} }
let handler = UnsafeCell::new(Box::new(Wrapper { let state = Rc::new(state);
scope, let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper {
state: Rc::new(state), state: Rc::clone(&state),
})); filters: scope.take_filters(),
})];
let handler = UnsafeCell::new(Box::new(Wrapper { scope, state }));
self.nested self.nested
.push((Resource::prefix("", &path), handler)); .push((Resource::prefix("", &path), handler, filters));
self self
} }
@ -147,12 +185,13 @@ impl<S: 'static> Scope<S> {
F: FnOnce(Scope<S>) -> Scope<S>, F: FnOnce(Scope<S>) -> Scope<S>,
{ {
let scope = Scope { let scope = Scope {
filters: Vec::new(),
nested: Vec::new(), nested: Vec::new(),
resources: Rc::new(Vec::new()), resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()), middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())), default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
}; };
let scope = f(scope); let mut scope = f(scope);
let mut path = path.trim().trim_right_matches('/').to_owned(); let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') { if !path.is_empty() && !path.starts_with('/') {
@ -162,9 +201,11 @@ impl<S: 'static> Scope<S> {
path.push('/'); path.push('/');
} }
let filters = scope.take_filters();
self.nested.push(( self.nested.push((
Resource::prefix("", &path), Resource::prefix("", &path),
UnsafeCell::new(Box::new(scope)), UnsafeCell::new(Box::new(scope)),
filters,
)); ));
self self
@ -315,10 +356,15 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
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 path: &'static str = unsafe { &*(&req.path()[len..] as *const _) };
for &(ref prefix, ref handler) in &self.nested { 'outer: for &(ref prefix, ref handler, ref filters) in &self.nested {
if let Some(prefix_len) = if let Some(prefix_len) =
prefix.match_prefix_with_params(path, req.match_info_mut()) prefix.match_prefix_with_params(path, req.match_info_mut())
{ {
for filter in filters {
if !filter.check(&mut req) {
continue 'outer;
}
}
let prefix_len = len + prefix_len - 1; 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 _) };
@ -363,6 +409,23 @@ impl<S: 'static, S2: 'static> RouteHandler<S2> for Wrapper<S> {
} }
} }
struct FiltersWrapper<S: 'static> {
state: Rc<S>,
filters: Vec<Box<Predicate<S>>>,
}
impl<S: 'static, S2: 'static> Predicate<S2> for FiltersWrapper<S> {
fn check(&self, req: &mut HttpRequest<S2>) -> bool {
let mut req = req.change_state(Rc::clone(&self.state));
for filter in &self.filters {
if !filter.check(&mut req) {
return false;
}
}
true
}
}
/// Compose resource level middlewares with route handler. /// Compose resource level middlewares with route handler.
struct Compose<S: 'static> { struct Compose<S: 'static> {
info: ComposeInfo<S>, info: ComposeInfo<S>,
@ -713,8 +776,9 @@ mod tests {
use application::App; use application::App;
use body::Body; use body::Body;
use http::StatusCode; use http::{Method, StatusCode};
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
use pred;
use test::TestRequest; use test::TestRequest;
#[test] #[test]
@ -730,6 +794,29 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
} }
#[test]
fn test_scope_filter() {
let mut app = App::new()
.scope("/app", |scope| {
scope
.filter(pred::Get())
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
})
.finish();
let req = TestRequest::with_uri("/app/path1")
.method(Method::POST)
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/path1")
.method(Method::GET)
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test] #[test]
fn test_scope_variable_segment() { fn test_scope_variable_segment() {
let mut app = App::new() let mut app = App::new()
@ -748,7 +835,7 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::OK); assert_eq!(resp.as_msg().status(), StatusCode::OK);
match resp.as_msg().body() { match resp.as_msg().body() {
Body::Binary(ref b) => { &Body::Binary(ref b) => {
let bytes: Bytes = b.clone().into(); let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"project: project1")); assert_eq!(bytes, Bytes::from_static(b"project: project1"));
} }
@ -777,6 +864,33 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
} }
#[test]
fn test_scope_with_state_filter() {
struct State;
let mut app = App::new()
.scope("/app", |scope| {
scope.with_state("/t1", State, |scope| {
scope
.filter(pred::Get())
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::POST)
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::GET)
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test] #[test]
fn test_nested_scope() { fn test_nested_scope() {
let mut app = App::new() let mut app = App::new()
@ -792,6 +906,31 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
} }
#[test]
fn test_nested_scope_filter() {
let mut app = App::new()
.scope("/app", |scope| {
scope.nested("/t1", |scope| {
scope
.filter(pred::Get())
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::POST)
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/app/t1/path1")
.method(Method::GET)
.finish();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
}
#[test] #[test]
fn test_nested_scope_with_variable_segment() { fn test_nested_scope_with_variable_segment() {
let mut app = App::new() let mut app = App::new()
@ -814,7 +953,7 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
match resp.as_msg().body() { match resp.as_msg().body() {
Body::Binary(ref b) => { &Body::Binary(ref b) => {
let bytes: Bytes = b.clone().into(); let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"project: project_1")); assert_eq!(bytes, Bytes::from_static(b"project: project_1"));
} }
@ -847,7 +986,7 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::CREATED); assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
match resp.as_msg().body() { match resp.as_msg().body() {
Body::Binary(ref b) => { &Body::Binary(ref b) => {
let bytes: Bytes = b.clone().into(); let bytes: Bytes = b.clone().into();
assert_eq!(bytes, Bytes::from_static(b"project: test - 1")); assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
} }