feat: add mime and cache headers to response
This commit is contained in:
parent
a263d18963
commit
d8d5aae898
4 changed files with 70 additions and 13 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
@ -306,6 +306,7 @@ dependencies = [
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
"log",
|
"log",
|
||||||
|
"mime_guess",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -636,6 +637,16 @@ version = "0.3.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
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]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -991,6 +1002,15 @@ version = "1.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
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]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
|
|
@ -26,6 +26,7 @@ simple_logger = "2.1.0"
|
||||||
async_zip = "0.0.7"
|
async_zip = "0.0.7"
|
||||||
async-walkdir = "0.2.0"
|
async-walkdir = "0.2.0"
|
||||||
headers = "0.3.7"
|
headers = "0.3.7"
|
||||||
|
mime_guess = "2.0.4"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width" />
|
<meta name="viewport" content="width=device-width" />
|
||||||
<title>Duf</title>
|
<title>Duf file server</title>
|
||||||
__STYLE__
|
__STYLE__
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -5,7 +5,10 @@ use async_zip::write::{EntryOptions, ZipFileWriter};
|
||||||
use async_zip::Compression;
|
use async_zip::Compression;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use futures::TryStreamExt;
|
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::header::{HeaderValue, ACCEPT, CONTENT_TYPE, ORIGIN, RANGE, WWW_AUTHENTICATE};
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
use hyper::{Body, Method, StatusCode};
|
use hyper::{Body, Method, StatusCode};
|
||||||
|
@ -121,7 +124,7 @@ impl InnerService {
|
||||||
}
|
}
|
||||||
self.handle_ls_dir(path.as_path(), true).await
|
self.handle_ls_dir(path.as_path(), true).await
|
||||||
} else {
|
} else {
|
||||||
self.handle_send_file(path.as_path()).await
|
self.handle_send_file(&req, path.as_path()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
@ -234,11 +237,42 @@ impl InnerService {
|
||||||
Ok(Response::new(body))
|
Ok(Response::new(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_send_file(&self, path: &Path) -> BoxResult<Response> {
|
async fn handle_send_file(&self, req: &Request, path: &Path) -> BoxResult<Response> {
|
||||||
let file = fs::File::open(path).await?;
|
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 stream = FramedRead::new(file, BytesCodec::new());
|
||||||
let body = Body::wrap_stream(stream);
|
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> {
|
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));
|
INDEX_HTML.replace("__STYLE__", &format!("<style>\n{}</style>", INDEX_CSS));
|
||||||
output = output.replace("__DATA__", &data);
|
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> {
|
fn auth_guard(&self, req: &Request) -> BoxResult<bool> {
|
||||||
|
@ -311,7 +345,7 @@ struct IndexData {
|
||||||
struct PathItem {
|
struct PathItem {
|
||||||
path_type: PathType,
|
path_type: PathType,
|
||||||
name: String,
|
name: String,
|
||||||
mtime: Option<u64>,
|
mtime: u64,
|
||||||
size: Option<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,
|
(true, false) => PathType::SymlinkFile,
|
||||||
(false, false) => PathType::File,
|
(false, false) => PathType::File,
|
||||||
};
|
};
|
||||||
let mtime = meta
|
let mtime = get_timestamp(&meta.modified()?);
|
||||||
.modified()?
|
|
||||||
.duration_since(SystemTime::UNIX_EPOCH)
|
|
||||||
.ok()
|
|
||||||
.map(|v| v.as_millis() as u64);
|
|
||||||
let size = match path_type {
|
let size = match path_type {
|
||||||
PathType::Dir | PathType::SymlinkDir => None,
|
PathType::Dir | PathType::SymlinkDir => None,
|
||||||
PathType::File | PathType::SymlinkFile => Some(meta.len()),
|
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 {
|
fn normalize_path<P: AsRef<Path>>(path: P) -> String {
|
||||||
let path = path.as_ref().to_str().unwrap_or_default();
|
let path = path.as_ref().to_str().unwrap_or_default();
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
|
|
Loading…
Reference in a new issue