Compare commits

...

23 Commits

Author SHA1 Message Date
Armin Ronacher 6848a12095 prepare 0.6.12 release 2018-06-09 01:22:19 +02:00
Nikolay Kim 4797298706 Allow to use custom resolver for ClientConnector 2018-06-08 16:10:47 -07:00
Nikolay Kim 5eaf4cbefd update changelog 2018-06-08 08:42:10 -07:00
Nikolay Kim 7f1844e541 fix doc test 2018-06-07 20:22:50 -07:00
Nikolay Kim 6c7ac7fc22 update changelog 2018-06-07 20:07:58 -07:00
Nikolay Kim 42f9e1034b add Host predicate 2018-06-07 20:05:45 -07:00
Nikolay Kim e3cd0fdd13 add application filters 2018-06-07 20:05:26 -07:00
Nikolay Kim 40ff550460 update changelog 2018-06-07 20:04:40 -07:00
Armin Ronacher 7119340d44 Added improved failure interoperability with downcasting (#285)
Deprecates Error::cause and introduces failure interoperability functions and downcasting.
2018-06-07 20:03:10 -07:00
Nikolay Kim e140bc3906 prep release 2018-06-05 09:44:29 -07:00
Nikolay Kim fdc08d365d metadata for docs.rs 2018-06-05 08:59:30 -07:00
Nikolay Kim 8f1b88e39e update changelog 2018-06-05 08:53:27 -07:00
Nikolay Kim 6a40a0a466 fix multipart boundary parsing #282 2018-06-05 08:52:46 -07:00
Nikolay Kim 89fc6b6ac9 changelog 2018-06-05 07:42:52 -07:00
Nikolay Kim afa67b838a CORS: Do not validate Origin header on non-OPTION requests #271 2018-06-05 07:41:13 -07:00
Nikolay Kim f7b7d282bf Middleware::response is not invoked if error result was returned by another Middleware::start #255 2018-06-04 13:57:54 -07:00
Nikolay Kim 09780ea9f3 changelog updates 2018-06-02 15:03:34 -07:00
Nikolay Kim a7dab950f3 Support chunked encoding for UrlEncoded body #262 2018-06-02 15:02:42 -07:00
Nikolay Kim ec0737e392 fix doc test 2018-06-02 13:48:37 -07:00
Nikolay Kim d664993d56 remove debug prints 2018-06-02 11:58:11 -07:00
Nikolay Kim a9c6c57a67 remove debug print 2018-06-02 11:55:27 -07:00
Nikolay Kim 08e7374eee update changelog 2018-06-02 11:46:53 -07:00
Nikolay Kim 42da1448fb fixed HttpRequest::url_for for a named route with no variables #265 2018-06-02 11:46:02 -07:00
16 changed files with 741 additions and 199 deletions

View File

@ -1,5 +1,38 @@
# Changes
## [0.6.12] - 2018-06-08
### Added
* Add `Host` filter #287
* Allow to filter applications
* Improved failure interoperability with downcasting #285
* Allow to use custom resolver for `ClientConnector`
### Deprecated
* `Error::cause()` and introduces failure interoperability functions and downcasting.
## [0.6.11] - 2018-06-05
### Fixed
* Support chunked encoding for UrlEncoded body #262
* `HttpRequest::url_for()` for a named route with no variables segments #265
* `Middleware::response()` is not invoked if error result was returned by another `Middleware::start()` #255
* CORS: Do not validate Origin header on non-OPTION requests #271
* Fix multipart upload "Incomplete" error #282
## [0.6.10] - 2018-05-24
### Added

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "0.6.10"
version = "0.6.12"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@ -46,8 +46,11 @@ flate2-c = ["flate2/miniz-sys"]
# rust backend for flate2 crate
flate2-rust = ["flate2/rust_backend"]
[package.metadata.docs.rs]
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
[dependencies]
actix = "^0.5.5"
actix = "^0.5.8"
base64 = "0.9"
bitflags = "1.0"

View File

@ -22,6 +22,7 @@ pub struct HttpApplication<S = ()> {
prefix_len: usize,
router: Router,
inner: Rc<UnsafeCell<Inner<S>>>,
filters: Option<Vec<Box<Predicate<S>>>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>,
}
@ -143,11 +144,21 @@ impl<S: 'static> HttpHandler for HttpApplication<S> {
|| path.split_at(self.prefix_len).1.starts_with('/'))
};
if m {
let mut req = req.with_state(Rc::clone(&self.state), self.router.clone());
let tp = self.get_handler(&mut req);
let mut req2 =
req.clone_with_state(Rc::clone(&self.state), self.router.clone());
if let Some(ref filters) = self.filters {
for filter in filters {
if !filter.check(&mut req2) {
return Err(req);
}
}
}
let tp = self.get_handler(&mut req2);
let inner = Rc::clone(&self.inner);
Ok(Box::new(Pipeline::new(
req,
req2,
Rc::clone(&self.middlewares),
inner,
tp,
@ -168,6 +179,7 @@ struct ApplicationParts<S> {
external: HashMap<String, Resource>,
encoding: ContentEncoding,
middlewares: Vec<Box<Middleware<S>>>,
filters: Vec<Box<Predicate<S>>>,
}
/// Structure that follows the builder pattern for building application
@ -190,6 +202,7 @@ impl App<()> {
handlers: Vec::new(),
external: HashMap::new(),
encoding: ContentEncoding::Auto,
filters: Vec::new(),
middlewares: Vec::new(),
}),
}
@ -229,6 +242,7 @@ where
handlers: Vec::new(),
external: HashMap::new(),
middlewares: Vec::new(),
filters: Vec::new(),
encoding: ContentEncoding::Auto,
}),
}
@ -267,8 +281,8 @@ where
/// let app = App::new()
/// .prefix("/app")
/// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .finish();
/// }
@ -285,6 +299,26 @@ where
self
}
/// Add match predicate to application.
///
/// ```rust
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn main() {
/// App::new()
/// .filter(pred::Host("www.rust-lang.org"))
/// .resource("/path", |r| r.f(|_| HttpResponse::Ok()))
/// # .finish();
/// # }
/// ```
pub fn filter<T: Predicate<S> + 'static>(mut self, p: T) -> App<S> {
{
let parts = self.parts.as_mut().expect("Use after finish");
parts.filters.push(Box::new(p));
}
self
}
/// Configure route for a specific path.
///
/// This is a simplified version of the `App::resource()` method.
@ -300,10 +334,12 @@ where
///
/// fn main() {
/// let app = App::new()
/// .route("/test", http::Method::GET,
/// |_: HttpRequest| HttpResponse::Ok())
/// .route("/test", http::Method::POST,
/// |_: HttpRequest| HttpResponse::MethodNotAllowed());
/// .route("/test", http::Method::GET, |_: HttpRequest| {
/// HttpResponse::Ok()
/// })
/// .route("/test", http::Method::POST, |_: HttpRequest| {
/// HttpResponse::MethodNotAllowed()
/// });
/// }
/// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> App<S>
@ -345,12 +381,12 @@ where
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// 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()))
/// });
/// 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()))
/// });
/// }
/// ```
///
@ -402,11 +438,10 @@ where
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .resource("/users/{userid}/{friend}", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// });
/// let app = App::new().resource("/users/{userid}/{friend}", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// });
/// }
/// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> App<S>
@ -469,9 +504,9 @@ where
/// use actix_web::{App, HttpRequest, HttpResponse, Result};
///
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(HttpResponse::Ok().into())
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(HttpResponse::Ok().into())
/// }
///
/// fn main() {
@ -514,13 +549,11 @@ where
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .handler("/app", |req: HttpRequest| {
/// match *req.method() {
/// http::Method::GET => HttpResponse::Ok(),
/// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// _ => HttpResponse::NotFound(),
/// }});
/// let app = App::new().handler("/app", |req: HttpRequest| match *req.method() {
/// http::Method::GET => HttpResponse::Ok(),
/// http::Method::POST => HttpResponse::MethodNotAllowed(),
/// _ => HttpResponse::NotFound(),
/// });
/// }
/// ```
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> App<S> {
@ -561,15 +594,14 @@ where
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{App, HttpResponse, fs, middleware};
/// use actix_web::{fs, middleware, App, HttpResponse};
///
/// // this function could be located in different module
/// fn config(app: App) -> App {
/// app
/// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// })
/// app.resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
/// })
/// }
///
/// fn main() {
@ -610,6 +642,11 @@ where
handlers: parts.handlers,
resources,
}));
let filters = if parts.filters.is_empty() {
None
} else {
Some(parts.filters)
};
HttpApplication {
state: Rc::new(parts.state),
@ -618,6 +655,7 @@ where
prefix,
prefix_len,
inner,
filters,
}
}
@ -636,19 +674,22 @@ where
/// struct State2;
///
/// fn main() {
/// # thread::spawn(|| {
/// server::new(|| { vec![
/// App::with_state(State1)
/// .prefix("/app1")
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .boxed(),
/// App::with_state(State2)
/// .prefix("/app2")
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .boxed() ]})
/// .bind("127.0.0.1:8080").unwrap()
/// # thread::spawn(|| {
/// server::new(|| {
/// vec![
/// App::with_state(State1)
/// .prefix("/app1")
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .boxed(),
/// App::with_state(State2)
/// .prefix("/app2")
/// .resource("/", |r| r.f(|r| HttpResponse::Ok()))
/// .boxed(),
/// ]
/// }).bind("127.0.0.1:8080")
/// .unwrap()
/// .run()
/// # });
/// # });
/// }
/// ```
pub fn boxed(mut self) -> Box<HttpHandler> {
@ -699,7 +740,8 @@ mod tests {
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use test::TestRequest;
use pred;
use test::{TestRequest, TestServer};
#[test]
fn test_default_resource() {
@ -898,4 +940,21 @@ mod tests {
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_filter() {
let mut srv = TestServer::with_factory(|| {
App::new()
.filter(pred::Get())
.handler("/test", |_| HttpResponse::Ok())
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv.post().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}
}

View File

@ -9,8 +9,8 @@ use actix::actors::{Connect as ResolveConnect, Connector, ConnectorError};
use actix::fut::WrapFuture;
use actix::registry::ArbiterService;
use actix::{
fut, Actor, ActorFuture, ActorResponse, Arbiter, AsyncContext, Context,
ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn,
fut, Actor, ActorFuture, ActorResponse, Addr, Arbiter, AsyncContext, Context,
ContextFutureSpawner, Handler, Message, Recipient, Supervised, Syn, Unsync,
};
use futures::task::{current as current_task, Task};
@ -181,6 +181,7 @@ pub struct ClientConnector {
pool: Rc<Pool>,
pool_modified: Rc<Cell<bool>>,
resolver: Addr<Unsync, Connector>,
conn_lifetime: Duration,
conn_keep_alive: Duration,
limit: usize,
@ -225,6 +226,7 @@ impl Default for ClientConnector {
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified,
connector: builder.build().unwrap(),
resolver: Connector::from_registry(),
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
@ -245,6 +247,7 @@ impl Default for ClientConnector {
subscriber: None,
pool: Rc::new(Pool::new(Rc::clone(&_modified))),
pool_modified: _modified,
resolver: Connector::from_registry(),
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
@ -277,9 +280,9 @@ impl ClientConnector {
/// # use std::io::Write;
/// extern crate openssl;
/// use actix::prelude::*;
/// use actix_web::client::{Connect, ClientConnector};
/// use actix_web::client::{ClientConnector, Connect};
///
/// use openssl::ssl::{SslMethod, SslConnector};
/// use openssl::ssl::{SslConnector, SslMethod};
///
/// fn main() {
/// let sys = System::new("test");
@ -312,6 +315,7 @@ impl ClientConnector {
subscriber: None,
pool: Rc::new(Pool::new(Rc::clone(&modified))),
pool_modified: modified,
resolver: Connector::from_registry(),
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
@ -371,6 +375,12 @@ impl ClientConnector {
self
}
/// Use custom resolver actor
pub fn resolver(mut self, addr: Addr<Unsync, Connector>) -> Self {
self.resolver = addr;
self
}
fn acquire(&mut self, key: &Key) -> Acquire {
// check limits
if self.limit > 0 {
@ -705,7 +715,7 @@ impl Handler<Connect> for ClientConnector {
{
ActorResponse::async(
Connector::from_registry()
self.resolver
.send(
ResolveConnect::host_and_port(&conn.0.host, port)
.timeout(conn_timeout),

View File

@ -33,21 +33,42 @@ use httpresponse::HttpResponse;
/// `Result`.
pub type Result<T, E = Error> = result::Result<T, E>;
/// General purpose actix web error
/// General purpose actix web error.
///
/// An actix web error is used to carry errors from `failure` or `std::error`
/// through actix in a convenient way. It can be created through through
/// converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created
/// for it that can be used to create an http response from it this means that
/// if you have access to an actix `Error` you can always get a
/// `ResponseError` reference from it.
pub struct Error {
cause: Box<ResponseError>,
backtrace: Option<Backtrace>,
}
impl Error {
/// Returns a reference to the underlying cause of this Error.
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665
/// Deprecated way to reference the underlying response error.
#[deprecated(since = "0.6.0", note = "please use `Error::as_response_error()` instead")]
pub fn cause(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the underlying cause of this `Error` as `Fail`
pub fn as_fail(&self) -> &Fail {
self.cause.as_fail()
}
/// Returns the reference to the underlying `ResponseError`.
pub fn as_response_error(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the Backtrace carried by this error, if it
/// carries one.
///
/// This uses the same `Backtrace` type that `failure` uses.
pub fn backtrace(&self) -> &Backtrace {
if let Some(bt) = self.cause.backtrace() {
bt
@ -55,10 +76,61 @@ impl Error {
self.backtrace.as_ref().unwrap()
}
}
/// Attempts to downcast this `Error` to a particular `Fail` type by reference.
///
/// If the underlying error is not of type `T`, this will return `None`.
pub fn downcast_ref<T: Fail>(&self) -> Option<&T> {
// in the most trivial way the cause is directly of the requested type.
if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) {
return Some(rv);
}
// in the more complex case the error has been constructed from a failure
// error. This happens because we implement From<failure::Error> by
// calling compat() and then storing it here. In failure this is
// represented by a failure::Error being wrapped in a failure::Compat.
//
// So we first downcast into that compat, to then further downcast through
// the failure's Error downcasting system into the original failure.
//
// This currently requires a transmute. This could be avoided if failure
// provides a deref: https://github.com/rust-lang-nursery/failure/pull/213
let compat: Option<&failure::Compat<failure::Error>> = Fail::downcast_ref(self.cause.as_fail());
if let Some(compat) = compat {
pub struct CompatWrappedError {
error: failure::Error,
}
let compat: &CompatWrappedError = unsafe {
::std::mem::transmute(compat)
};
compat.error.downcast_ref()
} else {
None
}
}
}
/// Helper trait to downcast a response error into a fail.
///
/// This is currently not exposed because it's unclear if this is the best way to
/// achieve the downcasting on `Error` for which this is needed.
#[doc(hidden)]
pub trait InternalResponseErrorAsFail {
#[doc(hidden)]
fn as_fail(&self) -> &Fail;
#[doc(hidden)]
fn as_mut_fail(&mut self) -> &mut Fail;
}
#[doc(hidden)]
impl<T: ResponseError> InternalResponseErrorAsFail for T {
fn as_fail(&self) -> &Fail { self }
fn as_mut_fail(&mut self) -> &mut Fail { self }
}
/// Error that can be converted to `HttpResponse`
pub trait ResponseError: Fail {
pub trait ResponseError: Fail + InternalResponseErrorAsFail {
/// Create response for error
///
/// Internal server error is generated by default.
@ -833,7 +905,7 @@ mod tests {
}
#[test]
fn test_cause() {
fn test_as_fail() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned();
let e = ParseError::Io(orig);
@ -851,7 +923,7 @@ mod tests {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned();
let e = Error::from(orig);
assert_eq!(format!("{}", e.cause()), desc);
assert_eq!(format!("{}", e.as_fail()), desc);
}
#[test]
@ -950,6 +1022,32 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_error_downcasting_direct() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = DemoError.into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_downcasting_compat() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = failure::Error::from(DemoError).into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_helpers() {
let r: HttpResponse = ErrorBadRequest("err").into();

View File

@ -148,7 +148,6 @@ pub trait HttpMessage {
/// Returns error:
///
/// * content type is not `application/x-www-form-urlencoded`
/// * transfer encoding is `chunked`.
/// * content-length is greater than 256k
///
/// ## Server example
@ -365,9 +364,7 @@ where
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
if let Some(req) = self.req.take() {
if req.chunked().unwrap_or(false) {
return Err(UrlencodedError::Chunked);
} else if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Some(len) = req.headers().get(header::CONTENT_LENGTH) {
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
if len > 262_144 {
@ -575,13 +572,6 @@ mod tests {
#[test]
fn test_urlencoded_error() {
let req =
TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish();
assert_eq!(
req.urlencoded::<Info>().poll().err().unwrap(),
UrlencodedError::Chunked
);
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",

View File

@ -141,6 +141,12 @@ impl HttpRequest<()> {
pub fn with_state<S>(self, state: Rc<S>, router: Router) -> HttpRequest<S> {
HttpRequest(self.0, Some(state), Some(router))
}
pub(crate) fn clone_with_state<S>(
&self, state: Rc<S>, router: Router,
) -> HttpRequest<S> {
HttpRequest(self.0.clone(), Some(state), Some(router))
}
}
impl<S> HttpMessage for HttpRequest<S> {
@ -325,6 +331,15 @@ impl<S> HttpRequest<S> {
}
}
/// Generate url for named resource
///
/// This method is similar to `HttpRequest::url_for()` but it can be used
/// for urls that do not contain variable parts.
pub fn url_for_static(&self, name: &str) -> Result<Url, UrlGenerationError> {
const NO_PARAMS: [&str; 0] = [];
self.url_for(name, &NO_PARAMS)
}
/// This method returns reference to current `Router` object.
#[inline]
pub fn router(&self) -> Option<&Router> {
@ -695,20 +710,38 @@ mod tests {
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let routes =
vec![(Resource::new("index", "/user/{name}.{ext}"), Some(resource))];
let routes = vec![(Resource::new("index", "/user/{name}.html"), Some(resource))];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
assert!(router.has_route("/user/test.html"));
assert!(!router.has_route("/prefix/user/test.html"));
let req = req.with_state(Rc::new(()), router);
let url = req.url_for("index", &["test", "html"]);
let url = req.url_for("index", &["test"]);
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/user/test.html"
);
}
#[test]
fn test_url_for_static() {
let req = TestRequest::with_header(header::HOST, "www.rust-lang.org").finish();
let mut resource = ResourceHandler::<()>::default();
resource.name("index");
let routes = vec![(Resource::new("index", "/index.html"), Some(resource))];
let (router, _) = Router::new("/prefix/", ServerSettings::default(), routes);
assert!(router.has_route("/index.html"));
assert!(!router.has_route("/prefix/index.html"));
let req = req.with_state(Rc::new(()), router);
let url = req.url_for_static("index");
assert_eq!(
url.ok().unwrap().as_str(),
"http://www.rust-lang.org/prefix/index.html"
);
}
#[test]
fn test_url_for_external() {
let req = HttpRequest::default();

View File

@ -89,7 +89,7 @@ impl HttpResponse {
/// Constructs a error response
#[inline]
pub fn from_error(error: Error) -> HttpResponse {
let mut resp = error.cause().error_response();
let mut resp = error.as_response_error().error_response();
resp.get_mut().error = Some(error);
resp
}

View File

@ -19,16 +19,16 @@
//!
//! ```rust
//! # extern crate actix_web;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//! use actix_web::middleware::cors::Cors;
//! use actix_web::{http, App, HttpRequest, HttpResponse};
//!
//! fn index(mut req: HttpRequest) -> &'static str {
//! "Hello world"
//! "Hello world"
//! }
//!
//! fn main() {
//! let app = App::new()
//! .configure(|app| Cors::for_app(app) // <- Construct CORS middleware builder
//! let app = App::new().configure(|app| {
//! Cors::for_app(app) // <- Construct CORS middleware builder
//! .allowed_origin("https://www.rust-lang.org/")
//! .allowed_methods(vec!["GET", "POST"])
//! .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
@ -38,7 +38,8 @@
//! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
//! r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
//! })
//! .register());
//! .register()
//! });
//! }
//! ```
//! In this example custom *CORS* middleware get registered for "/index.html"
@ -232,18 +233,20 @@ impl Cors {
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
/// let app = App::new().configure(
/// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/")
/// .resource("/resource", |r| { // register resource
/// r.method(http::Method::GET).f(|_| HttpResponse::Ok());
/// })
/// .register() // construct CORS and return application instance
/// );
/// .register()
/// }, // construct CORS and return application instance
/// );
/// }
/// ```
pub fn for_app<S: 'static>(app: App<S>) -> CorsBuilder<S> {
@ -420,7 +423,10 @@ impl<S> Middleware<S> for Cors {
.finish(),
))
} else {
self.validate_origin(req)?;
// Only check requests with a origin header.
if req.headers().contains_key(header::ORIGIN) {
self.validate_origin(req)?;
}
Ok(Started::Done)
}
@ -491,8 +497,8 @@ impl<S> Middleware<S> for Cors {
/// ```rust
/// # extern crate http;
/// # extern crate actix_web;
/// use http::header;
/// use actix_web::middleware::cors;
/// use http::header;
///
/// # fn main() {
/// let cors = cors::Cors::build()
@ -764,12 +770,13 @@ impl<S: 'static> CorsBuilder<S> {
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{http, App, HttpResponse};
/// use actix_web::middleware::cors::Cors;
/// use actix_web::{http, App, HttpResponse};
///
/// fn main() {
/// let app = App::new()
/// .configure(|app| Cors::for_app(app) // <- Construct CORS builder
/// let app = App::new().configure(
/// |app| {
/// Cors::for_app(app) // <- Construct CORS builder
/// .allowed_origin("https://www.rust-lang.org/")
/// .allowed_methods(vec!["GET", "POST"])
/// .allowed_header(http::header::CONTENT_TYPE)
@ -781,8 +788,9 @@ impl<S: 'static> CorsBuilder<S> {
/// r.method(http::Method::HEAD)
/// .f(|_| HttpResponse::MethodNotAllowed());
/// })
/// .register() // construct CORS and return application instance
/// );
/// .register()
/// }, // construct CORS and return application instance
/// );
/// }
/// ```
pub fn resource<F, R>(&mut self, path: &str, f: F) -> &mut CorsBuilder<S>
@ -1001,16 +1009,15 @@ mod tests {
assert!(cors.start(&mut req).unwrap().is_done());
}
#[test]
#[should_panic(expected = "MissingOrigin")]
fn test_validate_missing_origin() {
let cors = Cors::build()
.allowed_origin("https://www.example.com")
.finish();
let mut req = HttpRequest::default();
cors.start(&mut req).unwrap();
}
// #[test]
// #[should_panic(expected = "MissingOrigin")]
// fn test_validate_missing_origin() {
// let mut cors = Cors::build()
// .allowed_origin("https://www.example.com")
// .finish();
// let mut req = HttpRequest::default();
// cors.start(&mut req).unwrap();
// }
#[test]
#[should_panic(expected = "OriginNotAllowed")]
@ -1127,10 +1134,19 @@ mod tests {
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let request = srv
.get()
.uri(srv.url("/test"))
.header("ORIGIN", "https://www.example2.com")
.finish()
.unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let request = srv
.get()
.uri(srv.url("/test"))

View File

@ -511,7 +511,7 @@ where
match payload.read_exact(boundary.len() + 4)? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(None) => Err(MultipartError::Incomplete),
Async::Ready(Some(chunk)) => {
Async::Ready(Some(mut chunk)) => {
if &chunk[..2] == b"\r\n"
&& &chunk[2..4] == b"--"
&& &chunk[4..] == boundary.as_bytes()
@ -519,7 +519,10 @@ where
payload.unread_data(chunk);
Ok(Async::Ready(None))
} else {
Ok(Async::Ready(Some(chunk)))
// \r might be part of data stream
let ch = chunk.split_to(1);
payload.unread_data(chunk);
Ok(Async::Ready(Some(ch)))
}
}
}

View File

@ -262,7 +262,7 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
_s: PhantomData,
})
}
Err(err) => return ProcessResponse::init(err.into()),
Err(err) => return RunMiddlewares::init(info, err.into()),
}
}
}
@ -294,13 +294,13 @@ impl<S: 'static, H: PipelineHandler<S>> StartMiddlewares<S, H> {
continue 'outer;
}
Err(err) => {
return Some(ProcessResponse::init(err.into()))
return Some(RunMiddlewares::init(info, err.into()))
}
}
}
}
}
Err(err) => return Some(ProcessResponse::init(err.into())),
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
}
}
}

View File

@ -192,6 +192,45 @@ impl<S: 'static> Predicate<S> for HeaderPredicate<S> {
}
}
/// Return predicate that matches if request contains specified Host name.
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::{pred, App, HttpResponse};
///
/// fn main() {
/// App::new().resource("/index.html", |r| {
/// r.route()
/// .filter(pred::Host("www.rust-lang.org"))
/// .f(|_| HttpResponse::MethodNotAllowed())
/// });
/// }
/// ```
pub fn Host<S: 'static, H: AsRef<str>>(host: H) -> HostPredicate<S> {
HostPredicate(host.as_ref().to_string(), None, PhantomData)
}
#[doc(hidden)]
pub struct HostPredicate<S>(String, Option<String>, PhantomData<S>);
impl<S> HostPredicate<S> {
/// Set reuest scheme to match
pub fn scheme<H: AsRef<str>>(&mut self, scheme: H) {
self.1 = Some(scheme.as_ref().to_string())
}
}
impl<S: 'static> Predicate<S> for HostPredicate<S> {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
let info = req.connection_info();
if let Some(ref scheme) = self.1 {
self.0 == info.host() && scheme == info.scheme()
} else {
self.0 == info.host()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -224,6 +263,28 @@ mod tests {
assert!(!pred.check(&mut req));
}
#[test]
fn test_host() {
let mut headers = HeaderMap::new();
headers.insert(
header::HOST,
header::HeaderValue::from_static("www.rust-lang.org"),
);
let mut req = HttpRequest::new(
Method::GET,
Uri::from_str("/").unwrap(),
Version::HTTP_11,
headers,
None,
);
let pred = Host("www.rust-lang.org");
assert!(pred.check(&mut req));
let pred = Host("localhost");
assert!(!pred.check(&mut req));
}
#[test]
fn test_methods() {
let mut req = HttpRequest::new(

View File

@ -66,13 +66,12 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web;
/// # use actix_web::*;
/// # fn main() {
/// App::new()
/// .resource("/path", |r|
/// r.route()
/// .filter(pred::Get())
/// .filter(pred::Header("content-type", "text/plain"))
/// .f(|req| HttpResponse::Ok())
/// )
/// App::new().resource("/path", |r| {
/// r.route()
/// .filter(pred::Get())
/// .filter(pred::Header("content-type", "text/plain"))
/// .f(|req| HttpResponse::Ok())
/// })
/// # .finish();
/// # }
/// ```
@ -115,7 +114,7 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Result, http};
/// use actix_web::{http, App, Path, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
@ -129,8 +128,9 @@ impl<S: 'static> Route<S> {
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
///
@ -143,7 +143,7 @@ impl<S: 'static> Route<S> {
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// # use std::collections::HashMap;
/// use actix_web::{http, App, Query, Path, Result, Json};
/// use actix_web::{http, App, Json, Path, Query, Result};
///
/// #[derive(Deserialize)]
/// struct Info {
@ -151,14 +151,17 @@ impl<S: 'static> Route<S> {
/// }
///
/// /// extract path info using serde
/// fn index(info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>)) -> Result<String> {
/// fn index(
/// info: (Path<Info>, Query<HashMap<String, String>>, Json<Info>),
/// ) -> Result<String> {
/// Ok(format!("Welcome {}!", info.0.username))
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with<T, F, R>(&mut self, handler: F) -> ExtractorConfig<S, T>
@ -181,7 +184,7 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Path, Error, http};
/// use actix_web::{http, App, Error, Path};
/// use futures::Future;
///
/// #[derive(Deserialize)]
@ -190,15 +193,15 @@ impl<S: 'static> Route<S> {
/// }
///
/// /// extract path info using serde
/// fn index(info: Path<Info>) -> Box<Future<Item=&'static str, Error=Error>> {
/// fn index(info: Path<Info>) -> Box<Future<Item = &'static str, Error = Error>> {
/// unimplemented!()
/// }
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET)
/// .with_async(index)); // <- use `with` extractor
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with_async(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with_async<T, F, R, I, E>(&mut self, handler: F) -> ExtractorConfig<S, T>
@ -222,7 +225,7 @@ impl<S: 'static> Route<S> {
/// # extern crate actix_web;
/// # extern crate futures;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{App, Query, Path, Result, http};
/// use actix_web::{http, App, Path, Query, Result};
///
/// #[derive(Deserialize)]
/// struct PParam {
@ -241,8 +244,9 @@ impl<S: 'static> Route<S> {
///
/// fn main() {
/// let app = App::new().resource(
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index)); // <- use `with` extractor
/// "/{username}/index.html", // <- define path parameters
/// |r| r.method(http::Method::GET).with2(index),
/// ); // <- use `with` extractor
/// }
/// ```
pub fn with2<T1, T2, F, R>(
@ -424,7 +428,7 @@ impl<S: 'static> StartMiddlewares<S> {
_s: PhantomData,
})
}
Err(err) => return FinishingMiddlewares::init(info, err.into()),
Err(err) => return RunMiddlewares::init(info, err.into()),
}
}
}
@ -455,16 +459,13 @@ impl<S: 'static> StartMiddlewares<S> {
continue 'outer;
}
Err(err) => {
return Some(FinishingMiddlewares::init(
info,
err.into(),
))
return Some(RunMiddlewares::init(info, err.into()))
}
}
}
}
}
Err(err) => return Some(FinishingMiddlewares::init(info, err.into())),
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
}
}
}

View File

@ -335,22 +335,41 @@ impl Resource {
U: IntoIterator<Item = I>,
I: AsRef<str>,
{
let mut iter = elements.into_iter();
let mut path = if self.rtp != ResourceType::External {
format!("{}/", router.prefix())
} else {
String::new()
};
for el in &self.elements {
match *el {
PatternElement::Str(ref s) => path.push_str(s),
PatternElement::Var(_) => {
if let Some(val) = iter.next() {
path.push_str(val.as_ref())
} else {
return Err(UrlGenerationError::NotEnoughElements);
let mut path = match self.tp {
PatternType::Prefix(ref p) => p.to_owned(),
PatternType::Static(ref p) => p.to_owned(),
PatternType::Dynamic(..) => {
let mut path = String::new();
let mut iter = elements.into_iter();
for el in &self.elements {
match *el {
PatternElement::Str(ref s) => path.push_str(s),
PatternElement::Var(_) => {
if let Some(val) = iter.next() {
path.push_str(val.as_ref())
} else {
return Err(UrlGenerationError::NotEnoughElements);
}
}
}
}
path
}
};
if self.rtp != ResourceType::External {
let prefix = router.prefix();
if prefix.ends_with('/') {
if path.starts_with('/') {
path.insert_str(0, &prefix[..prefix.len() - 1]);
} else {
path.insert_str(0, prefix);
}
} else {
if !path.starts_with('/') {
path.insert(0, '/');
}
path.insert_str(0, prefix);
}
}
Ok(path)
@ -415,6 +434,10 @@ impl Resource {
}
}
if !el.is_empty() {
elems.push(PatternElement::Str(el.clone()));
}
let re = if is_dynamic {
if !for_prefix {
re1.push('$');
@ -447,7 +470,7 @@ mod tests {
use test::TestRequest;
#[test]
fn test_recognizer() {
fn test_recognizer10() {
let routes = vec![
(Resource::new("", "/name"), Some(ResourceHandler::default())),
(
@ -470,6 +493,10 @@ mod tests {
Resource::new("", "/v/{tail:.*}"),
Some(ResourceHandler::default()),
),
(
Resource::new("", "/test2/{test}.html"),
Some(ResourceHandler::default()),
),
(
Resource::new("", "{test}/index.html"),
Some(ResourceHandler::default()),
@ -507,8 +534,12 @@ mod tests {
"blah-blah/index.html"
);
let mut req = TestRequest::with_uri("/bbb/index.html").finish();
let mut req = TestRequest::with_uri("/test2/index.html").finish();
assert_eq!(rec.recognize(&mut req), Some(6));
assert_eq!(req.match_info().get("test").unwrap(), "index");
let mut req = TestRequest::with_uri("/bbb/index.html").finish();
assert_eq!(rec.recognize(&mut req), Some(7));
assert_eq!(req.match_info().get("test").unwrap(), "bbb");
}

View File

@ -38,12 +38,12 @@ type NestedInfo<S> = (Resource, Route<S>, Vec<Box<Predicate<S>>>);
/// use actix_web::{http, App, HttpRequest, HttpResponse};
///
/// fn main() {
/// 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()))
/// });
/// 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()))
/// });
/// }
/// ```
///
@ -89,13 +89,14 @@ impl<S: 'static> Scope<S> {
/// }
///
/// 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())
/// });
/// 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 {
@ -116,12 +117,11 @@ impl<S: 'static> Scope<S> {
/// }
///
/// fn main() {
/// let app = App::new()
/// .scope("/app", |scope| {
/// scope.with_state("/state2", AppState, |scope| {
/// scope.resource("/test1", |r| r.f(index))
/// })
/// });
/// let app = App::new().scope("/app", |scope| {
/// scope.with_state("/state2", AppState, |scope| {
/// scope.resource("/test1", |r| r.f(index))
/// })
/// });
/// }
/// ```
pub fn with_state<F, T: 'static>(mut self, path: &str, state: T, f: F) -> Scope<S>
@ -162,12 +162,9 @@ impl<S: 'static> Scope<S> {
/// }
///
/// fn main() {
/// let app = App::with_state(AppState)
/// .scope("/app", |scope| {
/// scope.nested("/v1", |scope| {
/// scope.resource("/test1", |r| r.f(index))
/// })
/// });
/// 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>
@ -211,12 +208,13 @@ impl<S: 'static> Scope<S> {
/// }
///
/// fn main() {
/// let app = App::new()
/// .scope("/app", |scope| {
/// scope.route("/test1", http::Method::GET, index)
/// .route("/test2", http::Method::POST,
/// |_: HttpRequest| HttpResponse::MethodNotAllowed())
/// });
/// let app = App::new().scope("/app", |scope| {
/// scope.route("/test1", http::Method::GET, index).route(
/// "/test2",
/// http::Method::POST,
/// |_: HttpRequest| HttpResponse::MethodNotAllowed(),
/// )
/// });
/// }
/// ```
pub fn route<T, F, R>(mut self, path: &str, method: Method, f: F) -> Scope<S>
@ -261,17 +259,16 @@ impl<S: 'static> Scope<S> {
/// use actix_web::*;
///
/// fn main() {
/// 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())
/// })
/// });
/// 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())
/// })
/// });
/// }
/// ```
pub fn resource<F, R>(mut self, path: &str, f: F) -> Scope<S>
@ -518,7 +515,7 @@ impl<S: 'static> StartMiddlewares<S> {
_s: PhantomData,
})
}
Err(err) => return Response::init(err.into()),
Err(err) => return RunMiddlewares::init(info, err.into()),
}
}
}
@ -554,12 +551,14 @@ impl<S: 'static> StartMiddlewares<S> {
self.fut = Some(fut);
continue 'outer;
}
Err(err) => return Some(Response::init(err.into())),
Err(err) => {
return Some(RunMiddlewares::init(info, err.into()))
}
}
}
}
}
Err(err) => return Some(Response::init(err.into())),
Err(err) => return Some(RunMiddlewares::init(info, err.into())),
}
}
}

View File

@ -9,6 +9,7 @@ use std::thread;
use std::time::Duration;
use actix::*;
use actix_web::error::{Error, ErrorInternalServerError};
use actix_web::*;
use futures::{future, Future};
use tokio_core::reactor::Timeout;
@ -792,3 +793,207 @@ fn test_async_sync_resource_middleware_multiple() {
thread::sleep(Duration::from_millis(40));
assert_eq!(num3.load(Ordering::Relaxed), 2);
}
struct MiddlewareWithErr;
impl<S> middleware::Middleware<S> for MiddlewareWithErr {
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
Err(ErrorInternalServerError("middleware error"))
}
}
struct MiddlewareAsyncWithErr;
impl<S> middleware::Middleware<S> for MiddlewareAsyncWithErr {
fn start(&self, _req: &mut HttpRequest<S>) -> Result<middleware::Started, Error> {
Ok(middleware::Started::Future(Box::new(future::err(
ErrorInternalServerError("middleware error"),
))))
}
}
#[test]
fn test_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new()
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new()
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().scope("/scope", |scope| {
scope
.middleware(mw1)
.middleware(MiddlewareWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_scope_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().scope("/scope", |scope| {
scope
.middleware(mw1)
.middleware(MiddlewareAsyncWithErr)
.resource("/test", |r| r.h(|_| HttpResponse::Ok()))
})
});
let request = srv.get().uri(srv.url("/scope/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(MiddlewareWithErr);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}
#[test]
fn test_resource_middleware_async_chain_with_error() {
let num1 = Arc::new(AtomicUsize::new(0));
let num2 = Arc::new(AtomicUsize::new(0));
let num3 = Arc::new(AtomicUsize::new(0));
let act_num1 = Arc::clone(&num1);
let act_num2 = Arc::clone(&num2);
let act_num3 = Arc::clone(&num3);
let mut srv = test::TestServer::with_factory(move || {
let mw1 = MiddlewareTest {
start: Arc::clone(&act_num1),
response: Arc::clone(&act_num2),
finish: Arc::clone(&act_num3),
};
App::new().resource("/test", move |r| {
r.middleware(mw1);
r.middleware(MiddlewareAsyncWithErr);
r.h(|_| HttpResponse::Ok());
})
});
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(num1.load(Ordering::Relaxed), 1);
assert_eq!(num2.load(Ordering::Relaxed), 1);
assert_eq!(num3.load(Ordering::Relaxed), 1);
}