mirror of
https://github.com/actix/actix-web.git
synced 2024-12-20 07:06:42 +00:00
add nested scope support
This commit is contained in:
parent
70d0c5c700
commit
48e05a2d87
7 changed files with 176 additions and 53 deletions
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
16
src/param.rs
16
src/param.rs
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
124
src/scope.rs
124
src/scope.rs
|
@ -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,11 +297,34 @@ 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)
|
||||
// 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() {
|
||||
|
@ -268,7 +339,6 @@ impl<S: 'static> RouteHandler<S> for Scope<S> {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Wrapper<S: 'static> {
|
||||
state: Rc<S>,
|
||||
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#![allow(deprecated)]
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate bytes;
|
||||
|
|
Loading…
Reference in a new issue