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 {
let path: &'static str = unsafe {
&*(&req.path()[inner.prefix + prefix.len()..] as *const _)
};
let prefix_len = inner.prefix + 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().add("tail", "/");
} else {
@ -881,8 +883,9 @@ mod tests {
);
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!(req.prefix_len(), 9);
let req = TestRequest::with_uri("/app/test/").finish();
let resp = app.run(req);

View File

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

View File

@ -42,6 +42,22 @@ impl<'a> Params<'a> {
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
pub fn is_empty(&self) -> bool {
self.0.is_empty()

View File

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

View File

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

View File

@ -9,6 +9,7 @@ use super::settings::WorkerSettings;
use error::ParseError;
use http::header::{HeaderName, HeaderValue};
use http::{header, HttpTryFrom, Method, Uri, Version};
use httprequest::MessageFlags;
use uri::Url;
const MAX_BUFFER_SIZE: usize = 131_072;
@ -128,7 +129,9 @@ impl H1Decoder {
let msg = settings.get_http_message();
{
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() {
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
@ -165,7 +168,7 @@ impl H1Decoder {
}
// connection keep-alive state
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
&& conn.contains("keep-alive")
{
@ -178,6 +181,7 @@ impl H1Decoder {
} else {
false
};
msg_mut.flags.set(MessageFlags::KEEPALIVE, ka);
}
_ => (),
}

View File

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