From e5249a2d2978b6ca109d1b32ed3f01c45d0eeff3 Mon Sep 17 00:00:00 2001 From: Maximilian R Date: Sat, 27 Apr 2024 19:54:39 +0200 Subject: [PATCH 1/2] Add `prepend_path` option --- tower-http/src/services/fs/serve_dir/mod.rs | 20 +++++++++++++++++++ .../src/services/fs/serve_dir/open_file.rs | 9 +++++++-- tower-http/src/services/fs/serve_dir/tests.rs | 13 ++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/tower-http/src/services/fs/serve_dir/mod.rs b/tower-http/src/services/fs/serve_dir/mod.rs index 61b956d1..f4a703c3 100644 --- a/tower-http/src/services/fs/serve_dir/mod.rs +++ b/tower-http/src/services/fs/serve_dir/mod.rs @@ -52,6 +52,7 @@ const DEFAULT_CAPACITY: usize = 65536; #[derive(Clone, Debug)] pub struct ServeDir { base: PathBuf, + prepend_path: String, buf_chunk_size: usize, precompressed_variants: Option, // This is used to specialise implementation for @@ -72,6 +73,7 @@ impl ServeDir { Self { base, + prepend_path: "".to_string(), buf_chunk_size: DEFAULT_CAPACITY, precompressed_variants: None, variant: ServeVariant::Directory { @@ -88,6 +90,7 @@ impl ServeDir { { Self { base: path.as_ref().to_owned(), + prepend_path: "".to_string(), buf_chunk_size: DEFAULT_CAPACITY, precompressed_variants: None, variant: ServeVariant::SingleFile { mime }, @@ -115,6 +118,19 @@ impl ServeDir { } } + /// Sets a path to be prepended when performing a trailing slash redirect. + /// + /// This is useful when you want to serve the files at another location than "/", for example + /// when you are using multiple services and want this instance to handle `/static/`. + /// In that example, you should pass in "/static" so that a trailing slash redirect does not + /// redirect to `//` but instead to `/static//` + /// + /// The default is the empty string. + pub fn prepend_path(mut self, path: String) -> Self { + self.prepend_path = path; + self + } + /// Set a specific read buffer chunk size. /// /// The default capacity is 64kb. @@ -211,6 +227,7 @@ impl ServeDir { /// ``` pub fn fallback(self, new_fallback: F2) -> ServeDir { ServeDir { + prepend_path: "".to_string(), base: self.base, buf_chunk_size: self.buf_chunk_size, precompressed_variants: self.precompressed_variants, @@ -358,6 +375,8 @@ impl ServeDir { } }; + let prepend_path = self.prepend_path.clone(); + let buf_chunk_size = self.buf_chunk_size; let range_header = req .headers() @@ -375,6 +394,7 @@ impl ServeDir { let open_file_future = Box::pin(open_file::open_file( variant, + prepend_path, path_to_file, req, negotiated_encodings, diff --git a/tower-http/src/services/fs/serve_dir/open_file.rs b/tower-http/src/services/fs/serve_dir/open_file.rs index 01c1e2f9..aa6c829d 100644 --- a/tower-http/src/services/fs/serve_dir/open_file.rs +++ b/tower-http/src/services/fs/serve_dir/open_file.rs @@ -40,6 +40,7 @@ pub(super) enum FileRequestExtent { pub(super) async fn open_file( variant: ServeVariant, + prepend_path: String, mut path_to_file: PathBuf, req: Request>, negotiated_encodings: Vec<(Encoding, QValue)>, @@ -64,6 +65,7 @@ pub(super) async fn open_file( // returned which corresponds to a Some(output). Otherwise the path might be // modified and proceed to the open file/metadata future. if let Some(output) = maybe_redirect_or_append_path( + &prepend_path, &mut path_to_file, req.uri(), append_index_html_on_directories, @@ -251,6 +253,7 @@ async fn file_metadata_with_fallback( } async fn maybe_redirect_or_append_path( + prepend_path: &str, path_to_file: &mut PathBuf, uri: &Uri, append_index_html_on_directories: bool, @@ -267,8 +270,10 @@ async fn maybe_redirect_or_append_path( path_to_file.push("index.html"); None } else { - let location = - HeaderValue::from_str(&append_slash_on_path(uri.clone()).to_string()).unwrap(); + let location_string = format!("{prepend_path}{}", append_slash_on_path(uri.clone())); + + let location = HeaderValue::from_str(&location_string).unwrap(); + Some(OpenFileOutput::Redirect { location }) } } diff --git a/tower-http/src/services/fs/serve_dir/tests.rs b/tower-http/src/services/fs/serve_dir/tests.rs index d0d3952c..23e6504f 100644 --- a/tower-http/src/services/fs/serve_dir/tests.rs +++ b/tower-http/src/services/fs/serve_dir/tests.rs @@ -384,6 +384,19 @@ async fn redirect_to_trailing_slash_on_dir() { assert_eq!(location, "/src/"); } +#[tokio::test] +async fn redirect_to_trailing_slash_with_prepend_path() { + let svc = ServeDir::new(".").prepend_path("/foo".to_string()); + + let req = Request::builder().uri("/src").body(Body::empty()).unwrap(); + let res = svc.oneshot(req).await.unwrap(); + + assert_eq!(res.status(), StatusCode::TEMPORARY_REDIRECT); + + let location = &res.headers()[http::header::LOCATION]; + assert_eq!(location, "/foo/src/"); +} + #[tokio::test] async fn empty_directory_without_index() { let svc = ServeDir::new(".").append_index_html_on_directories(false); From 4ead891c7c633cc8b1c77c93e8c7adbc445a2948 Mon Sep 17 00:00:00 2001 From: "Maximilian R." Date: Thu, 2 May 2024 21:19:33 +0200 Subject: [PATCH 2/2] Rename `prepend_path` to `redirect_path_prefix` --- tower-http/src/services/fs/serve_dir/mod.rs | 14 +++++++------- tower-http/src/services/fs/serve_dir/tests.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tower-http/src/services/fs/serve_dir/mod.rs b/tower-http/src/services/fs/serve_dir/mod.rs index f4a703c3..3544621f 100644 --- a/tower-http/src/services/fs/serve_dir/mod.rs +++ b/tower-http/src/services/fs/serve_dir/mod.rs @@ -52,7 +52,7 @@ const DEFAULT_CAPACITY: usize = 65536; #[derive(Clone, Debug)] pub struct ServeDir { base: PathBuf, - prepend_path: String, + redirect_path_prefix: String, buf_chunk_size: usize, precompressed_variants: Option, // This is used to specialise implementation for @@ -73,7 +73,7 @@ impl ServeDir { Self { base, - prepend_path: "".to_string(), + redirect_path_prefix: "".to_string(), buf_chunk_size: DEFAULT_CAPACITY, precompressed_variants: None, variant: ServeVariant::Directory { @@ -90,7 +90,7 @@ impl ServeDir { { Self { base: path.as_ref().to_owned(), - prepend_path: "".to_string(), + redirect_path_prefix: "".to_string(), buf_chunk_size: DEFAULT_CAPACITY, precompressed_variants: None, variant: ServeVariant::SingleFile { mime }, @@ -126,8 +126,8 @@ impl ServeDir { /// redirect to `//` but instead to `/static//` /// /// The default is the empty string. - pub fn prepend_path(mut self, path: String) -> Self { - self.prepend_path = path; + pub fn redirect_path_prefix(mut self, path: String) -> Self { + self.redirect_path_prefix = path; self } @@ -227,7 +227,7 @@ impl ServeDir { /// ``` pub fn fallback(self, new_fallback: F2) -> ServeDir { ServeDir { - prepend_path: "".to_string(), + redirect_path_prefix: "".to_string(), base: self.base, buf_chunk_size: self.buf_chunk_size, precompressed_variants: self.precompressed_variants, @@ -375,7 +375,7 @@ impl ServeDir { } }; - let prepend_path = self.prepend_path.clone(); + let prepend_path = self.redirect_path_prefix.clone(); let buf_chunk_size = self.buf_chunk_size; let range_header = req diff --git a/tower-http/src/services/fs/serve_dir/tests.rs b/tower-http/src/services/fs/serve_dir/tests.rs index 23e6504f..bcac9eab 100644 --- a/tower-http/src/services/fs/serve_dir/tests.rs +++ b/tower-http/src/services/fs/serve_dir/tests.rs @@ -385,8 +385,8 @@ async fn redirect_to_trailing_slash_on_dir() { } #[tokio::test] -async fn redirect_to_trailing_slash_with_prepend_path() { - let svc = ServeDir::new(".").prepend_path("/foo".to_string()); +async fn redirect_to_trailing_slash_with_redirect_path_prefix() { + let svc = ServeDir::new(".").redirect_path_prefix("/foo".to_string()); let req = Request::builder().uri("/src").body(Body::empty()).unwrap(); let res = svc.oneshot(req).await.unwrap();