mirror of
https://github.com/sigoden/dufs.git
synced 2026-04-09 00:59:02 +03:00
feat: upgrade to hyper 1.0 (#321)
This commit is contained in:
103
src/server.rs
103
src/server.rs
@@ -1,29 +1,32 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use crate::auth::{www_authenticate, AccessPaths, AccessPerm};
|
||||
use crate::streamer::Streamer;
|
||||
use crate::http_utils::{body_full, IncomingStream, LengthLimitedStream};
|
||||
use crate::utils::{
|
||||
decode_uri, encode_uri, get_file_mtime_and_mode, get_file_name, glob, try_get_file_name,
|
||||
};
|
||||
use crate::Args;
|
||||
use anyhow::{anyhow, Result};
|
||||
use walkdir::WalkDir;
|
||||
use xml::escape::escape_str_pcdata;
|
||||
|
||||
use async_zip::tokio::write::ZipFileWriter;
|
||||
use async_zip::{Compression, ZipDateTime, ZipEntryBuilder};
|
||||
use anyhow::{anyhow, Result};
|
||||
use async_zip::{tokio::write::ZipFileWriter, Compression, ZipDateTime, ZipEntryBuilder};
|
||||
use bytes::Bytes;
|
||||
use chrono::{LocalResult, TimeZone, Utc};
|
||||
use futures::TryStreamExt;
|
||||
use futures_util::{pin_mut, TryStreamExt};
|
||||
use headers::{
|
||||
AcceptRanges, AccessControlAllowCredentials, AccessControlAllowOrigin, CacheControl,
|
||||
ContentLength, ContentType, ETag, HeaderMap, HeaderMapExt, IfModifiedSince, IfNoneMatch,
|
||||
IfRange, LastModified, Range,
|
||||
};
|
||||
use hyper::header::{
|
||||
HeaderValue, AUTHORIZATION, CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_RANGE, CONTENT_TYPE,
|
||||
RANGE,
|
||||
use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
|
||||
use hyper::body::Frame;
|
||||
use hyper::{
|
||||
body::Incoming,
|
||||
header::{
|
||||
HeaderValue, AUTHORIZATION, CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_RANGE,
|
||||
CONTENT_TYPE, RANGE,
|
||||
},
|
||||
Method, StatusCode, Uri,
|
||||
};
|
||||
use hyper::{Body, Method, StatusCode, Uri};
|
||||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::cmp::Ordering;
|
||||
@@ -39,11 +42,13 @@ use tokio::fs::File;
|
||||
use tokio::io::{AsyncReadExt, AsyncSeekExt, AsyncWrite};
|
||||
use tokio::{fs, io};
|
||||
use tokio_util::compat::FuturesAsyncWriteCompatExt;
|
||||
use tokio_util::io::StreamReader;
|
||||
use tokio_util::io::{ReaderStream, StreamReader};
|
||||
use uuid::Uuid;
|
||||
use walkdir::WalkDir;
|
||||
use xml::escape::escape_str_pcdata;
|
||||
|
||||
pub type Request = hyper::Request<Body>;
|
||||
pub type Response = hyper::Response<Body>;
|
||||
pub type Request = hyper::Request<Incoming>;
|
||||
pub type Response = hyper::Response<BoxBody<Bytes, anyhow::Error>>;
|
||||
|
||||
const INDEX_HTML: &str = include_str!("../assets/index.html");
|
||||
const INDEX_CSS: &str = include_str!("../assets/index.css");
|
||||
@@ -54,7 +59,7 @@ const BUF_SIZE: usize = 65536;
|
||||
const TEXT_MAX_SIZE: u64 = 4194304; // 4M
|
||||
|
||||
pub struct Server {
|
||||
args: Arc<Args>,
|
||||
args: Args,
|
||||
assets_prefix: String,
|
||||
html: Cow<'static, str>,
|
||||
single_file_req_paths: Vec<String>,
|
||||
@@ -62,7 +67,7 @@ pub struct Server {
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn init(args: Arc<Args>, running: Arc<AtomicBool>) -> Result<Self> {
|
||||
pub fn init(args: Args, running: Arc<AtomicBool>) -> Result<Self> {
|
||||
let assets_prefix = format!("{}__dufs_v{}_", args.uri_prefix, env!("CARGO_PKG_VERSION"));
|
||||
let single_file_req_paths = if args.path_is_file {
|
||||
vec![
|
||||
@@ -365,7 +370,7 @@ impl Server {
|
||||
status_forbid(&mut res);
|
||||
} else if !is_miss {
|
||||
*res.status_mut() = StatusCode::METHOD_NOT_ALLOWED;
|
||||
*res.body_mut() = Body::from("Already exists");
|
||||
*res.body_mut() = body_full("Already exists");
|
||||
} else {
|
||||
self.handle_mkcol(path, &mut res).await?;
|
||||
}
|
||||
@@ -411,7 +416,7 @@ impl Server {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn handle_upload(&self, path: &Path, mut req: Request, res: &mut Response) -> Result<()> {
|
||||
async fn handle_upload(&self, path: &Path, req: Request, res: &mut Response) -> Result<()> {
|
||||
ensure_path_parent(path).await?;
|
||||
|
||||
let mut file = match fs::File::create(&path).await {
|
||||
@@ -422,13 +427,12 @@ impl Server {
|
||||
}
|
||||
};
|
||||
|
||||
let body_with_io_error = req
|
||||
.body_mut()
|
||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err));
|
||||
let stream = IncomingStream::new(req.into_body());
|
||||
|
||||
let body_with_io_error = stream.map_err(|err| io::Error::new(io::ErrorKind::Other, err));
|
||||
let body_reader = StreamReader::new(body_with_io_error);
|
||||
|
||||
futures::pin_mut!(body_reader);
|
||||
pin_mut!(body_reader);
|
||||
|
||||
let ret = io::copy(&mut body_reader, &mut file).await;
|
||||
if ret.is_err() {
|
||||
@@ -596,8 +600,14 @@ impl Server {
|
||||
error!("Failed to zip {}, {}", path.display(), e);
|
||||
}
|
||||
});
|
||||
let reader = Streamer::new(reader, BUF_SIZE);
|
||||
*res.body_mut() = Body::wrap_stream(reader.into_stream());
|
||||
let reader_stream = ReaderStream::new(reader);
|
||||
let stream_body = StreamBody::new(
|
||||
reader_stream
|
||||
.map_ok(Frame::data)
|
||||
.map_err(|err| anyhow!("{err}")),
|
||||
);
|
||||
let boxed_body = stream_body.boxed();
|
||||
*res.body_mut() = boxed_body;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -660,21 +670,21 @@ impl Server {
|
||||
}
|
||||
None => match name {
|
||||
"index.js" => {
|
||||
*res.body_mut() = Body::from(INDEX_JS);
|
||||
*res.body_mut() = body_full(INDEX_JS);
|
||||
res.headers_mut().insert(
|
||||
"content-type",
|
||||
HeaderValue::from_static("application/javascript; charset=UTF-8"),
|
||||
);
|
||||
}
|
||||
"index.css" => {
|
||||
*res.body_mut() = Body::from(INDEX_CSS);
|
||||
*res.body_mut() = body_full(INDEX_CSS);
|
||||
res.headers_mut().insert(
|
||||
"content-type",
|
||||
HeaderValue::from_static("text/css; charset=UTF-8"),
|
||||
);
|
||||
}
|
||||
"favicon.ico" => {
|
||||
*res.body_mut() = Body::from(FAVICON_ICO);
|
||||
*res.body_mut() = body_full(FAVICON_ICO);
|
||||
res.headers_mut()
|
||||
.insert("content-type", HeaderValue::from_static("image/x-icon"));
|
||||
}
|
||||
@@ -761,18 +771,24 @@ impl Server {
|
||||
&& file.seek(SeekFrom::Start(range.start)).await.is_ok()
|
||||
{
|
||||
let end = range.end.unwrap_or(size - 1).min(size - 1);
|
||||
let part_size = end - range.start + 1;
|
||||
let reader = Streamer::new(file, BUF_SIZE);
|
||||
let range_size = end - range.start + 1;
|
||||
*res.status_mut() = StatusCode::PARTIAL_CONTENT;
|
||||
let content_range = format!("bytes {}-{}/{}", range.start, end, size);
|
||||
res.headers_mut()
|
||||
.insert(CONTENT_RANGE, content_range.parse()?);
|
||||
res.headers_mut()
|
||||
.insert(CONTENT_LENGTH, format!("{part_size}").parse()?);
|
||||
.insert(CONTENT_LENGTH, format!("{range_size}").parse()?);
|
||||
if head_only {
|
||||
return Ok(());
|
||||
}
|
||||
*res.body_mut() = Body::wrap_stream(reader.into_stream_sized(part_size));
|
||||
|
||||
let stream_body = StreamBody::new(
|
||||
LengthLimitedStream::new(file, range_size as usize)
|
||||
.map_ok(Frame::data)
|
||||
.map_err(|err| anyhow!("{err}")),
|
||||
);
|
||||
let boxed_body = stream_body.boxed();
|
||||
*res.body_mut() = boxed_body;
|
||||
} else {
|
||||
*res.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
|
||||
res.headers_mut()
|
||||
@@ -784,8 +800,15 @@ impl Server {
|
||||
if head_only {
|
||||
return Ok(());
|
||||
}
|
||||
let reader = Streamer::new(file, BUF_SIZE);
|
||||
*res.body_mut() = Body::wrap_stream(reader.into_stream());
|
||||
|
||||
let reader_stream = ReaderStream::new(file);
|
||||
let stream_body = StreamBody::new(
|
||||
reader_stream
|
||||
.map_ok(Frame::data)
|
||||
.map_err(|err| anyhow!("{err}")),
|
||||
);
|
||||
let boxed_body = stream_body.boxed();
|
||||
*res.body_mut() = boxed_body;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -828,7 +851,7 @@ impl Server {
|
||||
if head_only {
|
||||
return Ok(());
|
||||
}
|
||||
*res.body_mut() = output.into();
|
||||
*res.body_mut() = body_full(output);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -943,7 +966,7 @@ impl Server {
|
||||
res.headers_mut()
|
||||
.insert("lock-token", format!("<{token}>").parse()?);
|
||||
|
||||
*res.body_mut() = Body::from(format!(
|
||||
*res.body_mut() = body_full(format!(
|
||||
r#"<?xml version="1.0" encoding="utf-8"?>
|
||||
<D:prop xmlns:D="DAV:"><D:lockdiscovery><D:activelock>
|
||||
<D:locktoken><D:href>{token}</D:href></D:locktoken>
|
||||
@@ -1014,7 +1037,7 @@ impl Server {
|
||||
.typed_insert(ContentType::from(mime_guess::mime::TEXT_HTML_UTF_8));
|
||||
res.headers_mut()
|
||||
.typed_insert(ContentLength(output.as_bytes().len() as u64));
|
||||
*res.body_mut() = output.into();
|
||||
*res.body_mut() = body_full(output);
|
||||
if head_only {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -1060,7 +1083,7 @@ impl Server {
|
||||
if head_only {
|
||||
return Ok(());
|
||||
}
|
||||
*res.body_mut() = output.into();
|
||||
*res.body_mut() = body_full(output);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1419,7 +1442,7 @@ fn res_multistatus(res: &mut Response, content: &str) {
|
||||
"content-type",
|
||||
HeaderValue::from_static("application/xml; charset=utf-8"),
|
||||
);
|
||||
*res.body_mut() = Body::from(format!(
|
||||
*res.body_mut() = body_full(format!(
|
||||
r#"<?xml version="1.0" encoding="utf-8" ?>
|
||||
<D:multistatus xmlns:D="DAV:">
|
||||
{content}
|
||||
@@ -1539,12 +1562,12 @@ fn parse_range(headers: &HeaderMap<HeaderValue>) -> Option<RangeValue> {
|
||||
|
||||
fn status_forbid(res: &mut Response) {
|
||||
*res.status_mut() = StatusCode::FORBIDDEN;
|
||||
*res.body_mut() = Body::from("Forbidden");
|
||||
*res.body_mut() = body_full("Forbidden");
|
||||
}
|
||||
|
||||
fn status_not_found(res: &mut Response) {
|
||||
*res.status_mut() = StatusCode::NOT_FOUND;
|
||||
*res.body_mut() = Body::from("Not Found");
|
||||
*res.body_mut() = body_full("Not Found");
|
||||
}
|
||||
|
||||
fn status_no_content(res: &mut Response) {
|
||||
|
||||
Reference in New Issue
Block a user