1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-18 11:55:36 +02:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Nikolay Kim
01a0f3f5a0 remove unused dependency 2018-04-19 09:54:22 -07:00
Nikolay Kim
2c8d987241 Use Display formatting for InternalError Display implementation #188 2018-04-19 07:55:09 -07:00
Nikolay Kim
813d1d6e66 doc strings layout 2018-04-18 20:41:03 -07:00
Nikolay Kim
48b02abee7 fmt 2018-04-18 20:16:29 -07:00
Nikolay Kim
ce1081432b export session module 2018-04-18 20:11:49 -07:00
Nikolay Kim
e9bdba57a0 Add identity service middleware 2018-04-18 19:05:24 -07:00
Nikolay Kim
f907be585e Middleware response() is not invoked if there was an error in async handler #187 2018-04-18 14:15:53 -07:00
13 changed files with 538 additions and 25 deletions

View File

@@ -1,5 +1,14 @@
# Changes
## 0.5.4 (2018-04-19)
* Add identity service middleware
* Middleware response() is not invoked if there was an error in async handler #187
* Use Display formatting for InternalError Display implementation #188
## 0.5.3 (2018-04-18)
* Impossible to quote slashes in path parameters #182

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "0.5.3"
version = "0.5.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@@ -81,7 +81,6 @@ futures = "0.1"
futures-cpupool = "0.1"
tokio-io = "0.1"
tokio-core = "0.1"
trust-dns-resolver = "0.8"
# native-tls
native-tls = { version="0.1", optional = true }

View File

@@ -580,7 +580,7 @@ impl<T> InternalError<T> {
impl<T> Fail for InternalError<T>
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
fn backtrace(&self) -> Option<&Backtrace> {
Some(&self.backtrace)
@@ -598,16 +598,16 @@ where
impl<T> fmt::Display for InternalError<T>
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Display + 'static,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.cause, f)
fmt::Display::fmt(&self.cause, f)
}
}
impl<T> ResponseError for InternalError<T>
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
fn error_response(&self) -> HttpResponse {
match self.status {
@@ -625,7 +625,7 @@ where
impl<T> Responder for InternalError<T>
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
type Item = HttpResponse;
type Error = Error;
@@ -640,7 +640,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorBadRequest<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::BAD_REQUEST).into()
}
@@ -650,7 +650,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorUnauthorized<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
}
@@ -660,7 +660,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorForbidden<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::FORBIDDEN).into()
}
@@ -670,7 +670,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorNotFound<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::NOT_FOUND).into()
}
@@ -680,7 +680,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorMethodNotAllowed<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
}
@@ -690,7 +690,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorRequestTimeout<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into()
}
@@ -700,7 +700,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorConflict<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::CONFLICT).into()
}
@@ -710,7 +710,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorGone<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::GONE).into()
}
@@ -720,7 +720,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorPreconditionFailed<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
}
@@ -730,7 +730,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorExpectationFailed<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
}
@@ -740,7 +740,7 @@ where
#[allow(non_snake_case)]
pub fn ErrorInternalServerError<T>(err: T) -> Error
where
T: Send + Sync + fmt::Debug + 'static,
T: Send + Sync + fmt::Debug + fmt::Display + 'static,
{
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
}

View File

@@ -201,6 +201,19 @@ impl<S> HttpRequest<S> {
&mut self.as_mut().extensions
}
/// Request extensions
#[inline]
#[doc(hidden)]
pub fn extensions_ro(&self) -> &Extensions {
&self.as_ref().extensions
}
/// Mutable refernece to a the request's extensions
#[inline]
pub fn extensions_mut(&mut self) -> &mut Extensions {
&mut self.as_mut().extensions
}
/// Default `CpuPool`
#[inline]
#[doc(hidden)]
@@ -590,6 +603,8 @@ impl<S> fmt::Debug for HttpRequest<S> {
#[cfg(test)]
mod tests {
#![allow(deprecated)]
use super::*;
use http::{HttpTryFrom, Uri};
use resource::ResourceHandler;

View File

@@ -110,7 +110,6 @@ extern crate percent_encoding;
extern crate serde_json;
extern crate serde_urlencoded;
extern crate smallvec;
extern crate trust_dns_resolver;
#[macro_use]
extern crate actix;

View File

@@ -7,8 +7,8 @@
//!
//! 1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
//! 2. Use any of the builder methods to set fields in the backend.
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
//! constructed backend.
//! 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
//! constructed backend.
//!
//! Cors middleware could be used as parameter for `App::middleware()` or
//! `ResourceHandler::middleware()` methods. But you have to use

389
src/middleware/identity.rs Normal file
View File

@@ -0,0 +1,389 @@
//! Request identity service for Actix applications.
//!
//! [**IdentityService**](struct.IdentityService.html) middleware can be
//! used with different policies types to store identity information.
//!
//! Bu default, only cookie identity policy is implemented. Other backend
//! implementations can be added separately.
//!
//! [**CookieIdentityPolicy**](struct.CookieIdentityPolicy.html)
//! uses cookies as identity storage.
//!
//! To access current request identity
//! [**RequestIdentity**](trait.RequestIdentity.html) should be used.
//! *HttpRequest* implements *RequestIdentity* trait.
//!
//! ```rust
//! use actix_web::middleware::identity::RequestIdentity;
//! use actix_web::middleware::identity::{CookieIdentityPolicy, IdentityService};
//! use actix_web::*;
//!
//! fn index(req: HttpRequest) -> Result<String> {
//! // access request identity
//! if let Some(id) = req.identity() {
//! Ok(format!("Welcome! {}", id))
//! } else {
//! Ok("Welcome Anonymous!".to_owned())
//! }
//! }
//!
//! fn login(mut req: HttpRequest) -> HttpResponse {
//! req.remember("User1".to_owned()); // <- remember identity
//! HttpResponse::Ok().finish()
//! }
//!
//! fn logout(mut req: HttpRequest) -> HttpResponse {
//! req.forget(); // <- remove identity
//! HttpResponse::Ok().finish()
//! }
//!
//! fn main() {
//! let app = App::new().middleware(IdentityService::new(
//! // <- create identity middleware
//! CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
//! .name("auth-cookie")
//! .secure(false),
//! ));
//! }
//! ```
use std::rc::Rc;
use cookie::{Cookie, CookieJar, Key};
use futures::Future;
use futures::future::{FutureResult, err as FutErr, ok as FutOk};
use time::Duration;
use error::{Error, Result};
use http::header::{self, HeaderValue};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middleware::{Middleware, Response, Started};
/// The helper trait to obtain your identity from a request.
///
/// ```rust
/// use actix_web::*;
/// use actix_web::middleware::identity::RequestIdentity;
///
/// fn index(req: HttpRequest) -> Result<String> {
/// // access request identity
/// if let Some(id) = req.identity() {
/// Ok(format!("Welcome! {}", id))
/// } else {
/// Ok("Welcome Anonymous!".to_owned())
/// }
/// }
///
/// fn login(mut req: HttpRequest) -> HttpResponse {
/// req.remember("User1".to_owned()); // <- remember identity
/// HttpResponse::Ok().finish()
/// }
///
/// fn logout(mut req: HttpRequest) -> HttpResponse {
/// req.forget(); // <- remove identity
/// HttpResponse::Ok().finish()
/// }
/// # fn main() {}
/// ```
pub trait RequestIdentity {
/// Return the claimed identity of the user associated request or
/// ``None`` if no identity can be found associated with the request.
fn identity(&self) -> Option<&str>;
/// Remember identity.
fn remember(&mut self, identity: String);
/// This method is used to 'forget' the current identity on subsequent
/// requests.
fn forget(&mut self);
}
impl<S> RequestIdentity for HttpRequest<S> {
fn identity(&self) -> Option<&str> {
if let Some(id) = self.extensions_ro().get::<IdentityBox>() {
return id.0.identity();
}
None
}
fn remember(&mut self, identity: String) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.remember(identity);
}
}
fn forget(&mut self) {
if let Some(id) = self.extensions_mut().get_mut::<IdentityBox>() {
return id.0.forget();
}
}
}
/// An identity
pub trait Identity: 'static {
fn identity(&self) -> Option<&str>;
fn remember(&mut self, key: String);
fn forget(&mut self);
/// Write session to storage backend.
fn write(&mut self, resp: HttpResponse) -> Result<Response>;
}
/// Identity policy definition.
pub trait IdentityPolicy<S>: Sized + 'static {
type Identity: Identity;
type Future: Future<Item = Self::Identity, Error = Error>;
/// Parse the session from request and load data from a service identity.
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::Future;
}
/// Request identity middleware
///
/// ```rust
/// # extern crate actix;
/// # extern crate actix_web;
/// use actix_web::App;
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
///
/// fn main() {
/// let app = App::new().middleware(
/// IdentityService::new( // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- create cookie session backend
/// .name("auth-cookie")
/// .secure(false))
/// );
/// }
/// ```
pub struct IdentityService<T> {
backend: T,
}
impl<T> IdentityService<T> {
/// Create new identity service with specified backend.
pub fn new(backend: T) -> Self {
IdentityService { backend }
}
}
struct IdentityBox(Box<Identity>);
#[doc(hidden)]
unsafe impl Send for IdentityBox {}
#[doc(hidden)]
unsafe impl Sync for IdentityBox {}
impl<S: 'static, T: IdentityPolicy<S>> Middleware<S> for IdentityService<T> {
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
let mut req = req.clone();
let fut = self.backend
.from_request(&mut req)
.then(move |res| match res {
Ok(id) => {
req.extensions().insert(IdentityBox(Box::new(id)));
FutOk(None)
}
Err(err) => FutErr(err),
});
Ok(Started::Future(Box::new(fut)))
}
fn response(
&self, req: &mut HttpRequest<S>, resp: HttpResponse
) -> Result<Response> {
if let Some(mut id) = req.extensions().remove::<IdentityBox>() {
id.0.write(resp)
} else {
Ok(Response::Done(resp))
}
}
}
#[doc(hidden)]
/// Identity that uses private cookies as identity storage.
pub struct CookieIdentity {
changed: bool,
identity: Option<String>,
inner: Rc<CookieIdentityInner>,
}
impl Identity for CookieIdentity {
fn identity(&self) -> Option<&str> {
self.identity.as_ref().map(|s| s.as_ref())
}
fn remember(&mut self, value: String) {
self.changed = true;
self.identity = Some(value);
}
fn forget(&mut self) {
self.changed = true;
self.identity = None;
}
fn write(&mut self, mut resp: HttpResponse) -> Result<Response> {
if self.changed {
let _ = self.inner.set_cookie(&mut resp, self.identity.take());
}
Ok(Response::Done(resp))
}
}
struct CookieIdentityInner {
key: Key,
name: String,
path: String,
domain: Option<String>,
secure: bool,
max_age: Option<Duration>,
}
impl CookieIdentityInner {
fn new(key: &[u8]) -> CookieIdentityInner {
CookieIdentityInner {
key: Key::from_master(key),
name: "actix-identity".to_owned(),
path: "/".to_owned(),
domain: None,
secure: true,
max_age: None,
}
}
fn set_cookie(&self, resp: &mut HttpResponse, id: Option<String>) -> Result<()> {
let some = id.is_some();
{
let id = id.unwrap_or_else(String::new);
let mut cookie = Cookie::new(self.name.clone(), id);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
}
if let Some(max_age) = self.max_age {
cookie.set_max_age(max_age);
}
let mut jar = CookieJar::new();
if some {
jar.private(&self.key).add(cookie);
} else {
jar.add_original(cookie.clone());
jar.private(&self.key).remove(cookie);
}
for cookie in jar.delta() {
let val = HeaderValue::from_str(&cookie.to_string())?;
resp.headers_mut().append(header::SET_COOKIE, val);
}
}
Ok(())
}
fn load<S>(&self, req: &mut HttpRequest<S>) -> Option<String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
jar.add_original(cookie.clone());
let cookie_opt = jar.private(&self.key).get(&self.name);
if let Some(cookie) = cookie_opt {
return Some(cookie.value().into());
}
}
}
}
None
}
}
/// Use cookies for request identity storage.
///
/// The constructors take a key as an argument.
/// This is the private key for cookie - when this value is changed,
/// all identities are lost. The constructors will panic if the key is less
/// than 32 bytes in length.
///
/// # Example
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::App;
/// use actix_web::middleware::identity::{IdentityService, CookieIdentityPolicy};
///
/// fn main() {
/// let app = App::new().middleware(
/// IdentityService::new( // <- create identity middleware
/// CookieIdentityPolicy::new(&[0; 32]) // <- construct cookie policy
/// .domain("www.rust-lang.org")
/// .name("actix_auth")
/// .path("/")
/// .secure(true)));
/// }
/// ```
pub struct CookieIdentityPolicy(Rc<CookieIdentityInner>);
impl CookieIdentityPolicy {
/// Construct new `CookieIdentityPolicy` instance.
///
/// Panics if key length is less than 32 bytes.
pub fn new(key: &[u8]) -> CookieIdentityPolicy {
CookieIdentityPolicy(Rc::new(CookieIdentityInner::new(key)))
}
/// Sets the `path` field in the session cookie being built.
pub fn path<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().path = value.into();
self
}
/// Sets the `name` field in the session cookie being built.
pub fn name<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().name = value.into();
self
}
/// Sets the `domain` field in the session cookie being built.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().domain = Some(value.into());
self
}
/// Sets the `secure` field in the session cookie being built.
///
/// If the `secure` field is set, a cookie will only be transmitted when the
/// connection is secure - i.e. `https`
pub fn secure(mut self, value: bool) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().secure = value;
self
}
/// Sets the `max-age` field in the session cookie being built.
pub fn max_age(mut self, value: Duration) -> CookieIdentityPolicy {
Rc::get_mut(&mut self.0).unwrap().max_age = Some(value);
self
}
}
impl<S> IdentityPolicy<S> for CookieIdentityPolicy {
type Identity = CookieIdentity;
type Future = FutureResult<CookieIdentity, Error>;
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::Future {
let identity = self.0.load(req);
FutOk(CookieIdentity {
identity,
changed: false,
inner: Rc::clone(&self.0),
})
}
}

View File

@@ -14,6 +14,7 @@ use httpresponse::HttpResponse;
use middleware::{Finished, Middleware, Started};
/// `Middleware` for logging request and response info to the terminal.
///
/// `Logger` middleware uses standard log crate to log information. You should
/// enable logger for `actix_web` package to see access log.
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)

View File

@@ -12,12 +12,17 @@ pub mod csrf;
mod defaultheaders;
mod errhandlers;
#[cfg(feature = "session")]
mod session;
pub mod identity;
#[cfg(feature = "session")]
pub mod session;
pub use self::defaultheaders::DefaultHeaders;
pub use self::errhandlers::ErrorHandlers;
pub use self::logger::Logger;
#[cfg(feature = "session")]
#[doc(hidden)]
#[deprecated(since = "0.5.4",
note = "please use `actix_web::middleware::session` instead")]
pub use self::session::{CookieSessionBackend, CookieSessionError, RequestSession,
Session, SessionBackend, SessionImpl, SessionStorage};

View File

@@ -1,3 +1,68 @@
//! User sessions.
//!
//! Actix provides a general solution for session management. The
//! [**SessionStorage**](struct.SessionStorage.html)
//! middleware can be used with different backend types to store session
//! data in different backends.
//!
//! By default, only cookie session backend is implemented. Other
//! backend implementations can be added.
//!
//! [**CookieSessionBackend**](struct.CookieSessionBackend.html)
//! uses cookies as session storage. `CookieSessionBackend` creates sessions
//! which are limited to storing fewer than 4000 bytes of data, as the payload
//! must fit into a single cookie. An internal server error is generated if a
//! session contains more than 4000 bytes.
//!
//! A cookie may have a security policy of *signed* or *private*. Each has
//! a respective `CookieSessionBackend` constructor.
//!
//! A *signed* cookie may be viewed but not modified by the client. A *private*
//! cookie may neither be viewed nor modified by the client.
//!
//! The constructors take a key as an argument. This is the private key
//! for cookie session - when this value is changed, all session data is lost.
//!
//! In general, you create a `SessionStorage` middleware and initialize it
//! with specific backend implementation, such as a `CookieSessionBackend`.
//! To access session data,
//! [*HttpRequest::session()*](trait.RequestSession.html#tymethod.session)
//! must be used. This method returns a
//! [*Session*](struct.Session.html) object, which allows us to get or set
//! session data.
//!
//! ```rust
//! # extern crate actix;
//! # extern crate actix_web;
//! use actix_web::{server, App, HttpRequest, Result};
//! use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend};
//!
//! fn index(mut req: HttpRequest) -> Result<&'static str> {
//! // access session data
//! if let Some(count) = req.session().get::<i32>("counter")? {
//! println!("SESSION value: {}", count);
//! req.session().set("counter", count+1)?;
//! } else {
//! req.session().set("counter", 1)?;
//! }
//!
//! Ok("Welcome!")
//! }
//!
//! fn main() {
//! let sys = actix::System::new("basic-example");
//! server::new(
//! || App::new().middleware(
//! SessionStorage::new( // <- create session middleware
//! CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
//! .secure(false)
//! )))
//! .bind("127.0.0.1:59880").unwrap()
//! .start();
//! # actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
//! let _ = sys.run();
//! }
//! ```
use std::collections::HashMap;
use std::marker::PhantomData;
use std::rc::Rc;

View File

@@ -328,7 +328,7 @@ impl<S: 'static, H> WaitingResponse<S, H> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Err(err) => Some(ProcessResponse::init(err.into())),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
}

View File

@@ -420,7 +420,7 @@ impl<S: 'static> WaitingResponse<S> {
match self.fut.poll() {
Ok(Async::NotReady) => None,
Ok(Async::Ready(response)) => Some(RunMiddlewares::init(info, response)),
Err(err) => Some(Response::init(err.into())),
Err(err) => Some(RunMiddlewares::init(info, err.into())),
}
}
}

View File

@@ -18,7 +18,7 @@ use flate2::Compression;
use flate2::read::GzDecoder;
use flate2::write::{DeflateDecoder, DeflateEncoder, GzEncoder};
use futures::stream::once;
use futures::{Future, Stream};
use futures::{future, Future, Stream};
use h2::client as h2client;
use modhttp::Request;
use rand::Rng;
@@ -915,3 +915,34 @@ fn test_resource_middlewares() {
assert_eq!(num2.load(Ordering::Relaxed), 1);
// assert_eq!(num3.load(Ordering::Relaxed), 1);
}
fn index_test_middleware_async_error(_: HttpRequest) -> FutureResponse<HttpResponse> {
future::result(Err(error::ErrorBadRequest("TEST"))).responder()
}
#[test]
fn test_middleware_async_error() {
let req = Arc::new(AtomicUsize::new(0));
let resp = Arc::new(AtomicUsize::new(0));
let fin = Arc::new(AtomicUsize::new(0));
let act_req = Arc::clone(&req);
let act_resp = Arc::clone(&resp);
let act_fin = Arc::clone(&fin);
let mut srv = test::TestServer::new(move |app| {
app.middleware(MiddlewareTest {
start: Arc::clone(&act_req),
response: Arc::clone(&act_resp),
finish: Arc::clone(&act_fin),
}).handler(index_test_middleware_async_error)
});
let request = srv.get().finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
assert_eq!(req.load(Ordering::Relaxed), 1);
assert_eq!(resp.load(Ordering::Relaxed), 1);
assert_eq!(fin.load(Ordering::Relaxed), 1);
}