From d8d5aae898e94b2017ad633e81ff357fc83be85d Mon Sep 17 00:00:00 2001 From: sigoden Date: Mon, 30 May 2022 11:22:28 +0800 Subject: [PATCH] feat: add mime and cache headers to response --- Cargo.lock | 20 +++++++++++++++++ Cargo.toml | 1 + src/index.html | 2 +- src/server.rs | 60 ++++++++++++++++++++++++++++++++++++++++---------- 4 files changed, 70 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 510b8a4..b311f77 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,7 @@ dependencies = [ "headers", "hyper", "log", + "mime_guess", "percent-encoding", "serde", "serde_json", @@ -636,6 +637,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.5.1" @@ -991,6 +1002,15 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index a19e352..47d94fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ simple_logger = "2.1.0" async_zip = "0.0.7" async-walkdir = "0.2.0" headers = "0.3.7" +mime_guess = "2.0.4" [profile.release] lto = true diff --git a/src/index.html b/src/index.html index 1009c16..19b6809 100644 --- a/src/index.html +++ b/src/index.html @@ -4,7 +4,7 @@ - Duf + Duf file server __STYLE__ diff --git a/src/server.rs b/src/server.rs index e88da23..c72fe42 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,7 +5,10 @@ use async_zip::write::{EntryOptions, ZipFileWriter}; use async_zip::Compression; use futures::stream::StreamExt; use futures::TryStreamExt; -use headers::{AccessControlAllowHeaders, AccessControlAllowOrigin, HeaderMapExt}; +use headers::{ + AccessControlAllowHeaders, AccessControlAllowOrigin, ContentType, ETag, HeaderMapExt, + IfModifiedSince, IfNoneMatch, LastModified, +}; use hyper::header::{HeaderValue, ACCEPT, CONTENT_TYPE, ORIGIN, RANGE, WWW_AUTHENTICATE}; use hyper::service::{make_service_fn, service_fn}; use hyper::{Body, Method, StatusCode}; @@ -121,7 +124,7 @@ impl InnerService { } self.handle_ls_dir(path.as_path(), true).await } else { - self.handle_send_file(path.as_path()).await + self.handle_send_file(&req, path.as_path()).await } } Err(_) => { @@ -234,11 +237,42 @@ impl InnerService { Ok(Response::new(body)) } - async fn handle_send_file(&self, path: &Path) -> BoxResult { - let file = fs::File::open(path).await?; + async fn handle_send_file(&self, req: &Request, path: &Path) -> BoxResult { + let (file, meta) = tokio::join!(fs::File::open(path), fs::metadata(path),); + let (file, meta) = (file?, meta?); + let mut res = Response::default(); + if let Ok(mtime) = meta.modified() { + let mtime_value = get_timestamp(&mtime); + let size = meta.len(); + let etag = format!(r#""{}-{}""#, mtime_value, size) + .parse::() + .unwrap(); + let last_modified = LastModified::from(mtime); + let fresh = { + // `If-None-Match` takes presedence over `If-Modified-Since`. + if let Some(if_none_match) = req.headers().typed_get::() { + !if_none_match.precondition_passes(&etag) + } else if let Some(if_modified_since) = req.headers().typed_get::() + { + !if_modified_since.is_modified(mtime) + } else { + false + } + }; + res.headers_mut().typed_insert(last_modified); + res.headers_mut().typed_insert(etag); + if fresh { + *res.status_mut() = StatusCode::NOT_MODIFIED; + return Ok(res); + } + } + if let Some(mime) = mime_guess::from_path(&path).first() { + res.headers_mut().typed_insert(ContentType::from(mime)); + } let stream = FramedRead::new(file, BytesCodec::new()); let body = Body::wrap_stream(stream); - Ok(Response::new(body)) + *res.body_mut() = body; + Ok(res) } fn send_index(&self, path: &Path, mut paths: Vec) -> BoxResult { @@ -254,7 +288,7 @@ impl InnerService { INDEX_HTML.replace("__STYLE__", &format!("", INDEX_CSS)); output = output.replace("__DATA__", &data); - Ok(hyper::Response::builder().body(output.into()).unwrap()) + Ok(Response::new(output.into())) } fn auth_guard(&self, req: &Request) -> BoxResult { @@ -311,7 +345,7 @@ struct IndexData { struct PathItem { path_type: PathType, name: String, - mtime: Option, + mtime: u64, size: Option, } @@ -336,11 +370,7 @@ async fn get_path_item>(path: P, base_path: P) -> BoxResult PathType::SymlinkFile, (false, false) => PathType::File, }; - let mtime = meta - .modified()? - .duration_since(SystemTime::UNIX_EPOCH) - .ok() - .map(|v| v.as_millis() as u64); + let mtime = get_timestamp(&meta.modified()?); let size = match path_type { PathType::Dir | PathType::SymlinkDir => None, PathType::File | PathType::SymlinkFile => Some(meta.len()), @@ -354,6 +384,12 @@ async fn get_path_item>(path: P, base_path: P) -> BoxResult u64 { + time.duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64 +} + fn normalize_path>(path: P) -> String { let path = path.as_ref().to_str().unwrap_or_default(); if cfg!(windows) {