2018-04-30 04:39:28 +02:00
|
|
|
use std::marker::PhantomData;
|
2018-05-07 23:40:04 +02:00
|
|
|
use std::mem;
|
2018-04-30 04:39:28 +02:00
|
|
|
use std::rc::Rc;
|
|
|
|
|
|
|
|
use futures::{Async, Future, Poll};
|
|
|
|
|
|
|
|
use error::Error;
|
2018-05-04 01:22:08 +02:00
|
|
|
use handler::{AsyncResult, AsyncResultItem, FromRequest, Responder, RouteHandler};
|
2018-07-15 11:12:21 +02:00
|
|
|
use http::Method;
|
2018-04-30 04:39:28 +02:00
|
|
|
use httprequest::HttpRequest;
|
|
|
|
use httpresponse::HttpResponse;
|
2018-05-16 20:00:29 +02:00
|
|
|
use middleware::{
|
|
|
|
Finished as MiddlewareFinished, Middleware, Response as MiddlewareResponse,
|
|
|
|
Started as MiddlewareStarted,
|
|
|
|
};
|
2018-05-07 23:40:04 +02:00
|
|
|
use pred::Predicate;
|
2018-07-15 11:12:21 +02:00
|
|
|
use resource::{DefaultResource, Resource};
|
|
|
|
use router::{ResourceDef, Router};
|
2018-06-25 06:58:04 +02:00
|
|
|
use server::Request;
|
2018-04-30 04:39:28 +02:00
|
|
|
|
|
|
|
/// Resources scope
|
|
|
|
///
|
|
|
|
/// Scope is a set of resources with common root path.
|
|
|
|
/// Scopes collect multiple paths under a common path prefix.
|
2018-05-07 22:50:43 +02:00
|
|
|
/// 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.
|
|
|
|
///
|
2018-05-08 00:19:03 +02:00
|
|
|
/// You can get variable path segments from `HttpRequest::match_info()`.
|
2018-05-07 23:40:04 +02:00
|
|
|
/// `Path` extractor also is able to extract scope level variable segments.
|
2018-04-30 04:39:28 +02:00
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # extern crate actix_web;
|
|
|
|
/// use actix_web::{http, App, HttpRequest, HttpResponse};
|
|
|
|
///
|
|
|
|
/// fn main() {
|
2018-06-01 18:37:14 +02:00
|
|
|
/// let app = App::new().scope("/{project_id}/", |scope| {
|
|
|
|
/// scope
|
|
|
|
/// .resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
/// .resource("/path2", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
/// .resource("/path3", |r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
|
|
|
/// });
|
2018-04-30 04:39:28 +02:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// In the above example three routes get registered:
|
2018-05-07 22:50:43 +02:00
|
|
|
/// * /{project_id}/path1 - reponds to all http method
|
|
|
|
/// * /{project_id}/path2 - `GET` requests
|
|
|
|
/// * /{project_id}/path3 - `HEAD` requests
|
2018-04-30 04:39:28 +02:00
|
|
|
///
|
2018-07-15 11:12:21 +02:00
|
|
|
pub struct Scope<S> {
|
|
|
|
rdef: ResourceDef,
|
|
|
|
router: Rc<Router<S>>,
|
2018-05-07 23:40:04 +02:00
|
|
|
filters: Vec<Box<Predicate<S>>>,
|
2018-06-21 19:06:23 +02:00
|
|
|
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
|
2018-05-08 00:19:03 +02:00
|
|
|
#[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))]
|
2018-04-30 04:39:28 +02:00
|
|
|
impl<S: 'static> Scope<S> {
|
2018-06-02 15:51:58 +02:00
|
|
|
/// Create a new scope
|
|
|
|
// TODO: Why is this not exactly the default impl?
|
2018-07-15 11:12:21 +02:00
|
|
|
pub fn new(path: &str) -> Scope<S> {
|
2018-04-30 04:39:28 +02:00
|
|
|
Scope {
|
2018-07-15 11:12:21 +02:00
|
|
|
rdef: ResourceDef::prefix(path),
|
|
|
|
router: Rc::new(Router::new()),
|
2018-05-07 23:40:04 +02:00
|
|
|
filters: Vec::new(),
|
2018-06-21 19:06:23 +02:00
|
|
|
middlewares: Rc::new(Vec::new()),
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-15 11:12:21 +02:00
|
|
|
#[inline]
|
|
|
|
pub(crate) fn rdef(&self) -> &ResourceDef {
|
|
|
|
&self.rdef
|
|
|
|
}
|
|
|
|
|
2018-05-07 23:40:04 +02:00
|
|
|
#[inline]
|
|
|
|
pub(crate) fn take_filters(&mut self) -> Vec<Box<Predicate<S>>> {
|
|
|
|
mem::replace(&mut self.filters, Vec::new())
|
|
|
|
}
|
|
|
|
|
2018-05-09 02:40:18 +02:00
|
|
|
/// Add match predicate to scope.
|
2018-05-07 23:40:04 +02:00
|
|
|
///
|
|
|
|
/// ```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() {
|
2018-06-01 18:37:14 +02:00
|
|
|
/// 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()
|
|
|
|
/// })
|
|
|
|
/// });
|
2018-05-07 23:40:04 +02:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> Self {
|
|
|
|
self.filters.push(Box::new(p));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-05-01 07:04:24 +02:00
|
|
|
/// Create nested scope with new state.
|
2018-04-30 07:28:16 +02:00
|
|
|
///
|
2018-04-30 07:19:52 +02:00
|
|
|
/// ```rust
|
|
|
|
/// # extern crate actix_web;
|
2018-05-01 04:51:55 +02:00
|
|
|
/// use actix_web::{App, HttpRequest};
|
2018-04-30 07:19:52 +02:00
|
|
|
///
|
|
|
|
/// struct AppState;
|
|
|
|
///
|
2018-06-25 06:58:04 +02:00
|
|
|
/// fn index(req: &HttpRequest<AppState>) -> &'static str {
|
2018-04-30 07:19:52 +02:00
|
|
|
/// "Welcome!"
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// fn main() {
|
2018-06-01 18:37:14 +02:00
|
|
|
/// let app = App::new().scope("/app", |scope| {
|
|
|
|
/// scope.with_state("/state2", AppState, |scope| {
|
|
|
|
/// scope.resource("/test1", |r| r.f(index))
|
|
|
|
/// })
|
|
|
|
/// });
|
2018-04-30 07:19:52 +02:00
|
|
|
/// }
|
|
|
|
/// ```
|
2018-05-01 07:04:24 +02:00
|
|
|
pub fn with_state<F, T: 'static>(mut self, path: &str, state: T, f: F) -> Scope<S>
|
2018-04-30 07:19:52 +02:00
|
|
|
where
|
|
|
|
F: FnOnce(Scope<T>) -> Scope<T>,
|
|
|
|
{
|
|
|
|
let scope = Scope {
|
2018-07-15 11:12:21 +02:00
|
|
|
rdef: ResourceDef::prefix(path),
|
2018-05-07 23:40:04 +02:00
|
|
|
filters: Vec::new(),
|
2018-07-15 11:12:21 +02:00
|
|
|
router: Rc::new(Router::new()),
|
2018-06-21 19:06:23 +02:00
|
|
|
middlewares: Rc::new(Vec::new()),
|
2018-04-30 07:19:52 +02:00
|
|
|
};
|
2018-05-07 23:40:04 +02:00
|
|
|
let mut scope = f(scope);
|
2018-04-30 07:19:52 +02:00
|
|
|
|
2018-05-07 23:40:04 +02:00
|
|
|
let state = Rc::new(state);
|
|
|
|
let filters: Vec<Box<Predicate<S>>> = vec![Box::new(FiltersWrapper {
|
|
|
|
state: Rc::clone(&state),
|
|
|
|
filters: scope.take_filters(),
|
|
|
|
})];
|
2018-06-21 19:21:28 +02:00
|
|
|
let handler = Box::new(Wrapper { scope, state });
|
2018-07-15 11:12:21 +02:00
|
|
|
|
|
|
|
Rc::get_mut(&mut self.router).unwrap().register_handler(
|
|
|
|
path,
|
|
|
|
handler,
|
|
|
|
Some(filters),
|
|
|
|
);
|
2018-05-01 07:04:24 +02:00
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create nested scope.
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # extern crate actix_web;
|
|
|
|
/// use actix_web::{App, HttpRequest};
|
|
|
|
///
|
|
|
|
/// struct AppState;
|
|
|
|
///
|
2018-06-25 06:58:04 +02:00
|
|
|
/// fn index(req: &HttpRequest<AppState>) -> &'static str {
|
2018-05-01 07:04:24 +02:00
|
|
|
/// "Welcome!"
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// fn main() {
|
2018-06-01 18:37:14 +02:00
|
|
|
/// let app = App::with_state(AppState).scope("/app", |scope| {
|
|
|
|
/// scope.nested("/v1", |scope| scope.resource("/test1", |r| r.f(index)))
|
|
|
|
/// });
|
2018-05-01 07:04:24 +02:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn nested<F>(mut self, path: &str, f: F) -> Scope<S>
|
|
|
|
where
|
|
|
|
F: FnOnce(Scope<S>) -> Scope<S>,
|
|
|
|
{
|
|
|
|
let scope = Scope {
|
2018-07-15 11:12:21 +02:00
|
|
|
rdef: ResourceDef::prefix(&path),
|
2018-05-07 23:40:04 +02:00
|
|
|
filters: Vec::new(),
|
2018-07-15 11:12:21 +02:00
|
|
|
router: Rc::new(Router::new()),
|
2018-06-21 19:06:23 +02:00
|
|
|
middlewares: Rc::new(Vec::new()),
|
2018-05-01 07:04:24 +02:00
|
|
|
};
|
2018-07-15 11:12:21 +02:00
|
|
|
Rc::get_mut(&mut self.router)
|
|
|
|
.unwrap()
|
|
|
|
.register_scope(f(scope));
|
2018-04-30 07:19:52 +02:00
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2018-04-30 04:39:28 +02:00
|
|
|
/// Configure route for a specific path.
|
|
|
|
///
|
|
|
|
/// This is a simplified version of the `Scope::resource()` method.
|
|
|
|
/// Handler functions need to accept one request extractor
|
|
|
|
/// argument.
|
|
|
|
///
|
|
|
|
/// This method could be called multiple times, in that case
|
|
|
|
/// multiple routes would be registered for same resource path.
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # extern crate actix_web;
|
|
|
|
/// use actix_web::{http, App, HttpRequest, HttpResponse, Path};
|
|
|
|
///
|
|
|
|
/// fn index(data: Path<(String, String)>) -> &'static str {
|
|
|
|
/// "Welcome!"
|
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// fn main() {
|
2018-06-01 18:37:14 +02:00
|
|
|
/// let app = App::new().scope("/app", |scope| {
|
|
|
|
/// scope.route("/test1", http::Method::GET, index).route(
|
|
|
|
/// "/test2",
|
|
|
|
/// http::Method::POST,
|
|
|
|
/// |_: HttpRequest| HttpResponse::MethodNotAllowed(),
|
|
|
|
/// )
|
|
|
|
/// });
|
2018-04-30 04:39:28 +02:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> Scope<S>
|
|
|
|
where
|
|
|
|
F: Fn(T) -> R + 'static,
|
|
|
|
R: Responder + 'static,
|
|
|
|
T: FromRequest<S> + 'static,
|
|
|
|
{
|
2018-07-15 11:12:21 +02:00
|
|
|
Rc::get_mut(&mut self.router)
|
|
|
|
.unwrap()
|
|
|
|
.register_route(path, method, f);
|
2018-04-30 04:39:28 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Configure resource for a specific path.
|
|
|
|
///
|
|
|
|
/// This method is similar to an `App::resource()` method.
|
|
|
|
/// Resources may have variable path segments. Resource path uses scope
|
|
|
|
/// path as a path prefix.
|
|
|
|
///
|
|
|
|
/// ```rust
|
|
|
|
/// # extern crate actix_web;
|
|
|
|
/// use actix_web::*;
|
|
|
|
///
|
|
|
|
/// fn main() {
|
2018-06-01 18:37:14 +02:00
|
|
|
/// let app = App::new().scope("/api", |scope| {
|
|
|
|
/// scope.resource("/users/{userid}/{friend}", |r| {
|
|
|
|
/// r.get().f(|_| HttpResponse::Ok());
|
|
|
|
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
|
|
|
|
/// r.route()
|
|
|
|
/// .filter(pred::Any(pred::Get()).or(pred::Put()))
|
|
|
|
/// .filter(pred::Header("Content-Type", "text/plain"))
|
|
|
|
/// .f(|_| HttpResponse::Ok())
|
|
|
|
/// })
|
|
|
|
/// });
|
2018-04-30 04:39:28 +02:00
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
pub fn resource<F, R>(mut self, path: &str, f: F) -> Scope<S>
|
|
|
|
where
|
2018-07-12 11:30:01 +02:00
|
|
|
F: FnOnce(&mut Resource<S>) -> R + 'static,
|
2018-04-30 04:39:28 +02:00
|
|
|
{
|
2018-07-15 11:12:21 +02:00
|
|
|
// add resource
|
2018-07-12 11:30:01 +02:00
|
|
|
let pattern = ResourceDef::with_prefix(
|
2018-05-23 22:21:29 +02:00
|
|
|
path,
|
|
|
|
if path.is_empty() { "" } else { "/" },
|
|
|
|
false,
|
|
|
|
);
|
2018-07-15 11:12:21 +02:00
|
|
|
let mut resource = Resource::new(pattern);
|
|
|
|
f(&mut resource);
|
2018-04-30 04:39:28 +02:00
|
|
|
|
2018-07-15 11:12:21 +02:00
|
|
|
Rc::get_mut(&mut self.router)
|
|
|
|
.unwrap()
|
|
|
|
.register_resource(resource);
|
2018-04-30 04:39:28 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Default resource to be used if no matching route could be found.
|
2018-06-19 19:46:58 +02:00
|
|
|
pub fn default_resource<F, R>(mut self, f: F) -> Scope<S>
|
2018-04-30 04:39:28 +02:00
|
|
|
where
|
2018-07-12 11:30:01 +02:00
|
|
|
F: FnOnce(&mut Resource<S>) -> R + 'static,
|
2018-04-30 04:39:28 +02:00
|
|
|
{
|
2018-07-15 11:12:21 +02:00
|
|
|
// create and configure default resource
|
|
|
|
let mut resource = Resource::new(ResourceDef::new(""));
|
|
|
|
f(&mut resource);
|
|
|
|
|
|
|
|
Rc::get_mut(&mut self.router)
|
|
|
|
.expect("Multiple copies of scope router")
|
|
|
|
.register_default_resource(resource.into());
|
|
|
|
|
2018-04-30 04:39:28 +02:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Register a scope middleware
|
|
|
|
///
|
|
|
|
/// This is similar to `App's` middlewares, but
|
|
|
|
/// middlewares get invoked on scope level.
|
|
|
|
///
|
2018-04-30 05:50:38 +02:00
|
|
|
/// *Note* `Middleware::finish()` fires right after response get
|
2018-05-08 00:19:03 +02:00
|
|
|
/// prepared. It does not wait until body get sent to the peer.
|
2018-04-30 04:39:28 +02:00
|
|
|
pub fn middleware<M: Middleware<S>>(mut self, mw: M) -> Scope<S> {
|
|
|
|
Rc::get_mut(&mut self.middlewares)
|
|
|
|
.expect("Can not use after configuration")
|
|
|
|
.push(Box::new(mw));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static> RouteHandler<S> for Scope<S> {
|
2018-06-25 06:58:04 +02:00
|
|
|
fn handle(&self, req: &HttpRequest<S>) -> AsyncResult<HttpResponse> {
|
2018-06-19 12:45:26 +02:00
|
|
|
let tail = req.match_info().tail as usize;
|
2018-04-30 04:39:28 +02:00
|
|
|
|
2018-05-08 00:19:03 +02:00
|
|
|
// recognize resources
|
2018-07-15 11:50:56 +02:00
|
|
|
let info = self.router.recognize(req, req.state(), tail);
|
2018-07-15 11:12:21 +02:00
|
|
|
let req2 = req.with_route_info(info);
|
|
|
|
if self.middlewares.is_empty() {
|
|
|
|
self.router.handle(&req2)
|
|
|
|
} else {
|
|
|
|
AsyncResult::async(Box::new(Compose::new(
|
|
|
|
req2,
|
|
|
|
Rc::clone(&self.router),
|
|
|
|
Rc::clone(&self.middlewares),
|
|
|
|
)))
|
2018-05-01 07:04:24 +02:00
|
|
|
}
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
2018-06-21 10:17:27 +02:00
|
|
|
|
|
|
|
fn has_default_resource(&self) -> bool {
|
2018-07-15 11:12:21 +02:00
|
|
|
self.router.has_default_resource()
|
2018-06-21 10:17:27 +02:00
|
|
|
}
|
|
|
|
|
2018-07-15 11:12:21 +02:00
|
|
|
fn default_resource(&mut self, default: DefaultResource<S>) {
|
|
|
|
Rc::get_mut(&mut self.router)
|
|
|
|
.expect("Can not use after configuration")
|
|
|
|
.register_default_resource(default);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn finish(&mut self) {
|
|
|
|
Rc::get_mut(&mut self.router)
|
|
|
|
.expect("Can not use after configuration")
|
|
|
|
.finish();
|
2018-06-21 10:17:27 +02:00
|
|
|
}
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
|
2018-04-30 07:19:52 +02:00
|
|
|
struct Wrapper<S: 'static> {
|
|
|
|
state: Rc<S>,
|
|
|
|
scope: Scope<S>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static, S2: 'static> RouteHandler<S2> for Wrapper<S> {
|
2018-06-25 06:58:04 +02:00
|
|
|
fn handle(&self, req: &HttpRequest<S2>) -> AsyncResult<HttpResponse> {
|
|
|
|
let req = req.with_state(Rc::clone(&self.state));
|
|
|
|
self.scope.handle(&req)
|
2018-04-30 07:19:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-07 23:40:04 +02:00
|
|
|
struct FiltersWrapper<S: 'static> {
|
|
|
|
state: Rc<S>,
|
|
|
|
filters: Vec<Box<Predicate<S>>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static, S2: 'static> Predicate<S2> for FiltersWrapper<S> {
|
2018-06-25 06:58:04 +02:00
|
|
|
fn check(&self, req: &Request, _: &S2) -> bool {
|
2018-05-07 23:40:04 +02:00
|
|
|
for filter in &self.filters {
|
2018-06-25 06:58:04 +02:00
|
|
|
if !filter.check(&req, &self.state) {
|
2018-05-07 23:40:04 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-30 04:39:28 +02:00
|
|
|
/// Compose resource level middlewares with route handler.
|
|
|
|
struct Compose<S: 'static> {
|
|
|
|
info: ComposeInfo<S>,
|
|
|
|
state: ComposeState<S>,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ComposeInfo<S: 'static> {
|
|
|
|
count: usize,
|
|
|
|
req: HttpRequest<S>,
|
2018-07-15 11:12:21 +02:00
|
|
|
router: Rc<Router<S>>,
|
2018-06-21 19:06:23 +02:00
|
|
|
mws: Rc<Vec<Box<Middleware<S>>>>,
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
enum ComposeState<S: 'static> {
|
|
|
|
Starting(StartMiddlewares<S>),
|
|
|
|
Handler(WaitingResponse<S>),
|
|
|
|
RunMiddlewares(RunMiddlewares<S>),
|
|
|
|
Finishing(FinishingMiddlewares<S>),
|
|
|
|
Completed(Response<S>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static> ComposeState<S> {
|
|
|
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
|
|
|
match *self {
|
|
|
|
ComposeState::Starting(ref mut state) => state.poll(info),
|
|
|
|
ComposeState::Handler(ref mut state) => state.poll(info),
|
|
|
|
ComposeState::RunMiddlewares(ref mut state) => state.poll(info),
|
|
|
|
ComposeState::Finishing(ref mut state) => state.poll(info),
|
|
|
|
ComposeState::Completed(_) => None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static> Compose<S> {
|
|
|
|
fn new(
|
2018-07-15 11:12:21 +02:00
|
|
|
req: HttpRequest<S>, router: Rc<Router<S>>, mws: Rc<Vec<Box<Middleware<S>>>>,
|
2018-04-30 04:39:28 +02:00
|
|
|
) -> Self {
|
|
|
|
let mut info = ComposeInfo {
|
|
|
|
mws,
|
2018-06-25 06:58:04 +02:00
|
|
|
req,
|
2018-07-15 11:12:21 +02:00
|
|
|
router,
|
2018-06-25 06:58:04 +02:00
|
|
|
count: 0,
|
2018-04-30 04:39:28 +02:00
|
|
|
};
|
|
|
|
let state = StartMiddlewares::init(&mut info);
|
|
|
|
|
|
|
|
Compose { state, info }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S> Future for Compose<S> {
|
|
|
|
type Item = HttpResponse;
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
|
|
|
loop {
|
|
|
|
if let ComposeState::Completed(ref mut resp) = self.state {
|
|
|
|
let resp = resp.resp.take().unwrap();
|
|
|
|
return Ok(Async::Ready(resp));
|
|
|
|
}
|
|
|
|
if let Some(state) = self.state.poll(&mut self.info) {
|
|
|
|
self.state = state;
|
|
|
|
} else {
|
|
|
|
return Ok(Async::NotReady);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Middlewares start executor
|
|
|
|
struct StartMiddlewares<S> {
|
|
|
|
fut: Option<Fut>,
|
|
|
|
_s: PhantomData<S>,
|
|
|
|
}
|
|
|
|
|
|
|
|
type Fut = Box<Future<Item = Option<HttpResponse>, Error = Error>>;
|
|
|
|
|
|
|
|
impl<S: 'static> StartMiddlewares<S> {
|
|
|
|
fn init(info: &mut ComposeInfo<S>) -> ComposeState<S> {
|
2018-06-21 19:06:23 +02:00
|
|
|
let len = info.mws.len();
|
2018-06-25 06:58:04 +02:00
|
|
|
|
2018-04-30 04:39:28 +02:00
|
|
|
loop {
|
|
|
|
if info.count == len {
|
2018-07-15 11:12:21 +02:00
|
|
|
let reply = info.router.handle(&info.req);
|
2018-04-30 04:39:28 +02:00
|
|
|
return WaitingResponse::init(info, reply);
|
|
|
|
} else {
|
2018-06-25 06:58:04 +02:00
|
|
|
let result = info.mws[info.count].start(&info.req);
|
|
|
|
match result {
|
2018-04-30 04:39:28 +02:00
|
|
|
Ok(MiddlewareStarted::Done) => info.count += 1,
|
|
|
|
Ok(MiddlewareStarted::Response(resp)) => {
|
2018-06-25 06:58:04 +02:00
|
|
|
return RunMiddlewares::init(info, resp);
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
2018-05-08 01:09:41 +02:00
|
|
|
Ok(MiddlewareStarted::Future(fut)) => {
|
|
|
|
return ComposeState::Starting(StartMiddlewares {
|
|
|
|
fut: Some(fut),
|
|
|
|
_s: PhantomData,
|
2018-06-25 06:58:04 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
return RunMiddlewares::init(info, err.into());
|
2018-05-08 01:09:41 +02:00
|
|
|
}
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
2018-06-21 19:06:23 +02:00
|
|
|
let len = info.mws.len();
|
2018-06-25 06:58:04 +02:00
|
|
|
|
2018-04-30 04:39:28 +02:00
|
|
|
'outer: loop {
|
|
|
|
match self.fut.as_mut().unwrap().poll() {
|
2018-06-25 06:58:04 +02:00
|
|
|
Ok(Async::NotReady) => {
|
|
|
|
return None;
|
|
|
|
}
|
2018-04-30 04:39:28 +02:00
|
|
|
Ok(Async::Ready(resp)) => {
|
|
|
|
info.count += 1;
|
2018-06-25 06:58:04 +02:00
|
|
|
|
2018-04-30 04:39:28 +02:00
|
|
|
if let Some(resp) = resp {
|
|
|
|
return Some(RunMiddlewares::init(info, resp));
|
|
|
|
}
|
2018-05-16 20:00:29 +02:00
|
|
|
loop {
|
|
|
|
if info.count == len {
|
2018-07-15 11:12:21 +02:00
|
|
|
let reply = info.router.handle(&info.req);
|
2018-05-16 20:00:29 +02:00
|
|
|
return Some(WaitingResponse::init(info, reply));
|
2018-04-30 04:39:28 +02:00
|
|
|
} else {
|
2018-06-25 06:58:04 +02:00
|
|
|
let result = info.mws[info.count].start(&info.req);
|
|
|
|
match result {
|
2018-04-30 04:39:28 +02:00
|
|
|
Ok(MiddlewareStarted::Done) => info.count += 1,
|
|
|
|
Ok(MiddlewareStarted::Response(resp)) => {
|
|
|
|
return Some(RunMiddlewares::init(info, resp));
|
|
|
|
}
|
|
|
|
Ok(MiddlewareStarted::Future(fut)) => {
|
|
|
|
self.fut = Some(fut);
|
|
|
|
continue 'outer;
|
|
|
|
}
|
2018-06-04 22:42:47 +02:00
|
|
|
Err(err) => {
|
2018-06-25 06:58:04 +02:00
|
|
|
return Some(RunMiddlewares::init(info, err.into()));
|
2018-06-04 22:42:47 +02:00
|
|
|
}
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-25 06:58:04 +02:00
|
|
|
Err(err) => {
|
|
|
|
return Some(RunMiddlewares::init(info, err.into()));
|
|
|
|
}
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// waiting for response
|
|
|
|
struct WaitingResponse<S> {
|
|
|
|
fut: Box<Future<Item = HttpResponse, Error = Error>>,
|
|
|
|
_s: PhantomData<S>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static> WaitingResponse<S> {
|
|
|
|
#[inline]
|
2018-05-04 01:22:08 +02:00
|
|
|
fn init(
|
2018-05-17 21:20:20 +02:00
|
|
|
info: &mut ComposeInfo<S>, reply: AsyncResult<HttpResponse>,
|
2018-05-04 01:22:08 +02:00
|
|
|
) -> ComposeState<S> {
|
2018-04-30 04:39:28 +02:00
|
|
|
match reply.into() {
|
2018-05-04 01:22:08 +02:00
|
|
|
AsyncResultItem::Ok(resp) => RunMiddlewares::init(info, resp),
|
|
|
|
AsyncResultItem::Err(err) => RunMiddlewares::init(info, err.into()),
|
|
|
|
AsyncResultItem::Future(fut) => ComposeState::Handler(WaitingResponse {
|
2018-04-30 04:39:28 +02:00
|
|
|
fut,
|
|
|
|
_s: PhantomData,
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
|
|
|
match self.fut.poll() {
|
|
|
|
Ok(Async::NotReady) => None,
|
2018-06-25 06:58:04 +02:00
|
|
|
Ok(Async::Ready(resp)) => Some(RunMiddlewares::init(info, resp)),
|
2018-04-30 04:39:28 +02:00
|
|
|
Err(err) => Some(RunMiddlewares::init(info, err.into())),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Middlewares response executor
|
|
|
|
struct RunMiddlewares<S> {
|
|
|
|
curr: usize,
|
|
|
|
fut: Option<Box<Future<Item = HttpResponse, Error = Error>>>,
|
|
|
|
_s: PhantomData<S>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static> RunMiddlewares<S> {
|
|
|
|
fn init(info: &mut ComposeInfo<S>, mut resp: HttpResponse) -> ComposeState<S> {
|
|
|
|
let mut curr = 0;
|
2018-06-21 19:06:23 +02:00
|
|
|
let len = info.mws.len();
|
2018-04-30 04:39:28 +02:00
|
|
|
|
|
|
|
loop {
|
2018-06-25 06:58:04 +02:00
|
|
|
let state = info.mws[curr].response(&info.req, resp);
|
2018-06-02 05:37:29 +02:00
|
|
|
resp = match state {
|
2018-04-30 04:39:28 +02:00
|
|
|
Err(err) => {
|
|
|
|
info.count = curr + 1;
|
|
|
|
return FinishingMiddlewares::init(info, err.into());
|
|
|
|
}
|
|
|
|
Ok(MiddlewareResponse::Done(r)) => {
|
|
|
|
curr += 1;
|
|
|
|
if curr == len {
|
|
|
|
return FinishingMiddlewares::init(info, r);
|
|
|
|
} else {
|
|
|
|
r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(MiddlewareResponse::Future(fut)) => {
|
|
|
|
return ComposeState::RunMiddlewares(RunMiddlewares {
|
|
|
|
curr,
|
|
|
|
fut: Some(fut),
|
|
|
|
_s: PhantomData,
|
2018-06-25 06:58:04 +02:00
|
|
|
});
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
2018-06-21 19:06:23 +02:00
|
|
|
let len = info.mws.len();
|
2018-04-30 04:39:28 +02:00
|
|
|
|
|
|
|
loop {
|
|
|
|
// poll latest fut
|
|
|
|
let mut resp = match self.fut.as_mut().unwrap().poll() {
|
|
|
|
Ok(Async::NotReady) => return None,
|
|
|
|
Ok(Async::Ready(resp)) => {
|
|
|
|
self.curr += 1;
|
|
|
|
resp
|
|
|
|
}
|
|
|
|
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
|
|
|
|
};
|
|
|
|
|
|
|
|
loop {
|
|
|
|
if self.curr == len {
|
|
|
|
return Some(FinishingMiddlewares::init(info, resp));
|
|
|
|
} else {
|
2018-06-25 06:58:04 +02:00
|
|
|
let state = info.mws[self.curr].response(&info.req, resp);
|
2018-06-02 05:37:29 +02:00
|
|
|
match state {
|
2018-04-30 04:39:28 +02:00
|
|
|
Err(err) => {
|
|
|
|
return Some(FinishingMiddlewares::init(info, err.into()))
|
|
|
|
}
|
|
|
|
Ok(MiddlewareResponse::Done(r)) => {
|
|
|
|
self.curr += 1;
|
|
|
|
resp = r
|
|
|
|
}
|
|
|
|
Ok(MiddlewareResponse::Future(fut)) => {
|
|
|
|
self.fut = Some(fut);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Middlewares start executor
|
|
|
|
struct FinishingMiddlewares<S> {
|
|
|
|
resp: Option<HttpResponse>,
|
|
|
|
fut: Option<Box<Future<Item = (), Error = Error>>>,
|
|
|
|
_s: PhantomData<S>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static> FinishingMiddlewares<S> {
|
|
|
|
fn init(info: &mut ComposeInfo<S>, resp: HttpResponse) -> ComposeState<S> {
|
|
|
|
if info.count == 0 {
|
|
|
|
Response::init(resp)
|
|
|
|
} else {
|
|
|
|
let mut state = FinishingMiddlewares {
|
|
|
|
resp: Some(resp),
|
|
|
|
fut: None,
|
|
|
|
_s: PhantomData,
|
|
|
|
};
|
|
|
|
if let Some(st) = state.poll(info) {
|
|
|
|
st
|
|
|
|
} else {
|
|
|
|
ComposeState::Finishing(state)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn poll(&mut self, info: &mut ComposeInfo<S>) -> Option<ComposeState<S>> {
|
|
|
|
loop {
|
|
|
|
// poll latest fut
|
|
|
|
let not_ready = if let Some(ref mut fut) = self.fut {
|
|
|
|
match fut.poll() {
|
|
|
|
Ok(Async::NotReady) => true,
|
|
|
|
Ok(Async::Ready(())) => false,
|
|
|
|
Err(err) => {
|
|
|
|
error!("Middleware finish error: {}", err);
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
};
|
|
|
|
if not_ready {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
self.fut = None;
|
2018-05-05 21:18:43 +02:00
|
|
|
if info.count == 0 {
|
|
|
|
return Some(Response::init(self.resp.take().unwrap()));
|
|
|
|
}
|
2018-04-30 04:39:28 +02:00
|
|
|
|
2018-05-05 21:18:43 +02:00
|
|
|
info.count -= 1;
|
2018-06-21 19:06:23 +02:00
|
|
|
let state = info.mws[info.count as usize]
|
2018-06-25 06:58:04 +02:00
|
|
|
.finish(&info.req, self.resp.as_ref().unwrap());
|
2018-06-02 05:37:29 +02:00
|
|
|
match state {
|
2018-04-30 04:39:28 +02:00
|
|
|
MiddlewareFinished::Done => {
|
|
|
|
if info.count == 0 {
|
|
|
|
return Some(Response::init(self.resp.take().unwrap()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
MiddlewareFinished::Future(fut) => {
|
|
|
|
self.fut = Some(fut);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Response<S> {
|
|
|
|
resp: Option<HttpResponse>,
|
|
|
|
_s: PhantomData<S>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S: 'static> Response<S> {
|
|
|
|
fn init(resp: HttpResponse) -> ComposeState<S> {
|
|
|
|
ComposeState::Completed(Response {
|
|
|
|
resp: Some(resp),
|
|
|
|
_s: PhantomData,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2018-05-07 22:50:43 +02:00
|
|
|
use bytes::Bytes;
|
|
|
|
|
2018-04-30 04:39:28 +02:00
|
|
|
use application::App;
|
2018-05-07 22:50:43 +02:00
|
|
|
use body::Body;
|
2018-05-07 23:40:04 +02:00
|
|
|
use http::{Method, StatusCode};
|
2018-05-08 00:19:03 +02:00
|
|
|
use httprequest::HttpRequest;
|
2018-04-30 04:39:28 +02:00
|
|
|
use httpresponse::HttpResponse;
|
2018-05-07 23:40:04 +02:00
|
|
|
use pred;
|
2018-04-30 04:39:28 +02:00
|
|
|
use test::TestRequest;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_scope() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-04-30 04:39:28 +02:00
|
|
|
.scope("/app", |scope| {
|
|
|
|
scope.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/path1").request();
|
2018-04-30 04:39:28 +02:00
|
|
|
let resp = app.run(req);
|
2018-05-02 02:19:15 +02:00
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
|
|
|
|
2018-05-23 22:21:29 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_root() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-23 22:21:29 +02:00
|
|
|
.scope("/app", |scope| {
|
|
|
|
scope
|
|
|
|
.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
.resource("/", |r| r.f(|_| HttpResponse::Created()))
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app").request();
|
2018-05-23 22:21:29 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/").request();
|
2018-05-23 22:21:29 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
|
|
|
}
|
|
|
|
|
2018-05-23 23:11:01 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_root2() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-23 23:11:01 +02:00
|
|
|
.scope("/app/", |scope| {
|
|
|
|
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app").request();
|
2018-05-23 23:11:01 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/").request();
|
2018-05-23 23:11:01 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_scope_root3() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-23 23:11:01 +02:00
|
|
|
.scope("/app/", |scope| {
|
|
|
|
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app").request();
|
2018-05-23 23:11:01 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/").request();
|
2018-05-23 23:11:01 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
}
|
|
|
|
|
2018-05-08 00:19:03 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_route() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-08 00:19:03 +02:00
|
|
|
.scope("app", |scope| {
|
2018-05-08 01:09:41 +02:00
|
|
|
scope
|
2018-05-09 00:44:50 +02:00
|
|
|
.route("/path1", Method::GET, |_: HttpRequest<_>| {
|
2018-05-08 01:09:41 +02:00
|
|
|
HttpResponse::Ok()
|
|
|
|
})
|
2018-05-09 00:44:50 +02:00
|
|
|
.route("/path1", Method::DELETE, |_: HttpRequest<_>| {
|
2018-05-08 01:09:41 +02:00
|
|
|
HttpResponse::Ok()
|
|
|
|
})
|
2018-05-08 00:19:03 +02:00
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/path1").request();
|
2018-05-08 00:19:03 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
|
2018-05-08 01:09:41 +02:00
|
|
|
let req = TestRequest::with_uri("/app/path1")
|
|
|
|
.method(Method::DELETE)
|
2018-06-25 06:58:04 +02:00
|
|
|
.request();
|
2018-05-08 01:09:41 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
|
2018-05-08 00:19:03 +02:00
|
|
|
let req = TestRequest::with_uri("/app/path1")
|
|
|
|
.method(Method::POST)
|
2018-06-25 06:58:04 +02:00
|
|
|
.request();
|
2018-05-08 00:19:03 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
}
|
|
|
|
|
2018-05-07 23:40:04 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_filter() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-07 23:40:04 +02:00
|
|
|
.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)
|
2018-06-25 06:58:04 +02:00
|
|
|
.request();
|
2018-05-07 23:40:04 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
|
|
|
|
let req = TestRequest::with_uri("/app/path1")
|
|
|
|
.method(Method::GET)
|
2018-06-25 06:58:04 +02:00
|
|
|
.request();
|
2018-05-07 23:40:04 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
}
|
|
|
|
|
2018-05-07 22:50:43 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_variable_segment() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-07 22:50:43 +02:00
|
|
|
.scope("/ab-{project}", |scope| {
|
|
|
|
scope.resource("/path1", |r| {
|
|
|
|
r.f(|r| {
|
|
|
|
HttpResponse::Ok()
|
|
|
|
.body(format!("project: {}", &r.match_info()["project"]))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/ab-project1/path1").request();
|
2018-05-07 22:50:43 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
|
|
|
|
match resp.as_msg().body() {
|
2018-05-07 23:40:04 +02:00
|
|
|
&Body::Binary(ref b) => {
|
2018-05-07 22:50:43 +02:00
|
|
|
let bytes: Bytes = b.clone().into();
|
|
|
|
assert_eq!(bytes, Bytes::from_static(b"project: project1"));
|
|
|
|
}
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/aa-project1/path1").request();
|
2018-05-07 22:50:43 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
}
|
|
|
|
|
2018-04-30 07:19:52 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_with_state() {
|
|
|
|
struct State;
|
|
|
|
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-04-30 07:19:52 +02:00
|
|
|
.scope("/app", |scope| {
|
2018-05-01 07:04:24 +02:00
|
|
|
scope.with_state("/t1", State, |scope| {
|
2018-04-30 07:19:52 +02:00
|
|
|
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1/path1").request();
|
2018-05-01 07:04:24 +02:00
|
|
|
let resp = app.run(req);
|
2018-05-02 02:19:15 +02:00
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
2018-05-01 07:04:24 +02:00
|
|
|
}
|
|
|
|
|
2018-05-23 22:21:29 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_with_state_root() {
|
|
|
|
struct State;
|
|
|
|
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-23 22:21:29 +02:00
|
|
|
.scope("/app", |scope| {
|
|
|
|
scope.with_state("/t1", State, |scope| {
|
|
|
|
scope
|
|
|
|
.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
.resource("/", |r| r.f(|_| HttpResponse::Created()))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1").request();
|
2018-05-23 22:21:29 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1/").request();
|
2018-05-23 22:21:29 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
|
|
|
}
|
|
|
|
|
2018-05-23 23:11:01 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_with_state_root2() {
|
|
|
|
struct State;
|
|
|
|
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-23 23:11:01 +02:00
|
|
|
.scope("/app", |scope| {
|
|
|
|
scope.with_state("/t1/", State, |scope| {
|
|
|
|
scope.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-07-15 14:53:02 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1").request();
|
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
2018-05-23 23:11:01 +02:00
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1/").request();
|
2018-05-23 23:11:01 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_scope_with_state_root3() {
|
|
|
|
struct State;
|
|
|
|
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-23 23:11:01 +02:00
|
|
|
.scope("/app", |scope| {
|
|
|
|
scope.with_state("/t1/", State, |scope| {
|
|
|
|
scope.resource("/", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1").request();
|
2018-05-23 23:11:01 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1/").request();
|
2018-05-23 23:11:01 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
}
|
|
|
|
|
2018-05-07 23:40:04 +02:00
|
|
|
#[test]
|
|
|
|
fn test_scope_with_state_filter() {
|
|
|
|
struct State;
|
|
|
|
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-07 23:40:04 +02:00
|
|
|
.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)
|
2018-06-25 06:58:04 +02:00
|
|
|
.request();
|
2018-05-07 23:40:04 +02:00
|
|
|
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)
|
2018-06-25 06:58:04 +02:00
|
|
|
.request();
|
2018-05-07 23:40:04 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
}
|
|
|
|
|
2018-05-01 07:04:24 +02:00
|
|
|
#[test]
|
|
|
|
fn test_nested_scope() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-01 07:04:24 +02:00
|
|
|
.scope("/app", |scope| {
|
|
|
|
scope.nested("/t1", |scope| {
|
|
|
|
scope.resource("/path1", |r| r.f(|_| HttpResponse::Created()))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1/path1").request();
|
2018-04-30 07:19:52 +02:00
|
|
|
let resp = app.run(req);
|
2018-05-02 02:19:15 +02:00
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
2018-04-30 07:19:52 +02:00
|
|
|
}
|
|
|
|
|
2018-05-23 22:21:29 +02:00
|
|
|
#[test]
|
|
|
|
fn test_nested_scope_root() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-23 22:21:29 +02:00
|
|
|
.scope("/app", |scope| {
|
|
|
|
scope.nested("/t1", |scope| {
|
|
|
|
scope
|
|
|
|
.resource("", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
.resource("/", |r| r.f(|_| HttpResponse::Created()))
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1").request();
|
2018-05-23 22:21:29 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/t1/").request();
|
2018-05-23 22:21:29 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
|
|
|
}
|
|
|
|
|
2018-05-07 23:40:04 +02:00
|
|
|
#[test]
|
|
|
|
fn test_nested_scope_filter() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-07 23:40:04 +02:00
|
|
|
.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)
|
2018-06-25 06:58:04 +02:00
|
|
|
.request();
|
2018-05-07 23:40:04 +02:00
|
|
|
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)
|
2018-06-25 06:58:04 +02:00
|
|
|
.request();
|
2018-05-07 23:40:04 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::OK);
|
|
|
|
}
|
|
|
|
|
2018-05-07 22:50:43 +02:00
|
|
|
#[test]
|
|
|
|
fn test_nested_scope_with_variable_segment() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-07 22:50:43 +02:00
|
|
|
.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();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/project_1/path1").request();
|
2018-05-07 22:50:43 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
|
|
|
|
|
|
|
match resp.as_msg().body() {
|
2018-05-07 23:40:04 +02:00
|
|
|
&Body::Binary(ref b) => {
|
2018-05-07 22:50:43 +02:00
|
|
|
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() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-05-07 22:50:43 +02:00
|
|
|
.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();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/test/1/path1").request();
|
2018-05-07 22:50:43 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::CREATED);
|
|
|
|
|
|
|
|
match resp.as_msg().body() {
|
2018-05-07 23:40:04 +02:00
|
|
|
&Body::Binary(ref b) => {
|
2018-05-07 22:50:43 +02:00
|
|
|
let bytes: Bytes = b.clone().into();
|
|
|
|
assert_eq!(bytes, Bytes::from_static(b"project: test - 1"));
|
|
|
|
}
|
|
|
|
_ => panic!(),
|
|
|
|
}
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/test/1/path2").request();
|
2018-05-07 22:50:43 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
|
|
|
}
|
|
|
|
|
2018-04-30 04:39:28 +02:00
|
|
|
#[test]
|
|
|
|
fn test_default_resource() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-04-30 04:39:28 +02:00
|
|
|
.scope("/app", |scope| {
|
|
|
|
scope
|
|
|
|
.resource("/path1", |r| r.f(|_| HttpResponse::Ok()))
|
|
|
|
.default_resource(|r| r.f(|_| HttpResponse::BadRequest()))
|
|
|
|
})
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app/path2").request();
|
2018-04-30 04:39:28 +02:00
|
|
|
let resp = app.run(req);
|
2018-05-02 02:19:15 +02:00
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST);
|
2018-04-30 04:39:28 +02:00
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/path2").request();
|
2018-04-30 04:39:28 +02:00
|
|
|
let resp = app.run(req);
|
2018-05-02 02:19:15 +02:00
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|
2018-06-03 09:48:12 +02:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_default_resource_propagation() {
|
2018-06-21 19:06:23 +02:00
|
|
|
let app = App::new()
|
2018-06-03 09:48:12 +02:00
|
|
|
.scope("/app1", |scope| {
|
|
|
|
scope.default_resource(|r| r.f(|_| HttpResponse::BadRequest()))
|
|
|
|
})
|
|
|
|
.scope("/app2", |scope| scope)
|
|
|
|
.default_resource(|r| r.f(|_| HttpResponse::MethodNotAllowed()))
|
|
|
|
.finish();
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/non-exist").request();
|
2018-06-03 09:48:12 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app1/non-exist").request();
|
2018-06-03 09:48:12 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::BAD_REQUEST);
|
|
|
|
|
2018-06-25 06:58:04 +02:00
|
|
|
let req = TestRequest::with_uri("/app2/non-exist").request();
|
2018-06-03 09:48:12 +02:00
|
|
|
let resp = app.run(req);
|
|
|
|
assert_eq!(resp.as_msg().status(), StatusCode::METHOD_NOT_ALLOWED);
|
|
|
|
}
|
2018-04-30 04:39:28 +02:00
|
|
|
}
|