diff --git a/src/application.rs b/src/application.rs index 94fb70fb2..254238b53 100644 --- a/src/application.rs +++ b/src/application.rs @@ -22,7 +22,8 @@ pub struct HttpApplication { prefix_len: usize, router: Router, inner: Rc>>, - middlewares: Rc>>>, + filters: Option>>>, + middlewares: Rc>>>>, } pub(crate) struct Inner { @@ -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, }), } @@ -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::Get()) + /// .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. @@ -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(mut self, path: &str, method: Method, f: F) -> App @@ -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(mut self, path: &str, f: F) -> App @@ -469,9 +504,9 @@ where /// use actix_web::{App, HttpRequest, HttpResponse, Result}; /// /// fn index(mut req: HttpRequest) -> Result { - /// 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>(mut self, path: &str, handler: H) -> App { @@ -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 { @@ -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); + } } 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 {