From 562b109e8b489a359d176d0fd296c67ed2fa88ee Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Wed, 2 Jul 2025 19:18:46 -0700 Subject: [PATCH 01/23] AvailableEncodings make properties private --- LICENSE | 2 +- configuration.md | 7 ++---- response/src/available_encodings.rs | 33 ++++++++++++++--------------- response/src/response_paths.rs | 2 +- 4 files changed, 20 insertions(+), 24 deletions(-) diff --git a/LICENSE b/LICENSE index f859efb..aef36cf 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2023, Wolfpup Software +Copyright (c) 2023, W-lfpup All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/configuration.md b/configuration.md index 8b42be5..806c375 100644 --- a/configuration.md +++ b/configuration.md @@ -9,14 +9,11 @@ A valid [JSON configuration file](./file_server.json) adheres to the following s "directory": "./demo", "host_and_port": "127.0.0.1:4000", "content_encodings": ["gzip", "deflate", "br", "zstd"], - "filepath_404s": [ - ["./demo/404.gz", "gzip"], - ["./demo/404.html", null] - ] + "filepath_404": "./demo/404.html" } ``` -The `content_encodings` and `filepath_404s` properties are optional. +The `content_encodings` and `filepath_404` properties are optional. ### Run diff --git a/response/src/available_encodings.rs b/response/src/available_encodings.rs index dd8639f..dfdd5c5 100644 --- a/response/src/available_encodings.rs +++ b/response/src/available_encodings.rs @@ -1,13 +1,13 @@ #[derive(Clone, Debug)] pub struct AvailableEncodings { - pub gzip: bool, - pub deflate: bool, - pub br: bool, - pub zstd: bool, + gzip: bool, + deflate: bool, + br: bool, + zstd: bool, } impl AvailableEncodings { - pub fn new(potential_encodings: &Option>) -> AvailableEncodings { + pub fn from(potential_encodings: &Option>) -> AvailableEncodings { let mut av_enc = AvailableEncodings { gzip: false, deflate: false, @@ -15,21 +15,20 @@ impl AvailableEncodings { zstd: false, }; - if let Some(pe) = potential_encodings { - av_enc.update(pe); - } - + av_enc.update(potential_encodings); av_enc } - pub fn update(&mut self, potential_encodings: &Vec) { - for encoding in potential_encodings { - match encoding.as_str() { - "gzip" => self.gzip = true, - "deflate" => self.deflate = true, - "br" => self.br = true, - "zstd" => self.zstd = true, - _ => {} + pub fn update(&mut self, potential_encodings: &Option>) { + if let Some(encodings) = potential_encodings { + for encoding in encodings { + match encoding.as_str() { + "gzip" => self.gzip = true, + "deflate" => self.deflate = true, + "br" => self.br = true, + "zstd" => self.zstd = true, + _ => {} + } } } } diff --git a/response/src/response_paths.rs b/response/src/response_paths.rs index 5723bca..a60172c 100644 --- a/response/src/response_paths.rs +++ b/response/src/response_paths.rs @@ -70,7 +70,7 @@ pub fn get_encodings( _ => return None, }; - let available_encodings = AvailableEncodings::new(content_encodings); + let available_encodings = AvailableEncodings::from(content_encodings); let mut encodings = Vec::new(); for encoding in encoding_str.split(",") { From 3416fb27e82b12c2887981430f14e9ee0b86e8a6 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 11:52:06 -0700 Subject: [PATCH 02/23] available_encodings --- response/src/available_encodings.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/response/src/available_encodings.rs b/response/src/available_encodings.rs index dfdd5c5..f662643 100644 --- a/response/src/available_encodings.rs +++ b/response/src/available_encodings.rs @@ -15,22 +15,19 @@ impl AvailableEncodings { zstd: false, }; - av_enc.update(potential_encodings); - av_enc - } - - pub fn update(&mut self, potential_encodings: &Option>) { if let Some(encodings) = potential_encodings { for encoding in encodings { match encoding.as_str() { - "gzip" => self.gzip = true, - "deflate" => self.deflate = true, - "br" => self.br = true, - "zstd" => self.zstd = true, + "gzip" => av_enc.gzip = true, + "deflate" => av_enc.deflate = true, + "br" => av_enc.br = true, + "zstd" => av_enc.zstd = true, _ => {} } } } + + av_enc } pub fn encoding_is_available(&self, encoding: &str) -> bool { From d7d6083fd789d73662fc82214bcd7392fb9d9cc1 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 13:18:28 -0700 Subject: [PATCH 03/23] use available encodings once and clone --- file_server/src/main.rs | 6 +++--- file_server/src/service.rs | 12 +++++++----- response/src/get_response.rs | 7 ++++--- response/src/head_response.rs | 18 +++++++++++------- response/src/lib.rs | 1 + response/src/range_response.rs | 9 +++++---- response/src/response_paths.rs | 4 +--- response/src/responses.rs | 7 ++++--- 8 files changed, 36 insertions(+), 28 deletions(-) diff --git a/file_server/src/main.rs b/file_server/src/main.rs index 175d83c..c68f7cd 100644 --- a/file_server/src/main.rs +++ b/file_server/src/main.rs @@ -45,9 +45,9 @@ async fn main() -> Result<(), String> { async fn get_config() -> Result { match env::args().nth(1) { - Some(conf_path) => { - let conf_path_buf = PathBuf::from(conf_path); - return Config::try_from(&conf_path_buf).await; + Some(conf_path_arg) => { + let conf_pathbuf = PathBuf::from(conf_path_arg); + return Config::try_from(&conf_pathbuf).await; } _ => Config::new(), } diff --git a/file_server/src/service.rs b/file_server/src/service.rs index 929e902..cb625c3 100644 --- a/file_server/src/service.rs +++ b/file_server/src/service.rs @@ -10,12 +10,12 @@ use std::pin::Pin; It should work with hyper responses across different libraries and dependencies. */ -use response::{build_response, BoxedResponse}; +use response::{build_response, AvailableEncodings, BoxedResponse}; #[derive(Clone, Debug)] pub struct Svc { directory: PathBuf, - content_encodings: Option>, + available_encodings: AvailableEncodings, fallback_404: Option, } @@ -25,9 +25,11 @@ impl Svc { content_encodings: Option>, fallback_404: Option, ) -> Svc { + let available_encodings = AvailableEncodings::from(&content_encodings); + Svc { directory: directory, - content_encodings: content_encodings, + available_encodings: available_encodings, fallback_404: fallback_404, } } @@ -40,11 +42,11 @@ impl Service> for Svc { fn call(&self, req: Request) -> Self::Future { let directory = self.directory.clone(); - let content_encodings = self.content_encodings.clone(); + let available_encodings = self.available_encodings.clone(); let fallback_404 = self.fallback_404.clone(); Box::pin( - async move { build_response(req, directory, content_encodings, fallback_404).await }, + async move { build_response(req, directory, available_encodings, fallback_404).await }, ) } } diff --git a/response/src/get_response.rs b/response/src/get_response.rs index 3a950b8..0b5d532 100644 --- a/response/src/get_response.rs +++ b/response/src/get_response.rs @@ -10,6 +10,7 @@ use tokio::fs; use tokio::fs::File; use tokio_util::io::ReaderStream; +use crate::available_encodings::AvailableEncodings; use crate::content_type::get_content_type; use crate::last_resort_response::build_last_resort_response; use crate::range_response::build_range_response; @@ -21,16 +22,16 @@ pub const NOT_FOUND_404: &str = "404 not found"; pub async fn build_get_response( req: Request, directory: PathBuf, - content_encodings: Option>, + available_encodings: AvailableEncodings, fallback_404: Option, ) -> Result { // check for range request - if let Some(res) = build_range_response(&req, &directory, &content_encodings).await { + if let Some(res) = build_range_response(&req, &directory, &available_encodings).await { return res; } // request file - let encodings = get_encodings(&req, &content_encodings); + let encodings = get_encodings(&req, &available_encodings); // serve file if let Some(res) = build_file_response(&req, &directory, &encodings).await { diff --git a/response/src/head_response.rs b/response/src/head_response.rs index c889745..26b96ce 100644 --- a/response/src/head_response.rs +++ b/response/src/head_response.rs @@ -6,6 +6,7 @@ use hyper::StatusCode; use std::path::PathBuf; use tokio::fs; +use crate::available_encodings::AvailableEncodings; use crate::content_type::get_content_type; use crate::last_resort_response::{build_last_resort_response, NOT_FOUND_404}; use crate::response_paths::{add_extension, get_encodings, get_path_from_request_url}; @@ -14,15 +15,16 @@ use crate::type_flyweight::BoxedResponse; pub async fn build_head_response( req: Request, directory: PathBuf, - content_encodings: Option>, + available_encodings: AvailableEncodings, ) -> Result { + let encodings = get_encodings(&req, &available_encodings); + let filepath = match get_path_from_request_url(&req, &directory).await { Some(fp) => fp, _ => return build_last_resort_response(StatusCode::NOT_FOUND, NOT_FOUND_404), }; let content_type = get_content_type(&filepath); - let encodings = get_encodings(&req, &content_encodings); // encodings if let Some(res) = compose_encoded_response(&filepath, content_type, encodings).await { @@ -40,16 +42,18 @@ pub async fn build_head_response( async fn compose_encoded_response( filepath: &PathBuf, content_type: &str, - encodings: Option>, + content_encodings: Option>, ) -> Option> { - let encds = match encodings { + let encodings = match content_encodings { Some(encds) => encds, _ => return None, }; - for enc in encds { - if let Some(encoded_path) = add_extension(filepath, &enc) { - if let Some(res) = compose_response(&encoded_path, content_type, Some(enc)).await { + for content_encoding in encodings { + if let Some(encoded_path) = add_extension(filepath, &content_encoding) { + if let Some(res) = + compose_response(&encoded_path, content_type, Some(content_encoding)).await + { return Some(res); } }; diff --git a/response/src/lib.rs b/response/src/lib.rs index 6e826c7..763b837 100644 --- a/response/src/lib.rs +++ b/response/src/lib.rs @@ -8,5 +8,6 @@ mod response_paths; mod responses; mod type_flyweight; +pub use crate::available_encodings::AvailableEncodings; pub use crate::responses::build_response; pub use crate::type_flyweight::BoxedResponse; diff --git a/response/src/range_response.rs b/response/src/range_response.rs index 646b90d..6252f02 100644 --- a/response/src/range_response.rs +++ b/response/src/range_response.rs @@ -11,6 +11,7 @@ use tokio::fs::File; use tokio::io::AsyncSeekExt; use tokio_util::io::ReaderStream; +use crate::available_encodings::AvailableEncodings; use crate::content_type::get_content_type; use crate::last_resort_response::{build_last_resort_response, NOT_FOUND_404}; use crate::response_paths::{add_extension, get_encodings, get_path_from_request_url}; @@ -28,7 +29,7 @@ pub const RANGE_NOT_SATISFIABLE_416: &str = "416 range not satisfiable"; pub async fn build_range_response( req: &Request, directory: &PathBuf, - content_encodings: &Option>, + available_encodings: &AvailableEncodings, ) -> Option> { let range_header = match get_range_header(req) { Some(rh) => rh, @@ -36,7 +37,7 @@ pub async fn build_range_response( }; let ranges = get_ranges(&range_header); - if let Some(res) = compose_range_response(req, directory, content_encodings, ranges).await { + if let Some(res) = compose_range_response(req, directory, available_encodings, ranges).await { return Some(res); }; @@ -137,7 +138,7 @@ fn get_window_range(range_chunk: &str) -> Option<(Option, Option)> async fn compose_range_response( req: &Request, directory: &PathBuf, - content_encodings: &Option>, + available_encodings: &AvailableEncodings, ranges: Option, Option)>>, ) -> Option> { let rngs = match ranges { @@ -155,7 +156,7 @@ async fn compose_range_response( _ => return None, }; - let encodings = get_encodings(req, content_encodings); + let encodings = get_encodings(req, available_encodings); if 1 == rngs.len() { if let Some(res) = build_single_range_response(&filepath, encodings, rngs).await { diff --git a/response/src/response_paths.rs b/response/src/response_paths.rs index a60172c..35b6764 100644 --- a/response/src/response_paths.rs +++ b/response/src/response_paths.rs @@ -58,7 +58,7 @@ pub async fn get_path_from_request_url( pub fn get_encodings( req: &Request, - content_encodings: &Option>, + available_encodings: &AvailableEncodings, ) -> Option> { let accept_encoding_header = match req.headers().get(ACCEPT_ENCODING) { Some(enc) => enc, @@ -70,8 +70,6 @@ pub fn get_encodings( _ => return None, }; - let available_encodings = AvailableEncodings::from(content_encodings); - let mut encodings = Vec::new(); for encoding in encoding_str.split(",") { let trimmed = encoding.trim(); diff --git a/response/src/responses.rs b/response/src/responses.rs index e0f462b..f16f2f5 100644 --- a/response/src/responses.rs +++ b/response/src/responses.rs @@ -4,6 +4,7 @@ use hyper::Method; use hyper::StatusCode; use std::path::PathBuf; +use crate::available_encodings::AvailableEncodings; use crate::get_response::build_get_response; use crate::head_response::build_head_response; use crate::last_resort_response::build_last_resort_response; @@ -14,12 +15,12 @@ pub const METHOD_NOT_ALLOWED_405: &str = "405 method not allowed"; pub async fn build_response( req: Request, directory: PathBuf, - content_encodings: Option>, + available_encodings: AvailableEncodings, fallback_404: Option, ) -> Result { match req.method() { - &Method::HEAD => build_head_response(req, directory, content_encodings).await, - &Method::GET => build_get_response(req, directory, content_encodings, fallback_404).await, + &Method::HEAD => build_head_response(req, directory, available_encodings).await, + &Method::GET => build_get_response(req, directory, available_encodings, fallback_404).await, _ => build_last_resort_response(StatusCode::METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED_405), } } From 8b2029c2a5301899029bbfa74e24271a5de68de2 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 13:29:02 -0700 Subject: [PATCH 04/23] add response params --- response/src/lib.rs | 2 +- response/src/responses.rs | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/response/src/lib.rs b/response/src/lib.rs index 763b837..352b58b 100644 --- a/response/src/lib.rs +++ b/response/src/lib.rs @@ -9,5 +9,5 @@ mod responses; mod type_flyweight; pub use crate::available_encodings::AvailableEncodings; -pub use crate::responses::build_response; +pub use crate::responses::{build_response, ResponseParams}; pub use crate::type_flyweight::BoxedResponse; diff --git a/response/src/responses.rs b/response/src/responses.rs index f16f2f5..f2f6944 100644 --- a/response/src/responses.rs +++ b/response/src/responses.rs @@ -12,6 +12,28 @@ use crate::type_flyweight::BoxedResponse; pub const METHOD_NOT_ALLOWED_405: &str = "405 method not allowed"; +pub struct ResponseParams { + directory: PathBuf, + available_encodings: AvailableEncodings, + fallback_404: Option, +} + +impl ResponseParams { + pub fn from( + directory: PathBuf, + content_encodings: Option>, + fallback_404: Option, + ) -> ResponseParams { + let available_encodings = AvailableEncodings::from(&content_encodings); + + ResponseParams { + directory: directory, + available_encodings: available_encodings, + fallback_404: fallback_404, + } + } +} + pub async fn build_response( req: Request, directory: PathBuf, @@ -19,8 +41,8 @@ pub async fn build_response( fallback_404: Option, ) -> Result { match req.method() { - &Method::HEAD => build_head_response(req, directory, available_encodings).await, &Method::GET => build_get_response(req, directory, available_encodings, fallback_404).await, + &Method::HEAD => build_head_response(req, directory, available_encodings).await, _ => build_last_resort_response(StatusCode::METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED_405), } } From 41737b5318a86a1359076a936ec98abe10583cef Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 13:35:42 -0700 Subject: [PATCH 05/23] create service from config --- file_server/src/main.rs | 2 +- file_server/src/service.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/file_server/src/main.rs b/file_server/src/main.rs index c68f7cd..dcee119 100644 --- a/file_server/src/main.rs +++ b/file_server/src/main.rs @@ -23,7 +23,7 @@ async fn main() -> Result<(), String> { println!("file_server: {}", conf.host_and_port); - let svc = service::Svc::new(conf.directory, conf.content_encodings, conf.filepath_404); + let svc = service::Svc::from(conf); loop { let (stream, _remote_address) = match listener.accept().await { diff --git a/file_server/src/service.rs b/file_server/src/service.rs index cb625c3..d5d21ea 100644 --- a/file_server/src/service.rs +++ b/file_server/src/service.rs @@ -5,6 +5,7 @@ use std::future::Future; use std::path::PathBuf; use std::pin::Pin; +use crate::config::Config; /* BoxedResponse is a type. It should work with hyper responses across @@ -20,17 +21,13 @@ pub struct Svc { } impl Svc { - pub fn new( - directory: PathBuf, - content_encodings: Option>, - fallback_404: Option, - ) -> Svc { - let available_encodings = AvailableEncodings::from(&content_encodings); + pub fn from(config: Config) -> Svc { + let available_encodings = AvailableEncodings::from(&config.content_encodings); Svc { - directory: directory, + directory: config.directory, available_encodings: available_encodings, - fallback_404: fallback_404, + fallback_404: config.filepath_404, } } } From 48687f1fef87fc3a92c6309216f8c8baa1a22e64 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 13:37:32 -0700 Subject: [PATCH 06/23] service nits --- file_server/src/service.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/file_server/src/service.rs b/file_server/src/service.rs index d5d21ea..da311fe 100644 --- a/file_server/src/service.rs +++ b/file_server/src/service.rs @@ -5,7 +5,7 @@ use std::future::Future; use std::path::PathBuf; use std::pin::Pin; -use crate::config::Config; +use config::Config; /* BoxedResponse is a type. It should work with hyper responses across @@ -15,8 +15,8 @@ use response::{build_response, AvailableEncodings, BoxedResponse}; #[derive(Clone, Debug)] pub struct Svc { - directory: PathBuf, available_encodings: AvailableEncodings, + directory: PathBuf, fallback_404: Option, } @@ -25,8 +25,8 @@ impl Svc { let available_encodings = AvailableEncodings::from(&config.content_encodings); Svc { - directory: config.directory, available_encodings: available_encodings, + directory: config.directory, fallback_404: config.filepath_404, } } From b530c1b9711b219f593f0855a61fc6475d0232aa Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 13:40:18 -0700 Subject: [PATCH 07/23] less reference passing --- file_server/src/service.rs | 2 +- response/src/available_encodings.rs | 2 +- response/src/responses.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/file_server/src/service.rs b/file_server/src/service.rs index da311fe..e05be6b 100644 --- a/file_server/src/service.rs +++ b/file_server/src/service.rs @@ -22,7 +22,7 @@ pub struct Svc { impl Svc { pub fn from(config: Config) -> Svc { - let available_encodings = AvailableEncodings::from(&config.content_encodings); + let available_encodings = AvailableEncodings::from(config.content_encodings); Svc { available_encodings: available_encodings, diff --git a/response/src/available_encodings.rs b/response/src/available_encodings.rs index f662643..073cd7a 100644 --- a/response/src/available_encodings.rs +++ b/response/src/available_encodings.rs @@ -7,7 +7,7 @@ pub struct AvailableEncodings { } impl AvailableEncodings { - pub fn from(potential_encodings: &Option>) -> AvailableEncodings { + pub fn from(potential_encodings: Option>) -> AvailableEncodings { let mut av_enc = AvailableEncodings { gzip: false, deflate: false, diff --git a/response/src/responses.rs b/response/src/responses.rs index f2f6944..a6e2e39 100644 --- a/response/src/responses.rs +++ b/response/src/responses.rs @@ -24,7 +24,7 @@ impl ResponseParams { content_encodings: Option>, fallback_404: Option, ) -> ResponseParams { - let available_encodings = AvailableEncodings::from(&content_encodings); + let available_encodings = AvailableEncodings::from(content_encodings); ResponseParams { directory: directory, From 2526c5590ee8bcf835bdc2e73004aca18a32b4ae Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 13:55:16 -0700 Subject: [PATCH 08/23] use OsString --- response/src/response_paths.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/response/src/response_paths.rs b/response/src/response_paths.rs index 35b6764..112456c 100644 --- a/response/src/response_paths.rs +++ b/response/src/response_paths.rs @@ -1,7 +1,7 @@ use hyper::body::Incoming; use hyper::header::ACCEPT_ENCODING; use hyper::http::Request; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::path; use std::path::PathBuf; use tokio::fs; @@ -88,7 +88,7 @@ pub fn get_encodings( // nightly API replacement // https://doc.rust-lang.org/std/path/struct.Path.html#method.with_added_extension -// Filepath must be an file, not a directory for this to work. +// Filepath must be a file, not a directory for this to work. pub fn add_extension(filepath: &PathBuf, encoding: &str) -> Option { let enc_ext = match get_encoded_ext(encoding) { Some(enc) => enc, @@ -96,9 +96,8 @@ pub fn add_extension(filepath: &PathBuf, encoding: &str) -> Option { }; let os_ext = OsStr::new(enc_ext); - - let mut fp_with_ext = filepath.as_os_str().to_os_string(); - fp_with_ext.push(os_ext); + let mut fp_with_ext = OsString::from(filepath); + fp_with_ext.push(enc_ext); Some(PathBuf::from(fp_with_ext)) } From 6656bd0da4c32d798c501e0dbb33cc7496458e78 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 14:09:33 -0700 Subject: [PATCH 09/23] response paths with OsString --- response/src/get_response.rs | 4 +--- response/src/response_paths.rs | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/response/src/get_response.rs b/response/src/get_response.rs index 0b5d532..78389d7 100644 --- a/response/src/get_response.rs +++ b/response/src/get_response.rs @@ -12,13 +12,11 @@ use tokio_util::io::ReaderStream; use crate::available_encodings::AvailableEncodings; use crate::content_type::get_content_type; -use crate::last_resort_response::build_last_resort_response; +use crate::last_resort_response::{build_last_resort_response, NOT_FOUND_404}; use crate::range_response::build_range_response; use crate::response_paths::{add_extension, get_encodings, get_path_from_request_url}; use crate::type_flyweight::BoxedResponse; -pub const NOT_FOUND_404: &str = "404 not found"; - pub async fn build_get_response( req: Request, directory: PathBuf, diff --git a/response/src/response_paths.rs b/response/src/response_paths.rs index 112456c..118db8d 100644 --- a/response/src/response_paths.rs +++ b/response/src/response_paths.rs @@ -1,7 +1,7 @@ use hyper::body::Incoming; use hyper::header::ACCEPT_ENCODING; use hyper::http::Request; -use std::ffi::{OsStr, OsString}; +use std::ffi::OsString; use std::path; use std::path::PathBuf; use tokio::fs; @@ -95,7 +95,6 @@ pub fn add_extension(filepath: &PathBuf, encoding: &str) -> Option { _ => return None, }; - let os_ext = OsStr::new(enc_ext); let mut fp_with_ext = OsString::from(filepath); fp_with_ext.push(enc_ext); From f8f0ca191bfb243229a111a02ce4550517ab99b2 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 16:08:57 -0700 Subject: [PATCH 10/23] pass response_params, more explicit names --- file_server/src/service.rs | 25 +++++++++---------------- response/src/get_response.rs | 27 ++++++++++++++------------- response/src/head_response.rs | 10 ++++------ response/src/lib.rs | 5 ++--- response/src/responses.rs | 20 ++++++-------------- response/src/type_flyweight.rs | 10 ++++++++++ 6 files changed, 45 insertions(+), 52 deletions(-) diff --git a/file_server/src/service.rs b/file_server/src/service.rs index e05be6b..a3b9827 100644 --- a/file_server/src/service.rs +++ b/file_server/src/service.rs @@ -2,7 +2,6 @@ use hyper::body::Incoming as IncomingBody; use hyper::service::Service; use hyper::Request; use std::future::Future; -use std::path::PathBuf; use std::pin::Pin; use config::Config; @@ -11,23 +10,21 @@ use config::Config; It should work with hyper responses across different libraries and dependencies. */ -use response::{build_response, AvailableEncodings, BoxedResponse}; +use response::{build_response, BoxedResponse, ResponseParams}; #[derive(Clone, Debug)] pub struct Svc { - available_encodings: AvailableEncodings, - directory: PathBuf, - fallback_404: Option, + response_params: ResponseParams, } impl Svc { pub fn from(config: Config) -> Svc { - let available_encodings = AvailableEncodings::from(config.content_encodings); - Svc { - available_encodings: available_encodings, - directory: config.directory, - fallback_404: config.filepath_404, + response_params: ResponseParams::from( + config.directory, + config.filepath_404, + config.content_encodings, + ), } } } @@ -38,12 +35,8 @@ impl Service> for Svc { type Future = Pin> + Send>>; fn call(&self, req: Request) -> Self::Future { - let directory = self.directory.clone(); - let available_encodings = self.available_encodings.clone(); - let fallback_404 = self.fallback_404.clone(); + let response_params = self.response_params.clone(); - Box::pin( - async move { build_response(req, directory, available_encodings, fallback_404).await }, - ) + Box::pin(async move { build_response(req, response_params).await }) } } diff --git a/response/src/get_response.rs b/response/src/get_response.rs index 78389d7..eabb87f 100644 --- a/response/src/get_response.rs +++ b/response/src/get_response.rs @@ -10,34 +10,35 @@ use tokio::fs; use tokio::fs::File; use tokio_util::io::ReaderStream; -use crate::available_encodings::AvailableEncodings; use crate::content_type::get_content_type; use crate::last_resort_response::{build_last_resort_response, NOT_FOUND_404}; use crate::range_response::build_range_response; use crate::response_paths::{add_extension, get_encodings, get_path_from_request_url}; -use crate::type_flyweight::BoxedResponse; +use crate::type_flyweight::{BoxedResponse, ResponseParams}; pub async fn build_get_response( req: Request, - directory: PathBuf, - available_encodings: AvailableEncodings, - fallback_404: Option, + res_params: ResponseParams, ) -> Result { // check for range request - if let Some(res) = build_range_response(&req, &directory, &available_encodings).await { + if let Some(res) = + build_range_response(&req, &res_params.directory, &res_params.available_encodings).await + { return res; } // request file - let encodings = get_encodings(&req, &available_encodings); + let encodings = get_encodings(&req, &res_params.available_encodings); // serve file - if let Some(res) = build_file_response(&req, &directory, &encodings).await { + if let Some(res) = build_file_response(&req, &res_params.directory, &encodings).await { return res; }; // serve 404 - if let Some(res) = build_not_found_response(&directory, &fallback_404, &encodings).await { + if let Some(res) = + build_not_found_response(&res_params.directory, &res_params.filepath_404, &encodings).await + { return res; }; @@ -59,21 +60,21 @@ async fn build_file_response( async fn build_not_found_response( directory: &PathBuf, - fallback_404: &Option, + filepath_404: &Option, encodings: &Option>, ) -> Option> { - let fallback = match fallback_404 { + let fallback = match filepath_404 { Some(fb) => fb, _ => return None, }; // file starts with directory - let fallback_404_abs = match path::absolute(fallback) { + let filepath_404_abs = match path::absolute(fallback) { Ok(fb) => fb, _ => return None, }; - if !fallback_404_abs.starts_with(directory) { + if !filepath_404_abs.starts_with(directory) { return None; } diff --git a/response/src/head_response.rs b/response/src/head_response.rs index 26b96ce..8f0f63e 100644 --- a/response/src/head_response.rs +++ b/response/src/head_response.rs @@ -6,20 +6,18 @@ use hyper::StatusCode; use std::path::PathBuf; use tokio::fs; -use crate::available_encodings::AvailableEncodings; use crate::content_type::get_content_type; use crate::last_resort_response::{build_last_resort_response, NOT_FOUND_404}; use crate::response_paths::{add_extension, get_encodings, get_path_from_request_url}; -use crate::type_flyweight::BoxedResponse; +use crate::type_flyweight::{BoxedResponse, ResponseParams}; pub async fn build_head_response( req: Request, - directory: PathBuf, - available_encodings: AvailableEncodings, + res_params: ResponseParams, ) -> Result { - let encodings = get_encodings(&req, &available_encodings); + let encodings = get_encodings(&req, &res_params.available_encodings); - let filepath = match get_path_from_request_url(&req, &directory).await { + let filepath = match get_path_from_request_url(&req, &res_params.directory).await { Some(fp) => fp, _ => return build_last_resort_response(StatusCode::NOT_FOUND, NOT_FOUND_404), }; diff --git a/response/src/lib.rs b/response/src/lib.rs index 352b58b..9caf038 100644 --- a/response/src/lib.rs +++ b/response/src/lib.rs @@ -8,6 +8,5 @@ mod response_paths; mod responses; mod type_flyweight; -pub use crate::available_encodings::AvailableEncodings; -pub use crate::responses::{build_response, ResponseParams}; -pub use crate::type_flyweight::BoxedResponse; +pub use crate::responses::build_response; +pub use crate::type_flyweight::{BoxedResponse, ResponseParams}; diff --git a/response/src/responses.rs b/response/src/responses.rs index a6e2e39..71d4446 100644 --- a/response/src/responses.rs +++ b/response/src/responses.rs @@ -8,41 +8,33 @@ use crate::available_encodings::AvailableEncodings; use crate::get_response::build_get_response; use crate::head_response::build_head_response; use crate::last_resort_response::build_last_resort_response; -use crate::type_flyweight::BoxedResponse; +use crate::type_flyweight::{BoxedResponse, ResponseParams}; pub const METHOD_NOT_ALLOWED_405: &str = "405 method not allowed"; -pub struct ResponseParams { - directory: PathBuf, - available_encodings: AvailableEncodings, - fallback_404: Option, -} - impl ResponseParams { pub fn from( directory: PathBuf, + filepath_404: Option, content_encodings: Option>, - fallback_404: Option, ) -> ResponseParams { let available_encodings = AvailableEncodings::from(content_encodings); ResponseParams { directory: directory, available_encodings: available_encodings, - fallback_404: fallback_404, + filepath_404: filepath_404, } } } pub async fn build_response( req: Request, - directory: PathBuf, - available_encodings: AvailableEncodings, - fallback_404: Option, + response_params: ResponseParams, ) -> Result { match req.method() { - &Method::GET => build_get_response(req, directory, available_encodings, fallback_404).await, - &Method::HEAD => build_head_response(req, directory, available_encodings).await, + &Method::GET => build_get_response(req, response_params).await, + &Method::HEAD => build_head_response(req, response_params).await, _ => build_last_resort_response(StatusCode::METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED_405), } } diff --git a/response/src/type_flyweight.rs b/response/src/type_flyweight.rs index ffa07cc..6dc6358 100644 --- a/response/src/type_flyweight.rs +++ b/response/src/type_flyweight.rs @@ -1,6 +1,16 @@ use bytes::Bytes; use http_body_util::combinators::BoxBody; use hyper::Response; +use std::path::PathBuf; use tokio::io; +use crate::available_encodings::AvailableEncodings; + pub type BoxedResponse = Response>; + +#[derive(Clone, Debug)] +pub struct ResponseParams { + pub directory: PathBuf, + pub available_encodings: AvailableEncodings, + pub filepath_404: Option, +} From ce62a4e9327dda55277f23f37ff2d351ae535092 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 16:14:52 -0700 Subject: [PATCH 11/23] no repeat responses in head --- response/src/head_response.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/response/src/head_response.rs b/response/src/head_response.rs index 8f0f63e..2099042 100644 --- a/response/src/head_response.rs +++ b/response/src/head_response.rs @@ -17,23 +17,20 @@ pub async fn build_head_response( ) -> Result { let encodings = get_encodings(&req, &res_params.available_encodings); - let filepath = match get_path_from_request_url(&req, &res_params.directory).await { - Some(fp) => fp, - _ => return build_last_resort_response(StatusCode::NOT_FOUND, NOT_FOUND_404), - }; + if let Some(filepath) = get_path_from_request_url(&req, &res_params.directory).await { + let content_type = get_content_type(&filepath); - let content_type = get_content_type(&filepath); + // encodings + if let Some(res) = compose_encoded_response(&filepath, content_type, encodings).await { + return res; + }; - // encodings - if let Some(res) = compose_encoded_response(&filepath, content_type, encodings).await { - return res; + // origin target + if let Some(res) = compose_response(&filepath, content_type, None).await { + return res; + } }; - // origin target - if let Some(res) = compose_response(&filepath, content_type, None).await { - return res; - } - build_last_resort_response(StatusCode::NOT_FOUND, NOT_FOUND_404) } From 6368493eab196db04e9c19a63862fb16c464192c Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 16:29:51 -0700 Subject: [PATCH 12/23] update readme --- README.md | 10 ++++++++-- configuration.md | 22 ++++++++-------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 6b1fa0f..6c4bdb1 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ An `http` file server written in rust using [tokio](https://tokio.rs/) and [hyper](https://hyper.rs/). +Includes support for: +- http 1.1 / 2 +- boxed responses (send large files frame by frame) +- `head` requests +- `range` requests + ## How to use ### Install @@ -16,7 +22,7 @@ cargo install --path file_server/file_server ### Run -Run the following command: +Bash the following command: ```sh file_server @@ -24,7 +30,7 @@ file_server This will start `file_server` with it's default configuration in the `cwd`. -Bash the following command to serve files in the `cwd` at `localhost:3000`: +Now files can be requested from the `cwd` at `localhost:3000`: ```sh curl localhost:3000 diff --git a/configuration.md b/configuration.md index 806c375..ad5e701 100644 --- a/configuration.md +++ b/configuration.md @@ -2,7 +2,7 @@ ## Configuration -A valid [JSON configuration file](./file_server.json) adheres to the following schema. +A valid [JSON configuration file](./file_server.json) matches the following schema. ```JSON { @@ -27,9 +27,9 @@ Open a browser and visit `http://localhost:4000`. ### Accept-Encoding -If an `accept-encoding` header is found in a request `file_server` will return a corresponding `gzip`-ed version of a requested file. +When an `accept-encoding` header is found in a request, `file_server` will return a corresponding `zip`-ed version of a requested file if available. -So if a request has: +So if a request has the following header: ``` Accept-Encoding: gzip; @@ -38,26 +38,20 @@ Accept-Encoding: gzip; And the target file has a correspponding gziped file: ```sh -./www/index.html.gz # serve gzip file if available -./www/index.html # otherwise serve unencoded file +./www/index.html # target file +./www/index.html.gz # gzipped file ``` -Then `file_server` will send the encoded file if available. Otherwise it serves the unencoded file. +Then `file_server` will send the encoded file, if available. Otherwise, it serves the unencoded file. ### No dynamic encoding support `File_server` does not encode or zip files ever. -This program serves static files. So you already got static files. - -Just zip them up now. Why abuse cpu resoures for run time compression on static files? It don't make sense. - -Go take a shower, stinky. Start that day over. +This program serves static files. Just zip them up now it saves on memory resources. ### Range requests -`File_server` supports range requests. +`File_server` supports single range requests. Multipart ranges are not currently supported because multipart ranges are a memory hog. - -However, there are plans to add limited support with big restrictions on range sizes. From 27146d71e7b655400ecd2dcf4a6ccbdacf800815 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 16:40:33 -0700 Subject: [PATCH 13/23] flatten readme --- .gitignore | 6 +++ README.md | 55 ++++++++++++++++++- configuration.md | 57 -------------------- file_server.json => file_server.example.json | 0 4 files changed, 60 insertions(+), 58 deletions(-) delete mode 100644 configuration.md rename file_server.json => file_server.example.json (100%) diff --git a/.gitignore b/.gitignore index 2ee01cd..07572f6 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,9 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + + +# FILE SERVER + +*.json +!*.example.json diff --git a/README.md b/README.md index 6c4bdb1..977830e 100644 --- a/README.md +++ b/README.md @@ -36,9 +36,62 @@ Now files can be requested from the `cwd` at `localhost:3000`: curl localhost:3000 ``` +Alternatively, bash the following command to serve files based on a an example configuration: + +```sh +file_server file_server.example.json +``` + +Open a browser and visit `http://localhost:4000`. + ### Configuration -Click [here](./configuration.md) to learn how to configure `file_server`. +A valid [JSON configuration file](./file_server.example.json) matches the following schema. + +```JSON +{ + "directory": "./demo", + "host_and_port": "127.0.0.1:4000", + "content_encodings": ["gzip", "deflate", "br", "zstd"], + "filepath_404": "./demo/404.html" +} +``` + +Filepaths can be relative or absolute. Relative paths are "relative from" the filepath of the JSON configuration. + +The `content_encodings` and `filepath_404` properties are optional. + +### Accept-Encoding + +When an `accept-encoding` header is found in a request, `file_server` will return a corresponding `zip`-ed version of file if available. + +So if a request has the following header: + +``` +Accept-Encoding: gzip; +``` + +And the source file has a correspponding gziped file: + +```sh +./www/index.html # source file +./www/index.html.gz # gzipped file +``` + +Then `file_server` will send the encoded file, if available. Otherwise, it serves the source file. + +### No dynamic encoding support + +`File_server` does not encode or zip files ever. + +This program serves static files. Just zip them up now to save memory resources. + +### Range requests + +`File_server` supports single range requests. + +Multipart ranges are not currently supported because multipart ranges are a memory hog and difficult to +deliver efficiently without potentially maxing out ram resources. ## Licence diff --git a/configuration.md b/configuration.md deleted file mode 100644 index ad5e701..0000000 --- a/configuration.md +++ /dev/null @@ -1,57 +0,0 @@ -# File server - -## Configuration - -A valid [JSON configuration file](./file_server.json) matches the following schema. - -```JSON -{ - "directory": "./demo", - "host_and_port": "127.0.0.1:4000", - "content_encodings": ["gzip", "deflate", "br", "zstd"], - "filepath_404": "./demo/404.html" -} -``` - -The `content_encodings` and `filepath_404` properties are optional. - -### Run - -Bash the following command to serve files based on a configuration: - -```sh -file_server path/to/config.json -``` - -Open a browser and visit `http://localhost:4000`. - -### Accept-Encoding - -When an `accept-encoding` header is found in a request, `file_server` will return a corresponding `zip`-ed version of a requested file if available. - -So if a request has the following header: - -``` -Accept-Encoding: gzip; -``` - -And the target file has a correspponding gziped file: - -```sh -./www/index.html # target file -./www/index.html.gz # gzipped file -``` - -Then `file_server` will send the encoded file, if available. Otherwise, it serves the unencoded file. - -### No dynamic encoding support - -`File_server` does not encode or zip files ever. - -This program serves static files. Just zip them up now it saves on memory resources. - -### Range requests - -`File_server` supports single range requests. - -Multipart ranges are not currently supported because multipart ranges are a memory hog. diff --git a/file_server.json b/file_server.example.json similarity index 100% rename from file_server.json rename to file_server.example.json From 6c28f7956e620fc32626053f823ff370e0224533 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 16:41:30 -0700 Subject: [PATCH 14/23] more readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 977830e..b446ac9 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Includes support for: - boxed responses (send large files frame by frame) - `head` requests - `range` requests +- encoded requests ## How to use From 10df5dfa9ebca31cf7163e5f45bb63f20bfbf1e8 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 16:57:14 -0700 Subject: [PATCH 15/23] split get response into range and file response --- response/src/get_response.rs | 8 ++++++++ response/src/responses.rs | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/response/src/get_response.rs b/response/src/get_response.rs index eabb87f..8a100e7 100644 --- a/response/src/get_response.rs +++ b/response/src/get_response.rs @@ -27,6 +27,14 @@ pub async fn build_get_response( return res; } + // fallback to file response + build_the_file_response(req, res_params).await +} + +pub async fn build_the_file_response( + req: Request, + res_params: ResponseParams, +) -> Result { // request file let encodings = get_encodings(&req, &res_params.available_encodings); diff --git a/response/src/responses.rs b/response/src/responses.rs index 71d4446..7bb1068 100644 --- a/response/src/responses.rs +++ b/response/src/responses.rs @@ -30,11 +30,11 @@ impl ResponseParams { pub async fn build_response( req: Request, - response_params: ResponseParams, + res_params: ResponseParams, ) -> Result { match req.method() { - &Method::GET => build_get_response(req, response_params).await, - &Method::HEAD => build_head_response(req, response_params).await, + &Method::GET => build_get_response(req, res_params).await, + &Method::HEAD => build_head_response(req, res_params).await, _ => build_last_resort_response(StatusCode::METHOD_NOT_ALLOWED, METHOD_NOT_ALLOWED_405), } } From e73cfbf602ea54ac9648592369f774fcef9565ef Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 17:15:27 -0700 Subject: [PATCH 16/23] range response --- response/src/get_response.rs | 14 ++++++-------- response/src/range_response.rs | 14 ++++++++++---- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/response/src/get_response.rs b/response/src/get_response.rs index 8a100e7..3712278 100644 --- a/response/src/get_response.rs +++ b/response/src/get_response.rs @@ -21,19 +21,17 @@ pub async fn build_get_response( res_params: ResponseParams, ) -> Result { // check for range request - if let Some(res) = - build_range_response(&req, &res_params.directory, &res_params.available_encodings).await - { + if let Some(res) = build_range_response(&req, &res_params).await { return res; } // fallback to file response - build_the_file_response(req, res_params).await + build_the_file_response(req, &res_params).await } pub async fn build_the_file_response( req: Request, - res_params: ResponseParams, + res_params: &ResponseParams, ) -> Result { // request file let encodings = get_encodings(&req, &res_params.available_encodings); @@ -104,7 +102,7 @@ async fn build_response( }; // origin target - compose_get_response(&filepath, content_type, status_code, None).await + compose_response(&filepath, content_type, status_code, None).await } async fn compose_encoded_response( @@ -121,7 +119,7 @@ async fn compose_encoded_response( for enc in encds { if let Some(encoded_path) = add_extension(filepath, &enc) { if let Some(res) = - compose_get_response(&encoded_path, content_type, status_code, Some(enc)).await + compose_response(&encoded_path, content_type, status_code, Some(enc)).await { return Some(res); } @@ -131,7 +129,7 @@ async fn compose_encoded_response( None } -async fn compose_get_response( +async fn compose_response( filepath: &PathBuf, content_type: &str, status_code: StatusCode, diff --git a/response/src/range_response.rs b/response/src/range_response.rs index 6252f02..fb81860 100644 --- a/response/src/range_response.rs +++ b/response/src/range_response.rs @@ -15,7 +15,7 @@ use crate::available_encodings::AvailableEncodings; use crate::content_type::get_content_type; use crate::last_resort_response::{build_last_resort_response, NOT_FOUND_404}; use crate::response_paths::{add_extension, get_encodings, get_path_from_request_url}; -use crate::type_flyweight::BoxedResponse; +use crate::type_flyweight::{BoxedResponse, ResponseParams}; // Range: =- // Range: =- @@ -28,8 +28,7 @@ pub const RANGE_NOT_SATISFIABLE_416: &str = "416 range not satisfiable"; pub async fn build_range_response( req: &Request, - directory: &PathBuf, - available_encodings: &AvailableEncodings, + res_params: &ResponseParams, ) -> Option> { let range_header = match get_range_header(req) { Some(rh) => rh, @@ -37,7 +36,14 @@ pub async fn build_range_response( }; let ranges = get_ranges(&range_header); - if let Some(res) = compose_range_response(req, directory, available_encodings, ranges).await { + if let Some(res) = compose_range_response( + req, + &res_params.directory, + &res_params.available_encodings, + ranges, + ) + .await + { return Some(res); }; From cb6cb633eee2680399545ac40ec5ff2fc3d27eec Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 17:31:15 -0700 Subject: [PATCH 17/23] keep response params implementation in flyweight --- response/src/responses.rs | 18 ------------------ response/src/type_flyweight.rs | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/response/src/responses.rs b/response/src/responses.rs index 7bb1068..b29c275 100644 --- a/response/src/responses.rs +++ b/response/src/responses.rs @@ -2,9 +2,7 @@ use hyper::body::Incoming; use hyper::http::Request; use hyper::Method; use hyper::StatusCode; -use std::path::PathBuf; -use crate::available_encodings::AvailableEncodings; use crate::get_response::build_get_response; use crate::head_response::build_head_response; use crate::last_resort_response::build_last_resort_response; @@ -12,22 +10,6 @@ use crate::type_flyweight::{BoxedResponse, ResponseParams}; pub const METHOD_NOT_ALLOWED_405: &str = "405 method not allowed"; -impl ResponseParams { - pub fn from( - directory: PathBuf, - filepath_404: Option, - content_encodings: Option>, - ) -> ResponseParams { - let available_encodings = AvailableEncodings::from(content_encodings); - - ResponseParams { - directory: directory, - available_encodings: available_encodings, - filepath_404: filepath_404, - } - } -} - pub async fn build_response( req: Request, res_params: ResponseParams, diff --git a/response/src/type_flyweight.rs b/response/src/type_flyweight.rs index 6dc6358..72122d1 100644 --- a/response/src/type_flyweight.rs +++ b/response/src/type_flyweight.rs @@ -14,3 +14,19 @@ pub struct ResponseParams { pub available_encodings: AvailableEncodings, pub filepath_404: Option, } + +impl ResponseParams { + pub fn from( + directory: PathBuf, + filepath_404: Option, + content_encodings: Option>, + ) -> ResponseParams { + let available_encodings = AvailableEncodings::from(content_encodings); + + ResponseParams { + directory: directory, + available_encodings: available_encodings, + filepath_404: filepath_404, + } + } +} From 68c79b4a2bfa6f418c72f905076f6955467b2700 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 18:32:51 -0700 Subject: [PATCH 18/23] spilt response path logic --- README.md | 18 ++++++++++-------- response/src/get_response.rs | 19 ++++++++----------- response/src/response_paths.rs | 6 +++++- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b446ac9..0ec4b98 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,6 @@ Now files can be requested from the `cwd` at `localhost:3000`: curl localhost:3000 ``` -Alternatively, bash the following command to serve files based on a an example configuration: - -```sh -file_server file_server.example.json -``` - -Open a browser and visit `http://localhost:4000`. - ### Configuration A valid [JSON configuration file](./file_server.example.json) matches the following schema. @@ -62,6 +54,16 @@ Filepaths can be relative or absolute. Relative paths are "relative from" the fi The `content_encodings` and `filepath_404` properties are optional. +#### Run with configuration + +Bash the following command to serve files based on a an example configuration: + +```sh +file_server file_server.example.json +``` + +Open a browser and visit `http://localhost:4000`. + ### Accept-Encoding When an `accept-encoding` header is found in a request, `file_server` will return a corresponding `zip`-ed version of file if available. diff --git a/response/src/get_response.rs b/response/src/get_response.rs index 3712278..a6af23d 100644 --- a/response/src/get_response.rs +++ b/response/src/get_response.rs @@ -4,16 +4,16 @@ use hyper::body::{Frame, Incoming}; use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE}; use hyper::http::{Request, Response}; use hyper::StatusCode; -use std::path; use std::path::PathBuf; use tokio::fs; -use tokio::fs::File; use tokio_util::io::ReaderStream; use crate::content_type::get_content_type; use crate::last_resort_response::{build_last_resort_response, NOT_FOUND_404}; use crate::range_response::build_range_response; -use crate::response_paths::{add_extension, get_encodings, get_path_from_request_url}; +use crate::response_paths::{ + add_extension, get_encodings, get_filepath, get_path_from_request_url, +}; use crate::type_flyweight::{BoxedResponse, ResponseParams}; pub async fn build_get_response( @@ -37,6 +37,7 @@ pub async fn build_the_file_response( let encodings = get_encodings(&req, &res_params.available_encodings); // serve file + // if get_path_from_request_url, build response if let Some(res) = build_file_response(&req, &res_params.directory, &encodings).await { return res; }; @@ -75,16 +76,12 @@ async fn build_not_found_response( }; // file starts with directory - let filepath_404_abs = match path::absolute(fallback) { - Ok(fb) => fb, + let filepath_404 = match get_filepath(directory, fallback).await { + Some(fb) => fb, _ => return None, }; - if !filepath_404_abs.starts_with(directory) { - return None; - } - - build_response(&fallback, StatusCode::NOT_FOUND, &encodings).await + build_response(&filepath_404, StatusCode::NOT_FOUND, &encodings).await } async fn build_response( @@ -144,7 +141,7 @@ async fn compose_response( return None; } - let file = match File::open(filepath).await { + let file = match fs::File::open(filepath).await { Ok(m) => m, _ => return None, }; diff --git a/response/src/response_paths.rs b/response/src/response_paths.rs index 118db8d..c35ca27 100644 --- a/response/src/response_paths.rs +++ b/response/src/response_paths.rs @@ -19,7 +19,11 @@ pub async fn get_path_from_request_url( _ => uri_path, }; - let mut target_path = match path::absolute(directory.join(&stripped)) { + get_filepath(directory, &PathBuf::from(stripped)).await +} + +pub async fn get_filepath(directory: &PathBuf, filepath: &PathBuf) -> Option { + let mut target_path = match path::absolute(directory.join(&filepath)) { Ok(pb) => pb, _ => return None, }; From aa23b33c8c87b243e9abbef8f88a4ad6f6cee60d Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Thu, 3 Jul 2025 18:36:01 -0700 Subject: [PATCH 19/23] rename functions albiet badly --- response/src/get_response.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/response/src/get_response.rs b/response/src/get_response.rs index a6af23d..aa847cb 100644 --- a/response/src/get_response.rs +++ b/response/src/get_response.rs @@ -26,10 +26,10 @@ pub async fn build_get_response( } // fallback to file response - build_the_file_response(req, &res_params).await + build_file_response(req, &res_params).await } -pub async fn build_the_file_response( +async fn build_file_response( req: Request, res_params: &ResponseParams, ) -> Result { @@ -38,7 +38,7 @@ pub async fn build_the_file_response( // serve file // if get_path_from_request_url, build response - if let Some(res) = build_file_response(&req, &res_params.directory, &encodings).await { + if let Some(res) = build_req_path_response(&req, &res_params.directory, &encodings).await { return res; }; @@ -52,7 +52,7 @@ pub async fn build_the_file_response( build_last_resort_response(StatusCode::NOT_FOUND, NOT_FOUND_404) } -async fn build_file_response( +async fn build_req_path_response( req: &Request, directory: &PathBuf, encodings: &Option>, From b0c17ee70b0edf1166707cb2920440d8dc735e61 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Sat, 5 Jul 2025 17:39:02 -0700 Subject: [PATCH 20/23] pre update --- response/src/range_response.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/response/src/range_response.rs b/response/src/range_response.rs index fb81860..07eb66d 100644 --- a/response/src/range_response.rs +++ b/response/src/range_response.rs @@ -66,20 +66,19 @@ fn get_range_header(req: &Request) -> Option { } // on any fail return nothing -fn get_ranges(range_str: &str) -> Option, Option)>> { - let stripped_range = range_str.trim(); - let range_values_str = match stripped_range.strip_prefix("bytes=") { +fn get_ranges(range_header_value: &str) -> Option, Option)>> { + let ranges_str = match range_header_value.trim().strip_prefix("bytes=") { Some(r) => r, _ => return None, }; let mut ranges: Vec<(Option, Option)> = Vec::new(); - for value_str in range_values_str.split(",") { - let trimmed_value_str = value_str.trim(); + for range_value_str in ranges_str.split(",") { + let range_str = range_value_str.trim(); // prefix range - if let Some(without_suffix) = trimmed_value_str.strip_suffix("-") { - let start_range_int: usize = match without_suffix.parse() { + if let Some(suffexless) = range_str.strip_suffix("-") { + let start_range_int: usize = match suffexless.parse() { Ok(sri) => sri, _ => return None, }; @@ -89,8 +88,8 @@ fn get_ranges(range_str: &str) -> Option, Option)>> { } // suffix-range - if let Some(without_prefix) = trimmed_value_str.strip_prefix("-") { - let end_range_int: usize = match without_prefix.parse() { + if let Some(prefixless) = range_str.strip_prefix("-") { + let end_range_int: usize = match prefixless.parse() { Ok(sri) => sri, _ => return None, }; @@ -100,7 +99,7 @@ fn get_ranges(range_str: &str) -> Option, Option)>> { } // window-range - let start_end_range = match get_window_range(trimmed_value_str) { + let start_end_range = match get_window_range(range_str) { Some(ser) => ser, _ => return None, }; @@ -108,7 +107,11 @@ fn get_ranges(range_str: &str) -> Option, Option)>> { ranges.push(start_end_range) } - return Some(ranges); + if 0 < ranges.len() { + return Some(ranges); + } + + None } fn get_window_range(range_chunk: &str) -> Option<(Option, Option)> { From e43f5073f9bdc76f729bc821e15874b55e80ec2c Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Sun, 6 Jul 2025 00:26:28 -0700 Subject: [PATCH 21/23] range response logic arguably updated --- response/src/range_response.rs | 111 +++++++++++++-------------------- 1 file changed, 43 insertions(+), 68 deletions(-) diff --git a/response/src/range_response.rs b/response/src/range_response.rs index 07eb66d..55c3bf0 100644 --- a/response/src/range_response.rs +++ b/response/src/range_response.rs @@ -6,7 +6,6 @@ use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYP use hyper::http::{Request, Response, StatusCode}; use std::io::SeekFrom; use std::path::PathBuf; -use tokio::fs; use tokio::fs::File; use tokio::io::AsyncSeekExt; use tokio_util::io::ReaderStream; @@ -30,27 +29,19 @@ pub async fn build_range_response( req: &Request, res_params: &ResponseParams, ) -> Option> { + // bail if no range header let range_header = match get_range_header(req) { Some(rh) => rh, _ => return None, }; - let ranges = get_ranges(&range_header); - if let Some(res) = compose_range_response( + compose_range_response( req, &res_params.directory, &res_params.available_encodings, - ranges, + range_header, ) .await - { - return Some(res); - }; - - Some(build_last_resort_response( - StatusCode::NOT_FOUND, - NOT_FOUND_404, - )) } fn get_range_header(req: &Request) -> Option { @@ -65,6 +56,35 @@ fn get_range_header(req: &Request) -> Option { } } +async fn compose_range_response( + req: &Request, + directory: &PathBuf, + available_encodings: &AvailableEncodings, + range_header: String, +) -> Option> { + if let Some(filepath) = get_path_from_request_url(req, directory).await { + if let Some(ranges) = get_ranges(&range_header) { + let encodings = get_encodings(req, available_encodings); + + if 1 == ranges.len() { + if let Some(res) = build_single_range_response(&filepath, encodings, ranges).await { + return Some(res); + } + } + }; + + return Some(build_last_resort_response( + StatusCode::RANGE_NOT_SATISFIABLE, + RANGE_NOT_SATISFIABLE_416, + )); + }; + + Some(build_last_resort_response( + StatusCode::NOT_FOUND, + NOT_FOUND_404, + )) +} + // on any fail return nothing fn get_ranges(range_header_value: &str) -> Option, Option)>> { let ranges_str = match range_header_value.trim().strip_prefix("bytes=") { @@ -144,42 +164,6 @@ fn get_window_range(range_chunk: &str) -> Option<(Option, Option)> None } -async fn compose_range_response( - req: &Request, - directory: &PathBuf, - available_encodings: &AvailableEncodings, - ranges: Option, Option)>>, -) -> Option> { - let rngs = match ranges { - Some(r) => r, - _ => { - return Some(build_last_resort_response( - StatusCode::RANGE_NOT_SATISFIABLE, - RANGE_NOT_SATISFIABLE_416, - )) - } - }; - - let filepath = match get_path_from_request_url(req, directory).await { - Some(fp) => fp, - _ => return None, - }; - - let encodings = get_encodings(req, available_encodings); - - if 1 == rngs.len() { - if let Some(res) = build_single_range_response(&filepath, encodings, rngs).await { - return Some(res); - } - } - - None -} - -fn build_content_range_header_str(start: &usize, end: &usize, size: &usize) -> String { - "bytes ".to_string() + &start.to_string() + "-" + &end.to_string() + "/" + &size.to_string() -} - async fn build_single_range_response( filepath: &PathBuf, encodings: Option>, @@ -226,8 +210,13 @@ async fn compose_single_range_response( content_encoding: Option<&str>, ranges: &Vec<(Option, Option)>, ) -> Option> { - let size = match get_size(filepath).await { - Some(s) => s, + let mut file = match File::open(filepath).await { + Ok(m) => m, + _ => return None, + }; + + let size: usize = match file.metadata().await { + Ok(md) => md.len() as usize, _ => return None, }; @@ -241,11 +230,6 @@ async fn compose_single_range_response( } }; - let mut file = match File::open(filepath).await { - Ok(m) => m, - _ => return None, - }; - if let Err(_err) = file.seek(SeekFrom::Start(start.clone() as u64)).await { return None; }; @@ -271,19 +255,6 @@ async fn compose_single_range_response( return Some(builder.body(boxed_body)); } -async fn get_size(filepath: &PathBuf) -> Option { - let metadata = match fs::metadata(filepath).await { - Ok(m) => m, - _ => return None, - }; - - if !metadata.is_file() { - return None; - } - - Some(metadata.len() as usize) -} - fn get_start_end( ranges: &Vec<(Option, Option)>, size: usize, @@ -304,3 +275,7 @@ fn get_start_end( None } + +fn build_content_range_header_str(start: &usize, end: &usize, size: &usize) -> String { + "bytes ".to_string() + &start.to_string() + "-" + &end.to_string() + "/" + &size.to_string() +} From 6ac9b2b621d6bd0eb1cf0583637e2e38e9389e06 Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Sun, 6 Jul 2025 00:36:35 -0700 Subject: [PATCH 22/23] bad response rather than range not satisfiable --- response/src/range_response.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/response/src/range_response.rs b/response/src/range_response.rs index 55c3bf0..330c2b1 100644 --- a/response/src/range_response.rs +++ b/response/src/range_response.rs @@ -23,6 +23,7 @@ use crate::type_flyweight::{BoxedResponse, ResponseParams}; // multi range requests require an entirely different strategy // Range: =-, …, - +pub const BAD_REQUEST_400: &str = "400 bad request"; pub const RANGE_NOT_SATISFIABLE_416: &str = "416 range not satisfiable"; pub async fn build_range_response( @@ -224,8 +225,8 @@ async fn compose_single_range_response( Some(se) => se, _ => { return Some(build_last_resort_response( - StatusCode::RANGE_NOT_SATISFIABLE, - RANGE_NOT_SATISFIABLE_416, + StatusCode::BAD_REQUEST, + BAD_REQUEST_400, )) } }; From d2c17a9a6c37157fe1eea2000276a6d0c045fa6e Mon Sep 17 00:00:00 2001 From: Taylor Vann Date: Sun, 6 Jul 2025 12:22:44 -0700 Subject: [PATCH 23/23] readme filepaths run --- README.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 0ec4b98..b24b9c9 100644 --- a/README.md +++ b/README.md @@ -59,10 +59,10 @@ The `content_encodings` and `filepath_404` properties are optional. Bash the following command to serve files based on a an example configuration: ```sh -file_server file_server.example.json +file_server file_server/file_server.example.json ``` -Open a browser and visit `http://localhost:4000`. +Open a browser and visit `http://localhost:3000` and an encoded version of `index.html` will be delivered. ### Accept-Encoding @@ -81,7 +81,7 @@ And the source file has a correspponding gziped file: ./www/index.html.gz # gzipped file ``` -Then `file_server` will send the encoded file, if available. Otherwise, it serves the source file. +`File_server` will send the encoded file, if available. Otherwise, it serves the source file. ### No dynamic encoding support @@ -93,9 +93,16 @@ This program serves static files. Just zip them up now to save memory resources. `File_server` supports single range requests. -Multipart ranges are not currently supported because multipart ranges are a memory hog and difficult to -deliver efficiently without potentially maxing out ram resources. +Bash the following command: + +```sh +curl -v -r 0-6 localhost:3000 +``` + +Multipart ranges are not currently supported. + +Multipart ranges are memory hogs and difficult to deliver efficiently without abusing memory resources. ## Licence -BSD 3-Clause License +`File_server` is released under the BSD 3-Clause License.