From e18227cc3d97369e90159fafcfc5b80b5d73b917 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Mon, 25 Mar 2019 13:43:02 -0700 Subject: [PATCH] add wrap_fn to App and Scope --- actix-files/src/lib.rs | 43 ++++++---- actix-files/src/named.rs | 16 ++-- src/app.rs | 174 ++++++++++++++++++++++++++++++++++++++- src/scope.rs | 75 ++++++++++++++++- 4 files changed, 282 insertions(+), 26 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 31ff4cdaf..8254c5fe5 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -20,8 +20,8 @@ use actix_web::dev::{ ServiceResponse, }; use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; +use actix_web::http::header::DispositionType; use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder}; -use actix_web::http::header::{DispositionType}; use futures::future::{ok, FutureResult}; mod error; @@ -300,7 +300,10 @@ impl Files { } /// Specifies mime override callback - pub fn mime_override(mut self, f: F) -> Self where F: Fn(&mime::Name) -> DispositionType + 'static { + pub fn mime_override(mut self, f: F) -> Self + where + F: Fn(&mime::Name) -> DispositionType + 'static, + { self.mime_override = Some(Rc::new(f)); self } @@ -331,7 +334,6 @@ impl Files { self.file_flags.set(named::Flags::LAST_MD, value); self } - } impl

HttpServiceFactory

for Files

@@ -395,7 +397,8 @@ impl

Service for Files

{ match NamedFile::open(path) { Ok(mut named_file) => { if let Some(ref mime_override) = self.mime_override { - let new_disposition = mime_override(&named_file.content_type.type_()); + let new_disposition = + mime_override(&named_file.content_type.type_()); named_file.content_disposition.disposition = new_disposition; } @@ -404,7 +407,7 @@ impl

Service for Files

{ Ok(item) => ok(ServiceResponse::new(req.clone(), item)), Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } - }, + } Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } } else if self.show_index { @@ -424,7 +427,8 @@ impl

Service for Files

{ match NamedFile::open(path) { Ok(mut named_file) => { if let Some(ref mime_override) = self.mime_override { - let new_disposition = mime_override(&named_file.content_type.type_()); + let new_disposition = + mime_override(&named_file.content_type.type_()); named_file.content_disposition.disposition = new_disposition; } @@ -433,7 +437,7 @@ impl

Service for Files

{ Ok(item) => ok(ServiceResponse::new(req.clone(), item)), Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } - }, + } Err(e) => ok(ServiceResponse::from_err(e, req.clone())), } } @@ -692,15 +696,24 @@ mod tests { } let mut srv = test::init_service( - App::new().service(Files::new("/", ".").mime_override(all_attachment).index_file("Cargo.toml")), + App::new().service( + Files::new("/", ".") + .mime_override(all_attachment) + .index_file("Cargo.toml"), + ), ); let request = TestRequest::get().uri("/").to_request(); let response = test::call_success(&mut srv, request); assert_eq!(response.status(), StatusCode::OK); - let content_disposition = response.headers().get(header::CONTENT_DISPOSITION).expect("To have CONTENT_DISPOSITION"); - let content_disposition = content_disposition.to_str().expect("Convert CONTENT_DISPOSITION to str"); + let content_disposition = response + .headers() + .get(header::CONTENT_DISPOSITION) + .expect("To have CONTENT_DISPOSITION"); + let content_disposition = content_disposition + .to_str() + .expect("Convert CONTENT_DISPOSITION to str"); assert_eq!(content_disposition, "attachment; filename=\"Cargo.toml\""); } @@ -864,16 +877,14 @@ mod tests { #[test] fn test_named_file_not_allowed() { - let file = - NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open("Cargo.toml").unwrap(); let req = TestRequest::default() .method(Method::POST) .to_http_request(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let file = - NamedFile::open("Cargo.toml").unwrap(); + let file = NamedFile::open("Cargo.toml").unwrap(); let req = TestRequest::default().method(Method::PUT).to_http_request(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); @@ -896,9 +907,7 @@ mod tests { #[test] fn test_named_file_allowed_method() { - let req = TestRequest::default() - .method(Method::GET) - .to_http_request(); + let req = TestRequest::default().method(Method::GET).to_http_request(); let file = NamedFile::open("Cargo.toml").unwrap(); let resp = file.respond_to(&req).unwrap(); assert_eq!(resp.status(), StatusCode::OK); diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index d2bf25691..7bc37054a 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -11,7 +11,9 @@ use bitflags::bitflags; use mime; use mime_guess::guess_mime_type; -use actix_web::http::header::{self, DispositionType, ContentDisposition, DispositionParam}; +use actix_web::http::header::{ + self, ContentDisposition, DispositionParam, DispositionType, +}; use actix_web::http::{ContentEncoding, Method, StatusCode}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; @@ -311,13 +313,17 @@ impl Responder for NamedFile { &Method::HEAD | &Method::GET => (), _ => { return Ok(HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") - .header(header::ALLOW, "GET, HEAD") - .body("This resource only supports GET and HEAD.")); + .header(header::CONTENT_TYPE, "text/plain") + .header(header::ALLOW, "GET, HEAD") + .body("This resource only supports GET and HEAD.")); } } - let etag = if self.flags.contains(Flags::ETAG) { self.etag() } else { None }; + let etag = if self.flags.contains(Flags::ETAG) { + self.etag() + } else { + None + }; let last_modified = if self.flags.contains(Flags::LAST_MD) { self.last_modified() } else { diff --git a/src/app.rs b/src/app.rs index 94a47afe6..f46f5252f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -147,6 +147,51 @@ where } } + /// Register a middleware function. + /// + /// ```rust + /// use actix_service::Service; + /// # use futures::Future; + /// use actix_web::{web, App}; + /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new() + /// .wrap_fn(|req, srv| + /// srv.call(req).map(|mut res| { + /// res.headers_mut().insert( + /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), + /// ); + /// res + /// })) + /// .route("/index.html", web::get().to(index)); + /// } + /// ``` + pub fn wrap_fn( + self, + mw: F, + ) -> AppRouter< + T, + P, + B, + impl NewService< + Request = ServiceRequest

, + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + > + where + F: FnMut(ServiceRequest

, &mut AppRouting

) -> R + Clone, + R: IntoFuture, Error = Error>, + { + self.wrap(mw) + } + /// Register a request modifier. It can modify any request parameters /// including payload stream type. pub fn chain( @@ -361,6 +406,29 @@ where } } + /// Register a middleware function. + pub fn wrap_fn( + self, + mw: F, + ) -> AppRouter< + C, + P, + B1, + impl NewService< + Request = ServiceRequest

, + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + > + where + B1: MessageBody, + F: FnMut(ServiceRequest

, &mut T::Service) -> R + Clone, + R: IntoFuture, Error = Error>, + { + self.wrap(mw) + } + /// Default resource to be used if no matching resource could be found. pub fn default_resource(mut self, f: F) -> Self where @@ -447,11 +515,13 @@ where #[cfg(test)] mod tests { use actix_service::Service; + use futures::{Future, IntoFuture}; use super::*; - use crate::http::{Method, StatusCode}; - use crate::test::{block_on, init_service, TestRequest}; - use crate::{web, HttpResponse}; + use crate::http::{header, HeaderValue, Method, StatusCode}; + use crate::service::{ServiceRequest, ServiceResponse}; + use crate::test::{block_on, call_success, init_service, TestRequest}; + use crate::{web, Error, HttpResponse}; #[test] fn test_default_resource() { @@ -510,4 +580,102 @@ mod tests { let resp = block_on(srv.call(req)).unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + + fn md( + req: ServiceRequest

, + srv: &mut S, + ) -> impl IntoFuture, Error = Error> + where + S: Service< + Request = ServiceRequest

, + Response = ServiceResponse, + Error = Error, + >, + { + srv.call(req).map(|mut res| { + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); + res + }) + } + + #[test] + fn test_wrap() { + let mut srv = init_service( + App::new() + .wrap(md) + .route("/test", web::get().to(|| HttpResponse::Ok())), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[test] + fn test_router_wrap() { + let mut srv = init_service( + App::new() + .route("/test", web::get().to(|| HttpResponse::Ok())) + .wrap(md), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[test] + fn test_wrap_fn() { + let mut srv = init_service( + App::new() + .wrap_fn(|req, srv| { + srv.call(req).map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + }) + .service(web::resource("/test").to(|| HttpResponse::Ok())), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } + + #[test] + fn test_router_wrap_fn() { + let mut srv = init_service( + App::new() + .route("/test", web::get().to(|| HttpResponse::Ok())) + .wrap_fn(|req, srv| { + srv.call(req).map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + }), + ); + let req = TestRequest::with_uri("/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } } diff --git a/src/scope.rs b/src/scope.rs index 1be594f1b..8c72824f4 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -8,7 +8,7 @@ use actix_service::{ ApplyTransform, IntoNewService, IntoTransform, NewService, Service, Transform, }; use futures::future::{ok, Either, Future, FutureResult}; -use futures::{Async, Poll}; +use futures::{Async, IntoFuture, Poll}; use crate::dev::{HttpServiceFactory, ServiceConfig}; use crate::error::Error; @@ -237,6 +237,53 @@ where factory_ref: self.factory_ref, } } + + /// Register a scope level middleware function. + /// + /// This function accepts instance of `ServiceRequest` type and + /// mutable reference to the next middleware in chain. + /// + /// ```rust + /// use actix_service::Service; + /// # use futures::Future; + /// use actix_web::{web, App}; + /// use actix_web::http::{header::CONTENT_TYPE, HeaderValue}; + /// + /// fn index() -> &'static str { + /// "Welcome!" + /// } + /// + /// fn main() { + /// let app = App::new().service( + /// web::scope("/app") + /// .wrap_fn(|req, srv| + /// srv.call(req).map(|mut res| { + /// res.headers_mut().insert( + /// CONTENT_TYPE, HeaderValue::from_static("text/plain"), + /// ); + /// res + /// })) + /// .route("/index.html", web::get().to(index))); + /// } + /// ``` + pub fn wrap_fn( + self, + mw: F, + ) -> Scope< + P, + impl NewService< + Request = ServiceRequest

, + Response = ServiceResponse, + Error = Error, + InitError = (), + >, + > + where + F: FnMut(ServiceRequest

, &mut T::Service) -> R + Clone, + R: IntoFuture, + { + self.wrap(mw) + } } impl HttpServiceFactory

for Scope @@ -862,4 +909,30 @@ mod tests { HeaderValue::from_static("0001") ); } + + #[test] + fn test_middleware_fn() { + let mut srv = init_service( + App::new().service( + web::scope("app") + .wrap_fn(|req, srv| { + srv.call(req).map(|mut res| { + res.headers_mut().insert( + header::CONTENT_TYPE, + HeaderValue::from_static("0001"), + ); + res + }) + }) + .route("/test", web::get().to(|| HttpResponse::Ok())), + ), + ); + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_success(&mut srv, req); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(header::CONTENT_TYPE).unwrap(), + HeaderValue::from_static("0001") + ); + } }