Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,3 @@ serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tokio-util = "0.7"
tokio = { version = "1.51", features = ["full"] }
tokio_stream = { version = "0.1" }
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ Which could produce a json object similar to the following:
"file_name": "demo",
"url_path": "/demo",
"size": 128,
"created_at": 1788035132686,
"accessed_at": 1792381141727,
"modified_at": 1792381141619
"created_at": "1788035132686",
"accessed_at": "1792381141727",
"modified_at": "1792381141619"
},
"entries": [
{
Expand All @@ -140,9 +140,9 @@ Which could produce a json object similar to the following:
"file_name": "index.html.zst",
"url_path": "/demo/index.html.zst",
"size": 304,
"created_at": 1788035132686,
"accessed_at": 1788035156555,
"modified_at": 1788035132686
"created_at": "1788035132686",
"accessed_at": "1788035156555",
"modified_at": "1788035132686"
},
{
"is_file": true,
Expand All @@ -151,14 +151,24 @@ Which could produce a json object similar to the following:
"file_name": "index.html",
"url_path": "/demo/index.html",
"size": 460,
"created_at": 1788035132686,
"accessed_at": 1788035156555,
"modified_at": 1788035132686
"created_at": "1788035132686",
"accessed_at": "1788035156555",
"modified_at": "1788035132686"
}
]
}
```

Json structs can be swapped for a more rust-specific struct:

```rust
use response::{EntryDetailsJson, FileDetailsJson, FileDetails};

let file_details_json: FileDetailsJson = ...

let file_details: Results<FileDetails, ParseIntError> = file_details_json::as_file_details();
```

## License

`file_server` is released under the BSD 3-Clause License.
2 changes: 1 addition & 1 deletion response/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "response"
version = "0.2.3"
version = "0.2.4"
edition = "2024"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
Empty file added response/README.md
Empty file.
2 changes: 2 additions & 0 deletions response/src/content_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ pub fn get_content_type(target_path: &Path) -> &str {
"gz" => "application/gzip",
"html" => HTML,
"ico" => "image/vnd.microsoft.icon",
"ics" => "text/calendar",
"jpeg" => "image/jpeg",
"jpg" => "image/jpeg",
"js" => "text/javascript; charset=utf-8",
"json" => "application/json; charset=utf-8",
"webmanifest" => "application/manifest+json",
"md" => "text/markdown",
"m3u8" => "application/x-mpegURL",
"m3u" => "application/x-mpegURL",
"midi" => "audio/midi",
Expand Down
42 changes: 16 additions & 26 deletions response/src/get_response.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::available_encodings::{add_extension, get_encodings};
use crate::available_encodings::get_encodings;
use crate::content_type::get_content_type;
use crate::last_resort_response;
use crate::range_response;
use crate::response_params::ResponseParams;
use crate::serialize_details;
use crate::utils_flyweight::{
BoxedResponse, NOT_FOUND_404, ResponseParams, get_url_path_from_request,
use crate::url_path_utils::{
BoxedResponse, NOT_FOUND_404, get_file_and_encoding, get_url_path_from_request,
};
use futures_util::TryStreamExt;
use http_body_util::{BodyExt, StreamBody};
Expand All @@ -13,15 +14,14 @@ use hyper::body::{Frame, Incoming};
use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE};
use hyper::http::{Request, Response};
use std::path::Path;
use tokio::fs;
use tokio::fs::File;
use tokio_util::io::ReaderStream;

pub async fn build_response(
req: Request<Incoming>,
res_params: ResponseParams,
) -> Result<BoxedResponse, hyper::http::Error> {
// check for a json details request
// should be on file
if let Some(res) = serialize_details::build_response(&req, &res_params).await {
return res;
};
Expand All @@ -31,15 +31,16 @@ pub async fn build_response(
return res;
}

// a humble get request
let encodings = get_encodings(&req, &res_params.available_encodings);
if let Some(res) = build_req_path_response(&req, &res_params.directory, &encodings).await {
if let Some(res) = build_response_from_url_path(&req, &res_params.directory, &encodings).await {
return res;
};

last_resort_response::build_response(StatusCode::NOT_FOUND, NOT_FOUND_404)
}

async fn build_req_path_response(
async fn build_response_from_url_path(
req: &Request<Incoming>,
directory: &Path,
encodings: &Vec<String>,
Expand All @@ -49,31 +50,20 @@ async fn build_req_path_response(
_ => return None,
};

let content_type = get_content_type(&filepath);
for enc in encodings {
let encoded_path = match add_extension(&filepath, enc) {
Some(enc_pth) => enc_pth,
_ => continue,
};

if let Some(res) = compose_response(&encoded_path, content_type, Some(enc)).await {
return Some(res);
}
}
let (file, encoding_opt) = match get_file_and_encoding(&filepath, encodings).await {
Some((f, e)) => (f, e),
_ => return None,
};

compose_response(&filepath, content_type, None).await
let content_type = get_content_type(&filepath);
compose_response(file, content_type, encoding_opt).await
}

async fn compose_response(
filepath: &Path,
file: File,
content_type: &str,
content_encoding: Option<&str>,
content_encoding: Option<String>,
) -> Option<Result<BoxedResponse, hyper::http::Error>> {
let file = match fs::File::open(filepath).await {
Ok(m) => m,
_ => return None,
};

let metadata = match file.metadata().await {
Ok(m) => m,
_ => return None,
Expand Down
76 changes: 42 additions & 34 deletions response/src/head_response.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::available_encodings::{add_extension, get_encodings};
use crate::content_type::get_content_type;
use crate::last_resort_response;
use crate::utils_flyweight::{
BoxedResponse, NOT_FOUND_404, ResponseParams, get_url_path_from_request,
};
use crate::response_params::ResponseParams;
use crate::url_path_utils::{BoxedResponse, NOT_FOUND_404, get_url_path_from_request};
use http_body_util::{BodyExt, Full};
use hyper::StatusCode;
use hyper::body::Incoming;
Expand All @@ -17,60 +16,69 @@ pub async fn build_response(
res_params: ResponseParams,
) -> Result<BoxedResponse, hyper::http::Error> {
if let Some(filepath) = get_url_path_from_request(&req, &res_params.directory, true) {
let content_type = get_content_type(&filepath);
let encodings = get_encodings(&req, &res_params.available_encodings);
if let Some((size, encoding_opt)) = get_size_and_encoding(&filepath, encodings).await {
let content_type = get_content_type(&filepath);
return compose_response(content_type, size, encoding_opt).await;
};
};

// try encoded
for encoding in encodings {
let encoded_path = match add_extension(&filepath, &encoding) {
Some(enc_pth) => enc_pth,
_ => continue,
};
last_resort_response::build_response(StatusCode::NOT_FOUND, NOT_FOUND_404)
}

if let Some(res) = compose_response(&encoded_path, content_type, Some(&encoding)).await
{
return res;
}
}
// just use "file" and grab metadata instead of more code?

// try base
if let Some(res) = compose_response(&filepath, content_type, None).await {
return res;
async fn get_size_and_encoding(
filepath: &Path,
encodings: Vec<String>,
) -> Option<(u64, Option<String>)> {
for encoding in encodings {
let encoded_path = match add_extension(&filepath, &encoding) {
Some(enc_pth) => enc_pth,
_ => continue,
};

if let Some(size) = get_file_size(&encoded_path).await {
return Some((size, Some(encoding.to_string())));
}
};
}

last_resort_response::build_response(StatusCode::NOT_FOUND, NOT_FOUND_404)
match get_file_size(filepath).await {
Some(size) => Some((size, None)),
_ => None,
}
}

async fn compose_response(
filepath: &Path,
content_type: &str,
content_encoding: Option<&str>,
) -> Option<Result<BoxedResponse, hyper::http::Error>> {
async fn get_file_size(filepath: &Path) -> Option<u64> {
let metadata = match fs::metadata(filepath).await {
Ok(m) => m,
_ => return None,
};

if !metadata.is_file() {
return None;
match metadata.is_file() {
true => Some(metadata.len()),
_ => None,
}
}

async fn compose_response(
content_type: &str,
size: u64,
content_encoding: Option<String>,
) -> Result<BoxedResponse, hyper::http::Error> {
let mut builder = Response::builder()
.status(StatusCode::OK)
.header(CONTENT_TYPE, content_type)
.header(ACCEPT_RANGES, "bytes")
.header(CONTENT_LENGTH, metadata.len());
.header(CONTENT_LENGTH, size);

if let Some(enc) = content_encoding {
builder = builder.header(CONTENT_ENCODING, enc);
}

Some(
builder.body(
Full::new(bytes::Bytes::new())
.map_err(|e| match e {})
.boxed(),
),
builder.body(
Full::new(bytes::Bytes::new())
.map_err(|e| match e {})
.boxed(),
)
}
2 changes: 1 addition & 1 deletion response/src/last_resort_response.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::content_type::HTML;
use crate::utils_flyweight::BoxedResponse;
use crate::url_path_utils::BoxedResponse;
use bytes;
use http_body_util::{BodyExt, Full};
use hyper::StatusCode;
Expand Down
10 changes: 6 additions & 4 deletions response/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ mod get_response;
mod head_response;
mod last_resort_response;
mod range_response;
mod response_params;
mod responses;
mod serialize_details;
mod utils_flyweight;
mod url_path_utils;

pub use crate::responses::compose_response;
pub use crate::serialize_details::{EntryDetails, FileDetails};
pub use crate::utils_flyweight::{BoxedResponse, ResponseParams, get_url_path_from_request};
pub use response_params::ResponseParams;
pub use responses::compose_response;
pub use serialize_details::{EntryDetailsJson, FileDetails, FileDetailsJson};
pub use url_path_utils::{BoxedResponse, get_url_path_from_request};
Loading
Loading