]> OzVa Git service - ozva-cloud/commitdiff
feat: add mime and cache headers to response
authorsigoden <sigoden@gmail.com>
Mon, 30 May 2022 03:22:28 +0000 (11:22 +0800)
committersigoden <sigoden@gmail.com>
Mon, 30 May 2022 03:22:28 +0000 (11:22 +0800)
Cargo.lock
Cargo.toml
src/index.html
src/server.rs

index 510b8a4bb030eb2f21aad3830fb573d6892cec62..b311f77028b81681b96c15b79ff58662cc1125de 100644 (file)
@@ -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"
index a19e352bcdcf08ed1d5cb129d4e377ac016d6ec1..47d94fd579b350bd49ae3b60b2e5ec2fb93bc12b 100644 (file)
@@ -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
index 1009c16faf835e2ccdbc4d1d463761d8748844e2..19b680928693da13cdf703bfda6b8182578893d3 100644 (file)
@@ -4,7 +4,7 @@
 <head>
   <meta charset="utf-8" />
   <meta name="viewport" content="width=device-width" />
-  <title>Duf</title>
+  <title>Duf file server</title>
   __STYLE__
 </head>
 <body>
index e88da23b3a7d9ee838694d227ce827c8c56c3bfd..c72fe42b1b900a35022e8a6dd617ee207e1b99c4 100644 (file)
@@ -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<Response> {
-        let file = fs::File::open(path).await?;
+    async fn handle_send_file(&self, req: &Request, path: &Path) -> BoxResult<Response> {
+        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::<ETag>()
+                .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::<IfNoneMatch>() {
+                    !if_none_match.precondition_passes(&etag)
+                } else if let Some(if_modified_since) = req.headers().typed_get::<IfModifiedSince>()
+                {
+                    !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<PathItem>) -> BoxResult<Response> {
@@ -254,7 +288,7 @@ impl InnerService {
             INDEX_HTML.replace("__STYLE__", &format!("<style>\n{}</style>", 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<bool> {
@@ -311,7 +345,7 @@ struct IndexData {
 struct PathItem {
     path_type: PathType,
     name: String,
-    mtime: Option<u64>,
+    mtime: u64,
     size: Option<u64>,
 }
 
@@ -336,11 +370,7 @@ async fn get_path_item<P: AsRef<Path>>(path: P, base_path: P) -> BoxResult<PathI
         (true, false) => 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<P: AsRef<Path>>(path: P, base_path: P) -> BoxResult<PathI
     })
 }
 
+fn get_timestamp(time: &SystemTime) -> u64 {
+    time.duration_since(SystemTime::UNIX_EPOCH)
+        .unwrap()
+        .as_millis() as u64
+}
+
 fn normalize_path<P: AsRef<Path>>(path: P) -> String {
     let path = path.as_ref().to_str().unwrap_or_default();
     if cfg!(windows) {