1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-31 08:57:00 +02:00

Compare commits

..

9 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
8 changed files with 315 additions and 63 deletions

View File

@@ -1,5 +1,23 @@
# 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

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "0.6.11"
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"
@@ -50,7 +50,7 @@ flate2-rust = ["flate2/rust_backend"]
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

@@ -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> {

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

@@ -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(