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/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/README.md b/README.md index 6b1fa0f..0ec4b98 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,13 @@ 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 +- encoded requests + ## How to use ### Install @@ -16,7 +23,7 @@ cargo install --path file_server/file_server ### Run -Run the following command: +Bash the following command: ```sh file_server @@ -24,7 +31,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 @@ -32,7 +39,62 @@ curl localhost:3000 ### 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. + +#### 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. + +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 8b42be5..0000000 --- a/configuration.md +++ /dev/null @@ -1,66 +0,0 @@ -# File server - -## Configuration - -A valid [JSON configuration file](./file_server.json) adheres to the following schema. - -```JSON -{ - "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] - ] -} -``` - -The `content_encodings` and `filepath_404s` 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 - -If an `accept-encoding` header is found in a request `file_server` will return a corresponding `gzip`-ed version of a requested file. - -So if a request has: - -``` -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 -``` - -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. - -### Range requests - -`File_server` supports 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. 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 diff --git a/file_server/src/main.rs b/file_server/src/main.rs index 175d83c..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 { @@ -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..a3b9827 100644 --- a/file_server/src/service.rs +++ b/file_server/src/service.rs @@ -2,33 +2,29 @@ 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; /* BoxedResponse is a type. It should work with hyper responses across different libraries and dependencies. */ -use response::{build_response, BoxedResponse}; +use response::{build_response, BoxedResponse, ResponseParams}; #[derive(Clone, Debug)] pub struct Svc { - directory: PathBuf, - content_encodings: Option>, - fallback_404: Option, + response_params: ResponseParams, } impl Svc { - pub fn new( - directory: PathBuf, - content_encodings: Option>, - fallback_404: Option, - ) -> Svc { + pub fn from(config: Config) -> Svc { Svc { - directory: directory, - content_encodings: content_encodings, - fallback_404: fallback_404, + response_params: ResponseParams::from( + config.directory, + config.filepath_404, + config.content_encodings, + ), } } } @@ -39,12 +35,8 @@ impl Service> for Svc { type Future = Pin> + Send>>; fn call(&self, req: Request) -> Self::Future { - let directory = self.directory.clone(); - let content_encodings = self.content_encodings.clone(); - let fallback_404 = self.fallback_404.clone(); + let response_params = self.response_params.clone(); - Box::pin( - async move { build_response(req, directory, content_encodings, fallback_404).await }, - ) + Box::pin(async move { build_response(req, response_params).await }) } } diff --git a/response/src/available_encodings.rs b/response/src/available_encodings.rs index dd8639f..073cd7a 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,25 +15,21 @@ impl AvailableEncodings { zstd: false, }; - if let Some(pe) = potential_encodings { - av_enc.update(pe); + if let Some(encodings) = potential_encodings { + for encoding in encodings { + match encoding.as_str() { + "gzip" => av_enc.gzip = true, + "deflate" => av_enc.deflate = true, + "br" => av_enc.br = true, + "zstd" => av_enc.zstd = true, + _ => {} + } + } } 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 encoding_is_available(&self, encoding: &str) -> bool { match encoding { "gzip" => self.gzip, diff --git a/response/src/get_response.rs b/response/src/get_response.rs index 3a950b8..aa847cb 100644 --- a/response/src/get_response.rs +++ b/response/src/get_response.rs @@ -4,48 +4,55 @@ 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; +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"; +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( req: Request, - directory: PathBuf, - content_encodings: Option>, - fallback_404: Option, + res_params: ResponseParams, ) -> 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, &res_params).await { return res; } + // fallback to file response + build_file_response(req, &res_params).await +} + +async fn build_file_response( + req: Request, + res_params: &ResponseParams, +) -> Result { // request file - let encodings = get_encodings(&req, &content_encodings); + let encodings = get_encodings(&req, &res_params.available_encodings); // serve file - if let Some(res) = build_file_response(&req, &directory, &encodings).await { + // if get_path_from_request_url, build response + if let Some(res) = build_req_path_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; }; 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>, @@ -60,25 +67,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) { - Ok(fb) => fb, + let filepath_404 = match get_filepath(directory, fallback).await { + Some(fb) => fb, _ => return None, }; - if !fallback_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( @@ -96,7 +99,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( @@ -113,7 +116,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); } @@ -123,7 +126,7 @@ async fn compose_encoded_response( None } -async fn compose_get_response( +async fn compose_response( filepath: &PathBuf, content_type: &str, status_code: StatusCode, @@ -138,7 +141,7 @@ async fn compose_get_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/head_response.rs b/response/src/head_response.rs index c889745..2099042 100644 --- a/response/src/head_response.rs +++ b/response/src/head_response.rs @@ -9,30 +9,27 @@ use tokio::fs; 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, - content_encodings: Option>, + res_params: ResponseParams, ) -> Result { - 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 encodings = get_encodings(&req, &res_params.available_encodings); - let content_type = get_content_type(&filepath); - let encodings = get_encodings(&req, &content_encodings); + if let Some(filepath) = get_path_from_request_url(&req, &res_params.directory).await { + 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) } @@ -40,16 +37,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..9caf038 100644 --- a/response/src/lib.rs +++ b/response/src/lib.rs @@ -9,4 +9,4 @@ mod responses; mod type_flyweight; pub use crate::responses::build_response; -pub use crate::type_flyweight::BoxedResponse; +pub use crate::type_flyweight::{BoxedResponse, ResponseParams}; diff --git a/response/src/range_response.rs b/response/src/range_response.rs index 646b90d..330c2b1 100644 --- a/response/src/range_response.rs +++ b/response/src/range_response.rs @@ -6,15 +6,15 @@ 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; +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: =- @@ -23,27 +23,26 @@ use crate::type_flyweight::BoxedResponse; // 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( req: &Request, - directory: &PathBuf, - content_encodings: &Option>, + 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(req, directory, content_encodings, ranges).await { - return Some(res); - }; - - Some(build_last_resort_response( - StatusCode::NOT_FOUND, - NOT_FOUND_404, - )) + compose_range_response( + req, + &res_params.directory, + &res_params.available_encodings, + range_header, + ) + .await } fn get_range_header(req: &Request) -> Option { @@ -58,21 +57,49 @@ 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_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, }; @@ -82,8 +109,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, }; @@ -93,7 +120,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, }; @@ -101,7 +128,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)> { @@ -134,42 +165,6 @@ fn get_window_range(range_chunk: &str) -> Option<(Option, Option)> None } -async fn compose_range_response( - req: &Request, - directory: &PathBuf, - content_encodings: &Option>, - 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, content_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>, @@ -216,8 +211,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, }; @@ -225,17 +225,12 @@ 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, )) } }; - 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; }; @@ -261,19 +256,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, @@ -294,3 +276,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() +} diff --git a/response/src/response_paths.rs b/response/src/response_paths.rs index 5723bca..c35ca27 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::OsString; use std::path; use std::path::PathBuf; use tokio::fs; @@ -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, }; @@ -58,7 +62,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 +74,6 @@ pub fn get_encodings( _ => return None, }; - let available_encodings = AvailableEncodings::new(content_encodings); - let mut encodings = Vec::new(); for encoding in encoding_str.split(",") { let trimmed = encoding.trim(); @@ -90,17 +92,15 @@ 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, _ => return None, }; - 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)) } diff --git a/response/src/responses.rs b/response/src/responses.rs index e0f462b..b29c275 100644 --- a/response/src/responses.rs +++ b/response/src/responses.rs @@ -2,24 +2,21 @@ use hyper::body::Incoming; use hyper::http::Request; use hyper::Method; use hyper::StatusCode; -use std::path::PathBuf; 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 async fn build_response( req: Request, - directory: PathBuf, - content_encodings: Option>, - fallback_404: Option, + res_params: ResponseParams, ) -> 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::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), } } diff --git a/response/src/type_flyweight.rs b/response/src/type_flyweight.rs index ffa07cc..72122d1 100644 --- a/response/src/type_flyweight.rs +++ b/response/src/type_flyweight.rs @@ -1,6 +1,32 @@ 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, +} + +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, + } + } +}