diff --git a/CHANGES.md b/CHANGES.md index bf33040ec..2fee070c4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,11 +22,23 @@ * Added header `User-Agent: Actix-web/` to default headers when building a request + ### Removed * Remove `Route::with2()` and `Route::with3()` use tuple of extractors instead. +## [0.6.12] - 2018-06-07 + +### Added + +* Add `Host` filter #287 + +* Allow to filter applications + +* Improved failure interoperability with downcasting #285 + + ## [0.6.11] - 2018-06-05 * Support chunked encoding for UrlEncoded body #262 diff --git a/README.md b/README.md index 427838c1c..158df61bc 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ You may consider checking out ## Benchmarks -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r15&hw=ph&test=plaintext) +* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) * Some basic benchmarks could be found in this [repository](https://github.com/fafhrd91/benchmarks). diff --git a/src/application.rs b/src/application.rs index 90c70bd18..e16f85cdc 100644 --- a/src/application.rs +++ b/src/application.rs @@ -22,6 +22,7 @@ pub struct HttpApplication { prefix_len: usize, router: Router, inner: Rc>>, + filters: Option>>>, middlewares: Rc>>>>, } @@ -143,11 +144,21 @@ impl HttpHandler for HttpApplication { || 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 { external: HashMap, encoding: ContentEncoding, middlewares: Vec>>, + filters: Vec>>, } /// 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, }), } @@ -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 + 'static>(mut self, p: T) -> App { + { + 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. @@ -608,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), @@ -616,6 +655,7 @@ where prefix, prefix_len, inner, + filters, } } @@ -700,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() { @@ -899,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); + } } diff --git a/src/error.rs b/src/error.rs index d08093fb2..4b2dae72e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -36,7 +36,7 @@ pub type Result = result::Result; /// General purpose actix web error. /// -/// An actix web error is used to carry errors from `failure` or `std::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()`. /// @@ -51,7 +51,9 @@ pub struct Error { impl Error { /// Deprecated way to reference the underlying response error. - #[deprecated(since = "0.6.0", note = "please use `Error::as_response_error()` instead")] + #[deprecated( + since = "0.6.0", note = "please use `Error::as_response_error()` instead" + )] pub fn cause(&self) -> &ResponseError { self.cause.as_ref() } @@ -97,14 +99,14 @@ impl Error { // // 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> = Fail::downcast_ref(self.cause.as_fail()); + let compat: Option<&failure::Compat> = + 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) - }; + let compat: &CompatWrappedError = + unsafe { &*(compat as *const _ as *const CompatWrappedError) }; compat.error.downcast_ref() } else { None @@ -126,8 +128,12 @@ pub trait InternalResponseErrorAsFail { #[doc(hidden)] impl InternalResponseErrorAsFail for T { - fn as_fail(&self) -> &Fail { self } - fn as_mut_fail(&mut self) -> &mut Fail { self } + fn as_fail(&self) -> &Fail { + self + } + fn as_mut_fail(&mut self) -> &mut Fail { + self + } } /// Error that can be converted to `HttpResponse` diff --git a/src/httprequest.rs b/src/httprequest.rs index d852bc743..a54a99581 100644 --- a/src/httprequest.rs +++ b/src/httprequest.rs @@ -141,6 +141,12 @@ impl HttpRequest<()> { pub fn with_state(self, state: Rc, router: Router) -> HttpRequest { HttpRequest(self.0, Some(state), Some(router)) } + + pub(crate) fn clone_with_state( + &self, state: Rc, router: Router, + ) -> HttpRequest { + HttpRequest(self.0.clone(), Some(state), Some(router)) + } } impl HttpMessage for HttpRequest { diff --git a/src/pred.rs b/src/pred.rs index 206a7941c..020052e25 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -196,6 +196,45 @@ impl Predicate for HeaderPredicate { } } +/// 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>(host: H) -> HostPredicate { + HostPredicate(host.as_ref().to_string(), None, PhantomData) +} + +#[doc(hidden)] +pub struct HostPredicate(String, Option, PhantomData); + +impl HostPredicate { + /// Set reuest scheme to match + pub fn scheme>(&mut self, scheme: H) { + self.1 = Some(scheme.as_ref().to_string()) + } +} + +impl Predicate for HostPredicate { + fn check(&self, req: &mut HttpRequest) -> 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::*; @@ -228,6 +267,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(