1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-28 01:32:57 +01:00

add nested scope support

This commit is contained in:
Nikolay Kim 2018-04-30 22:04:24 -07:00
parent 70d0c5c700
commit 48e05a2d87
7 changed files with 176 additions and 53 deletions

View File

@ -69,9 +69,11 @@ impl<S: 'static> HttpApplication<S> {
}; };
if m { if m {
let path: &'static str = unsafe { let prefix_len = inner.prefix + prefix.len();
&*(&req.path()[inner.prefix + prefix.len()..] as *const _) let path: &'static str =
}; unsafe { &*(&req.path()[prefix_len..] as *const _) };
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 {
@ -881,8 +883,9 @@ mod tests {
); );
let req = TestRequest::with_uri("/app/test").finish(); let req = TestRequest::with_uri("/app/test").finish();
let resp = app.run(req); let resp = app.run(req.clone());
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK); assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
assert_eq!(req.prefix_len(), 9);
let req = TestRequest::with_uri("/app/test/").finish(); let req = TestRequest::with_uri("/app/test/").finish();
let resp = app.run(req); let resp = app.run(req);

View File

@ -25,20 +25,27 @@ use router::{Resource, Router};
use server::helpers::SharedHttpInnerMessage; use server::helpers::SharedHttpInnerMessage;
use uri::Url as InnerUrl; use uri::Url as InnerUrl;
bitflags! {
pub(crate) struct MessageFlags: u8 {
const QUERY = 0b0000_0001;
const KEEPALIVE = 0b0000_0010;
}
}
pub struct HttpInnerMessage { pub struct HttpInnerMessage {
pub version: Version, pub version: Version,
pub method: Method, pub method: Method,
pub(crate) url: InnerUrl, pub(crate) url: InnerUrl,
pub(crate) flags: MessageFlags,
pub headers: HeaderMap, pub headers: HeaderMap,
pub extensions: Extensions, pub extensions: Extensions,
pub params: Params<'static>, pub params: Params<'static>,
pub cookies: Option<Vec<Cookie<'static>>>, pub cookies: Option<Vec<Cookie<'static>>>,
pub query: Params<'static>, pub query: Params<'static>,
pub query_loaded: bool,
pub addr: Option<SocketAddr>, pub addr: Option<SocketAddr>,
pub payload: Option<Payload>, pub payload: Option<Payload>,
pub info: Option<ConnectionInfo<'static>>, pub info: Option<ConnectionInfo<'static>>,
pub keep_alive: bool, pub prefix: u16,
resource: RouterResource, resource: RouterResource,
} }
@ -55,15 +62,15 @@ impl Default for HttpInnerMessage {
url: InnerUrl::default(), url: InnerUrl::default(),
version: Version::HTTP_11, version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16), headers: HeaderMap::with_capacity(16),
flags: MessageFlags::empty(),
params: Params::new(), params: Params::new(),
query: Params::new(), query: Params::new(),
query_loaded: false,
addr: None, addr: None,
cookies: None, cookies: None,
payload: None, payload: None,
extensions: Extensions::new(), extensions: Extensions::new(),
info: None, info: None,
keep_alive: true, prefix: 0,
resource: RouterResource::Notset, resource: RouterResource::Notset,
} }
} }
@ -73,7 +80,7 @@ impl HttpInnerMessage {
/// Checks if a connection should be kept alive. /// Checks if a connection should be kept alive.
#[inline] #[inline]
pub fn keep_alive(&self) -> bool { pub fn keep_alive(&self) -> bool {
self.keep_alive self.flags.contains(MessageFlags::KEEPALIVE)
} }
#[inline] #[inline]
@ -83,10 +90,10 @@ impl HttpInnerMessage {
self.params.clear(); self.params.clear();
self.addr = None; self.addr = None;
self.info = None; self.info = None;
self.query_loaded = false; self.flags = MessageFlags::empty();
self.cookies = None; self.cookies = None;
self.payload = None; self.payload = None;
self.keep_alive = true; self.prefix = 0;
self.resource = RouterResource::Notset; self.resource = RouterResource::Notset;
} }
} }
@ -115,12 +122,12 @@ impl HttpRequest<()> {
payload, payload,
params: Params::new(), params: Params::new(),
query: Params::new(), query: Params::new(),
query_loaded: false,
extensions: Extensions::new(), extensions: Extensions::new(),
cookies: None, cookies: None,
addr: None, addr: None,
info: None, info: None,
keep_alive: true, prefix: 0,
flags: MessageFlags::empty(),
resource: RouterResource::Notset, resource: RouterResource::Notset,
}), }),
None, None,
@ -234,12 +241,13 @@ impl<S> HttpRequest<S> {
} }
#[doc(hidden)] #[doc(hidden)]
pub fn prefix_len(&self) -> usize { pub fn prefix_len(&self) -> u16 {
if let Some(router) = self.router() { self.as_ref().prefix as u16
router.prefix().len()
} else {
0
} }
#[doc(hidden)]
pub fn set_prefix_len(&mut self, len: u16) {
self.as_mut().prefix = len;
} }
/// Read the Request Uri. /// Read the Request Uri.
@ -367,14 +375,16 @@ impl<S> HttpRequest<S> {
self.as_mut().addr = addr; self.as_mut().addr = addr;
} }
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "please use `Query<T>` extractor")]
/// Get a reference to the Params object. /// Get a reference to the Params object.
/// Params is a container for url query parameters. /// Params is a container for url query parameters.
pub fn query(&self) -> &Params { pub fn query(&self) -> &Params {
if !self.as_ref().query_loaded { if !self.as_ref().flags.contains(MessageFlags::QUERY) {
self.as_mut().flags.insert(MessageFlags::QUERY);
let params: &mut Params = let params: &mut Params =
unsafe { mem::transmute(&mut self.as_mut().query) }; unsafe { mem::transmute(&mut self.as_mut().query) };
params.clear(); params.clear();
self.as_mut().query_loaded = true;
for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) { for (key, val) in form_urlencoded::parse(self.query_string().as_ref()) {
params.add(key, val); params.add(key, val);
} }
@ -443,7 +453,7 @@ impl<S> HttpRequest<S> {
/// Checks if a connection should be kept alive. /// Checks if a connection should be kept alive.
pub fn keep_alive(&self) -> bool { pub fn keep_alive(&self) -> bool {
self.as_ref().keep_alive() self.as_ref().flags.contains(MessageFlags::KEEPALIVE)
} }
/// Check if request requires connection upgrade /// Check if request requires connection upgrade

View File

@ -42,6 +42,22 @@ impl<'a> Params<'a> {
self.0.push((name.into(), value.into())); self.0.push((name.into(), value.into()));
} }
pub(crate) fn set<N, V>(&mut self, name: N, value: V)
where
N: Into<Cow<'a, str>>,
V: Into<Cow<'a, str>>,
{
let name = name.into();
let value = value.into();
for item in &mut self.0 {
if item.0 == name {
item.1 = value;
return;
}
}
self.0.push((name, value));
}
/// Check if there are any matched patterns /// Check if there are any matched patterns
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.0.is_empty()

View File

@ -84,6 +84,7 @@ impl Router {
for (idx, pattern) in self.0.patterns.iter().enumerate() { for (idx, pattern) in self.0.patterns.iter().enumerate() {
if pattern.match_with_params(route_path, req.match_info_mut()) { if pattern.match_with_params(route_path, req.match_info_mut()) {
req.set_resource(idx); req.set_resource(idx);
req.set_prefix_len(self.0.prefix_len as u16);
return Some(idx); return Some(idx);
} }
} }

View File

@ -14,6 +14,7 @@ use middleware::{Finished as MiddlewareFinished, Middleware,
use resource::ResourceHandler; use resource::ResourceHandler;
use router::Resource; use router::Resource;
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>>>)>>;
/// Resources scope /// Resources scope
@ -42,7 +43,7 @@ type ScopeResources<S> = Rc<Vec<(Resource, Rc<UnsafeCell<ResourceHandler<S>>>)>>
/// * /app/path3 - `HEAD` requests /// * /app/path3 - `HEAD` requests
/// ///
pub struct Scope<S: 'static> { pub struct Scope<S: 'static> {
handler: Option<UnsafeCell<Box<RouteHandler<S>>>>, nested: Vec<(String, 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>,
@ -57,17 +58,14 @@ 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 {
handler: None, 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())),
} }
} }
/// Create scope with new state. /// Create nested scope with new state.
///
/// Scope can have only one nested scope with new state. Every call
/// destroys previously created scope with state.
/// ///
/// ```rust /// ```rust
/// # extern crate actix_web; /// # extern crate actix_web;
@ -82,28 +80,78 @@ impl<S: 'static> Scope<S> {
/// fn main() { /// fn main() {
/// let app = App::new() /// let app = App::new()
/// .scope("/app", |scope| { /// .scope("/app", |scope| {
/// scope.with_state(AppState, |scope| { /// scope.with_state("/state2", AppState, |scope| {
/// scope.resource("/test1", |r| r.f(index)) /// scope.resource("/test1", |r| r.f(index))
/// }) /// })
/// }); /// });
/// } /// }
/// ``` /// ```
pub fn with_state<F, T: 'static>(mut self, state: T, f: F) -> Scope<S> pub fn with_state<F, T: 'static>(mut self, path: &str, state: T, f: F) -> Scope<S>
where where
F: FnOnce(Scope<T>) -> Scope<T>, F: FnOnce(Scope<T>) -> Scope<T>,
{ {
let scope = Scope { let scope = Scope {
handler: None, 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 scope = f(scope);
self.handler = Some(UnsafeCell::new(Box::new(Wrapper { let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
let handler = UnsafeCell::new(Box::new(Wrapper {
scope, scope,
state: Rc::new(state), state: Rc::new(state),
}))); }));
self.nested.push((path, handler));
self
}
/// Create nested scope.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{App, HttpRequest};
///
/// struct AppState;
///
/// fn index(req: HttpRequest<AppState>) -> &'static str {
/// "Welcome!"
/// }
///
/// fn main() {
/// let app = App::with_state(AppState)
/// .scope("/app", |scope| {
/// scope.nested("/v1", |scope| {
/// scope.resource("/test1", |r| r.f(index))
/// })
/// });
/// }
/// ```
pub fn nested<F>(mut self, path: &str, f: F) -> Scope<S>
where
F: FnOnce(Scope<S>) -> Scope<S>,
{
let scope = Scope {
nested: Vec::new(),
resources: Rc::new(Vec::new()),
middlewares: Rc::new(Vec::new()),
default: Rc::new(UnsafeCell::new(ResourceHandler::default_not_found())),
};
let scope = f(scope);
let mut path = path.trim().trim_right_matches('/').to_owned();
if !path.is_empty() && !path.starts_with('/') {
path.insert(0, '/')
}
self.nested
.push((path, UnsafeCell::new(Box::new(scope))));
self self
} }
@ -249,11 +297,34 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
} }
} }
// nested scope // nested scopes
if let Some(ref handler) = self.handler { for &(ref prefix, ref handler) in &self.nested {
let hnd: &mut RouteHandler<_> = unsafe { (&mut *(handler.get())).as_mut() }; let len = req.prefix_len() as usize;
hnd.handle(req) 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 {
let prefix_len = len + prefix.len();
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 { } else {
req.match_info_mut().set("tail", path);
}
let hnd: &mut RouteHandler<_> =
unsafe { (&mut *(handler.get())).as_mut() };
return hnd.handle(req);
}
}
// default handler // default handler
let default = unsafe { &mut *self.default.as_ref().get() }; let default = unsafe { &mut *self.default.as_ref().get() };
if self.middlewares.is_empty() { if self.middlewares.is_empty() {
@ -267,7 +338,6 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
)) ))
} }
} }
}
} }
struct Wrapper<S: 'static> { struct Wrapper<S: 'static> {
@ -646,13 +716,31 @@ mod tests {
let mut app = App::new() let mut app = App::new()
.scope("/app", |scope| { .scope("/app", |scope| {
scope.with_state(State, |scope| { scope.with_state("/t1", State, |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created())) scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
}) })
}) })
.finish(); .finish();
let req = TestRequest::with_uri("/app/path1").finish(); let req = TestRequest::with_uri("/app/t1/path1").finish();
let resp = app.run(req);
assert_eq!(
resp.as_response().unwrap().status(),
StatusCode::CREATED
);
}
#[test]
fn test_nested_scope() {
let mut app = App::new()
.scope("/app", |scope| {
scope.nested("/t1", |scope| {
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
})
})
.finish();
let req = TestRequest::with_uri("/app/t1/path1").finish();
let resp = app.run(req); let resp = app.run(req);
assert_eq!( assert_eq!(
resp.as_response().unwrap().status(), resp.as_response().unwrap().status(),

View File

@ -9,6 +9,7 @@ use super::settings::WorkerSettings;
use error::ParseError; use error::ParseError;
use http::header::{HeaderName, HeaderValue}; use http::header::{HeaderName, HeaderValue};
use http::{header, HttpTryFrom, Method, Uri, Version}; use http::{header, HttpTryFrom, Method, Uri, Version};
use httprequest::MessageFlags;
use uri::Url; use uri::Url;
const MAX_BUFFER_SIZE: usize = 131_072; const MAX_BUFFER_SIZE: usize = 131_072;
@ -128,7 +129,9 @@ impl H1Decoder {
let msg = settings.get_http_message(); let msg = settings.get_http_message();
{ {
let msg_mut = msg.get_mut(); let msg_mut = msg.get_mut();
msg_mut.keep_alive = version != Version::HTTP_10; msg_mut
.flags
.set(MessageFlags::KEEPALIVE, version != Version::HTTP_10);
for header in headers[..headers_len].iter() { for header in headers[..headers_len].iter() {
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) { if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
@ -165,7 +168,7 @@ impl H1Decoder {
} }
// connection keep-alive state // connection keep-alive state
header::CONNECTION => { header::CONNECTION => {
msg_mut.keep_alive = if let Ok(conn) = value.to_str() { let ka = if let Ok(conn) = value.to_str() {
if version == Version::HTTP_10 if version == Version::HTTP_10
&& conn.contains("keep-alive") && conn.contains("keep-alive")
{ {
@ -178,6 +181,7 @@ impl H1Decoder {
} else { } else {
false false
}; };
msg_mut.flags.set(MessageFlags::KEEPALIVE, ka);
} }
_ => (), _ => (),
} }

View File

@ -1,3 +1,4 @@
#![allow(deprecated)]
extern crate actix; extern crate actix;
extern crate actix_web; extern crate actix_web;
extern crate bytes; extern crate bytes;