From f44a0bc159680f5c65c14bbf7947768272818276 Mon Sep 17 00:00:00 2001 From: tglman Date: Thu, 22 Apr 2021 18:13:13 +0100 Subject: [PATCH 1/3] add support of filtering guards in Files of actix-files (#2046) Co-authored-by: Rob Ede --- actix-files/CHANGES.md | 3 ++ actix-files/src/files.rs | 49 ++++++++++++++++--- .../tests/fixtures/guards/first/index.txt | 1 + .../tests/fixtures/guards/second/index.txt | 1 + actix-files/tests/guard.rs | 36 ++++++++++++++ src/guard.rs | 8 +++ 6 files changed, 91 insertions(+), 7 deletions(-) create mode 100644 actix-files/tests/fixtures/guards/first/index.txt create mode 100644 actix-files/tests/fixtures/guards/second/index.txt create mode 100644 actix-files/tests/guard.rs diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 6c28e42d0..0586a2fd3 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -11,6 +11,9 @@ ## 0.6.0-beta.4 - 2021-04-02 * No notable changes. +* Add support for `.guard` in `Files` to selectively filter `Files` services. [#2046] + +[#2046]: https://github.com/actix/actix-web/pull/2046 ## 0.6.0-beta.3 - 2021-03-09 * No notable changes. diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 9b24c4b21..00e4dd5d1 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -37,7 +37,8 @@ pub struct Files { renderer: Rc, mime_override: Option>, file_flags: named::Flags, - guards: Option>, + use_guards: Option>, + guards: Vec>>, hidden_files: bool, } @@ -59,12 +60,12 @@ impl Clone for Files { file_flags: self.file_flags, path: self.path.clone(), mime_override: self.mime_override.clone(), + use_guards: self.use_guards.clone(), guards: self.guards.clone(), hidden_files: self.hidden_files, } } } - impl Files { /// Create new `Files` instance for a specified base directory. /// @@ -104,7 +105,8 @@ impl Files { renderer: Rc::new(directory_listing), mime_override: None, file_flags: named::Flags::default(), - guards: None, + use_guards: None, + guards: Vec::new(), hidden_files: false, } } @@ -185,7 +187,28 @@ impl Files { /// Default behaviour allows GET and HEAD. #[inline] pub fn use_guards(mut self, guards: G) -> Self { - self.guards = Some(Rc::new(guards)); + self.use_guards = Some(Rc::new(guards)); + self + } + + /// Add match guard to use on directory listings and files. + /// + /// ```rust + /// use actix_web::{guard, App}; + /// use actix_files::Files; + /// + /// + /// fn main() { + /// let app = App::new() + /// .service( + /// Files::new("/","/my/site/files") + /// .guard(guard::Header("Host", "example.com")) + /// ); + /// } + /// ``` + #[inline] + pub fn guard(mut self, guard: G) -> Self { + self.guards.push(Box::new(Rc::new(guard))); self } @@ -238,7 +261,19 @@ impl Files { } impl HttpServiceFactory for Files { - fn register(self, config: &mut AppService) { + fn register(mut self, config: &mut AppService) { + let guards = if self.guards.is_empty() { + None + } else { + let guards = std::mem::take(&mut self.guards); + Some( + guards + .into_iter() + .map(|guard| -> Box { guard }) + .collect::>(), + ) + }; + if self.default.borrow().is_none() { *self.default.borrow_mut() = Some(config.default_service()); } @@ -249,7 +284,7 @@ impl HttpServiceFactory for Files { ResourceDef::prefix(&self.path) }; - config.register_service(rdef, None, self, None) + config.register_service(rdef, guards, self, None) } } @@ -271,7 +306,7 @@ impl ServiceFactory for Files { renderer: self.renderer.clone(), mime_override: self.mime_override.clone(), file_flags: self.file_flags, - guards: self.guards.clone(), + guards: self.use_guards.clone(), hidden_files: self.hidden_files, }; diff --git a/actix-files/tests/fixtures/guards/first/index.txt b/actix-files/tests/fixtures/guards/first/index.txt new file mode 100644 index 000000000..fe4f02ad0 --- /dev/null +++ b/actix-files/tests/fixtures/guards/first/index.txt @@ -0,0 +1 @@ +first \ No newline at end of file diff --git a/actix-files/tests/fixtures/guards/second/index.txt b/actix-files/tests/fixtures/guards/second/index.txt new file mode 100644 index 000000000..2147e4188 --- /dev/null +++ b/actix-files/tests/fixtures/guards/second/index.txt @@ -0,0 +1 @@ +second \ No newline at end of file diff --git a/actix-files/tests/guard.rs b/actix-files/tests/guard.rs new file mode 100644 index 000000000..8b1785e7f --- /dev/null +++ b/actix-files/tests/guard.rs @@ -0,0 +1,36 @@ +use actix_files::Files; +use actix_web::{ + guard::Host, + http::StatusCode, + test::{self, TestRequest}, + App, +}; +use bytes::Bytes; + +#[actix_rt::test] +async fn test_guard_filter() { + let srv = test::init_service( + App::new() + .service(Files::new("/", "./tests/fixtures/guards/first").guard(Host("first.com"))) + .service( + Files::new("/", "./tests/fixtures/guards/second").guard(Host("second.com")), + ), + ) + .await; + + let req = TestRequest::with_uri("/index.txt") + .append_header(("Host", "first.com")) + .to_request(); + let res = test::call_service(&srv, req).await; + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(test::read_body(res).await, Bytes::from("first")); + + let req = TestRequest::with_uri("/index.txt") + .append_header(("Host", "second.com")) + .to_request(); + let res = test::call_service(&srv, req).await; + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(test::read_body(res).await, Bytes::from("second")); +} diff --git a/src/guard.rs b/src/guard.rs index 3a1f5bb14..c71d64a29 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -26,6 +26,8 @@ //! ``` #![allow(non_snake_case)] use std::convert::TryFrom; +use std::ops::Deref; +use std::rc::Rc; use actix_http::http::{self, header, uri::Uri}; use actix_http::RequestHead; @@ -40,6 +42,12 @@ pub trait Guard { fn check(&self, request: &RequestHead) -> bool; } +impl Guard for Rc { + fn check(&self, request: &RequestHead) -> bool { + self.deref().check(request) + } +} + /// Create guard object for supplied function. /// /// ``` From 75867bd073e4bc16b416bc10e13a185c3cfcf14a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 22 Apr 2021 18:31:21 +0100 Subject: [PATCH 2/3] clean up files service docs and rename method follow on from #2046 --- actix-files/src/files.rs | 61 ++++++++++++++++++++++------------------ actix-files/src/lib.rs | 2 +- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 00e4dd5d1..aeb75e116 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -158,7 +158,6 @@ impl Files { /// Specifies whether to use ETag or not. /// /// Default is true. - #[inline] pub fn use_etag(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::ETAG, value); self @@ -167,7 +166,6 @@ impl Files { /// Specifies whether to use Last-Modified or not. /// /// Default is true. - #[inline] pub fn use_last_modified(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::LAST_MD, value); self @@ -176,46 +174,55 @@ impl Files { /// Specifies whether text responses should signal a UTF-8 encoding. /// /// Default is false (but will default to true in a future version). - #[inline] pub fn prefer_utf8(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::PREFER_UTF8, value); self } - /// Specifies custom guards to use for directory listings and files. + /// Adds a routing guard. /// - /// Default behaviour allows GET and HEAD. - #[inline] - pub fn use_guards(mut self, guards: G) -> Self { - self.use_guards = Some(Rc::new(guards)); - self - } - - /// Add match guard to use on directory listings and files. + /// Use this to allow multiple chained file services that respond to strictly different + /// properties of a request. Due to the way routing works, if a guard check returns true and the + /// request starts being handled by the file service, it will not be able to back-out and try + /// the next service, you will simply get a 404 (or 405) error response. /// - /// ```rust - /// use actix_web::{guard, App}; + /// To allow `POST` requests to retrieve files, see [`Files::use_guards`]. + /// + /// # Examples + /// ``` + /// use actix_web::{guard::Header, App}; /// use actix_files::Files; /// - /// - /// fn main() { - /// let app = App::new() - /// .service( - /// Files::new("/","/my/site/files") - /// .guard(guard::Header("Host", "example.com")) - /// ); - /// } + /// App::new().service( + /// Files::new("/","/my/site/files") + /// .guard(Header("Host", "example.com")) + /// ); /// ``` - #[inline] pub fn guard(mut self, guard: G) -> Self { self.guards.push(Box::new(Rc::new(guard))); self } + /// Specifies guard to check before fetching directory listings or files. + /// + /// Note that this guard has no effect on routing; it's main use is to guard on the request's + /// method just before serving the file, only allowing `GET` and `HEAD` requests by default. + /// See [`Files::guard`] for routing guards. + pub fn method_guard(mut self, guard: G) -> Self { + self.use_guards = Some(Rc::new(guard)); + self + } + + #[doc(hidden)] + #[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")] + /// See [`Files::method_guard`]. + pub fn use_guards(self, guard: G) -> Self { + self.method_guard(guard) + } + /// Disable `Content-Disposition` header. /// /// By default Content-Disposition` header is enabled. - #[inline] pub fn disable_content_disposition(mut self) -> Self { self.file_flags.remove(named::Flags::CONTENT_DISPOSITION); self @@ -223,8 +230,9 @@ impl Files { /// Sets default handler which is used when no matched file could be found. /// - /// For example, you could set a fall back static file handler: - /// ```rust + /// # Examples + /// Setting a fallback static file handler: + /// ``` /// use actix_files::{Files, NamedFile}; /// /// # fn run() -> Result<(), actix_web::Error> { @@ -253,7 +261,6 @@ impl Files { } /// Enables serving hidden files and directories, allowing a leading dots in url fragments. - #[inline] pub fn use_hidden_files(mut self) -> Self { self.hidden_files = true; self diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 34b02581e..0384ff2b0 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -532,7 +532,7 @@ mod tests { #[actix_rt::test] async fn test_files_guards() { let srv = test::init_service( - App::new().service(Files::new("/", ".").use_guards(guard::Post())), + App::new().service(Files::new("/", ".").method_guard(guard::Post())), ) .await; From 6a29a50f25fd103c61e93d97dbee5745640bb911 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 22 Apr 2021 18:37:45 +0100 Subject: [PATCH 3/3] files doc wording --- actix-files/src/files.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index aeb75e116..8e28cb45e 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -81,10 +81,9 @@ impl Files { /// If the mount path is set as the root path `/`, services registered after this one will /// be inaccessible. Register more specific handlers and services first. /// - /// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a - /// max number of threads equal to `512 * HttpServer::worker`. Real time thread count are - /// adjusted with work load. More threads would spawn when need and threads goes idle for a - /// period of time would be de-spawned. + /// `Files` utilizes the existing Tokio thread-pool for blocking filesystem operations. + /// The number of running threads is adjusted over time as needed, up to a maximum of 512 times + /// the number of server [workers](HttpServer::workers), by default. pub fn new>(mount_path: &str, serve_from: T) -> Files { let orig_dir = serve_from.into(); let dir = match orig_dir.canonicalize() {