Skip to content

Commit 70a76ed

Browse files
committed
refactor: optimize root path of static file module
1 parent 8128b17 commit 70a76ed

File tree

3 files changed

+54
-44
lines changed

3 files changed

+54
-44
lines changed

src/handler.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{Error, Result};
77

88
/// It defines options for a request handler.
99
pub struct RequestHandlerOpts {
10-
pub root_dir: PathBuf,
10+
pub root_dir: Arc<PathBuf>,
1111
pub compression: bool,
1212
pub dir_listing: bool,
1313
pub cors: Option<Arc<cors::Configured>>,
@@ -28,7 +28,7 @@ impl RequestHandler {
2828
let method = req.method();
2929
let headers = req.headers();
3030

31-
let root_dir = self.opts.root_dir.as_path();
31+
let root_dir = self.opts.root_dir.as_ref();
3232
let uri_path = req.uri().path();
3333
let dir_listing = self.opts.dir_listing;
3434

src/server.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use hyper::server::conn::AddrIncoming;
22
use hyper::server::Server as HyperServer;
33
use listenfd::ListenFd;
44
use std::net::{IpAddr, SocketAddr, TcpListener};
5+
use std::sync::Arc;
56
use structopt::StructOpt;
67

78
use crate::handler::{RequestHandler, RequestHandlerOpts};
@@ -77,7 +78,7 @@ impl Server {
7778
}
7879

7980
// Check for a valid root directory
80-
let root_dir = helpers::get_valid_dirpath(&opts.root)?;
81+
let root_dir = Arc::new(helpers::get_valid_dirpath(&opts.root)?);
8182

8283
// Custom error pages content
8384
error_page::PAGE_404

src/static_files.rs

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use std::io;
1818
use std::ops::Bound;
1919
use std::path::PathBuf;
2020
use std::pin::Pin;
21+
use std::sync::Arc;
2122
use std::task::Poll;
2223
use std::time::{SystemTime, UNIX_EPOCH};
2324
use std::{cmp, path::Path};
@@ -27,12 +28,22 @@ use tokio_util::io::poll_read_buf;
2728

2829
use crate::Result;
2930

31+
/// Arc `PathBuf` reference wrapper since Arc<PathBuf> doesn't implement AsRef<Path>.
32+
#[derive(Clone, Debug)]
33+
pub struct ArcPath(pub Arc<PathBuf>);
34+
35+
impl AsRef<Path> for ArcPath {
36+
fn as_ref(&self) -> &Path {
37+
(*self.0).as_ref()
38+
}
39+
}
40+
3041
/// Entry point to handle incoming requests which map to specific files
3142
/// on file system and return a file response.
3243
pub async fn handle(
3344
method: &Method,
3445
headers: &HeaderMap<HeaderValue>,
35-
base: &Path,
46+
path: impl Into<PathBuf>,
3647
uri_path: &str,
3748
dir_listing: bool,
3849
) -> Result<Response<Body>, StatusCode> {
@@ -41,13 +52,14 @@ pub async fn handle(
4152
return Err(StatusCode::METHOD_NOT_ALLOWED);
4253
}
4354

44-
let (filepath, meta, auto_index) = path_from_tail(base.to_owned(), uri_path).await?;
55+
let base = Arc::new(path.into());
56+
let (filepath, meta, auto_index) = path_from_tail(base, uri_path).await?;
4557

4658
// Directory listing
4759
// 1. Check if "directory listing" feature is enabled,
4860
// if current path is a valid directory and
4961
// if it does not contain an `index.html` file
50-
if dir_listing && auto_index && !filepath.exists() {
62+
if dir_listing && auto_index && !filepath.as_ref().exists() {
5163
// Redirect if current path does not end with a slash char
5264
if !uri_path.ends_with('/') {
5365
let uri = [uri_path, "/"].concat();
@@ -67,19 +79,19 @@ pub async fn handle(
6779
return Ok(resp);
6880
}
6981

70-
return directory_listing(method, uri_path, &filepath).await;
82+
return directory_listing(method, uri_path, filepath.as_ref()).await;
7183
}
7284

73-
file_reply(headers, (filepath, meta, auto_index)).await
85+
file_reply(headers, (filepath, &meta, auto_index)).await
7486
}
7587

7688
/// Convert an incoming uri into a valid and sanitized path then returns a tuple
7789
// with the path as well as its file metadata and an auto index check if it's a directory.
7890
fn path_from_tail(
79-
base: PathBuf,
91+
base: Arc<PathBuf>,
8092
tail: &str,
81-
) -> impl Future<Output = Result<(PathBuf, Metadata, bool), StatusCode>> + Send {
82-
future::ready(sanitize_path(base, tail)).and_then(|mut buf| async {
93+
) -> impl Future<Output = Result<(ArcPath, Metadata, bool), StatusCode>> + Send {
94+
future::ready(sanitize_path(base.as_ref(), tail)).and_then(|mut buf| async {
8395
match tokio::fs::metadata(&buf).await {
8496
Ok(meta) => {
8597
let mut auto_index = false;
@@ -89,7 +101,7 @@ fn path_from_tail(
89101
auto_index = true;
90102
}
91103
tracing::trace!("dir: {:?}", buf);
92-
Ok((buf, meta, auto_index))
104+
Ok((ArcPath(Arc::new(buf)), meta, auto_index))
93105
}
94106
Err(err) => {
95107
tracing::debug!("file not found: {:?}", err);
@@ -273,26 +285,30 @@ fn parse_last_modified(modified: SystemTime) -> Result<time::Tm, Box<dyn std::er
273285
}
274286

275287
/// Reply with a file content.
276-
fn file_reply(
277-
headers: &HeaderMap<HeaderValue>,
278-
res: (PathBuf, Metadata, bool),
279-
) -> impl Future<Output = Result<Response<Body>, StatusCode>> + Send {
288+
fn file_reply<'a>(
289+
headers: &'a HeaderMap<HeaderValue>,
290+
res: (ArcPath, &'a Metadata, bool),
291+
) -> impl Future<Output = Result<Response<Body>, StatusCode>> + Send + 'a {
280292
let (path, meta, auto_index) = res;
281293
let conditionals = get_conditional_headers(headers);
282294
TkFile::open(path.clone()).then(move |res| match res {
283295
Ok(f) => Either::Left(file_conditional(f, path, meta, auto_index, conditionals)),
284296
Err(err) => {
285297
let status = match err.kind() {
286298
io::ErrorKind::NotFound => {
287-
tracing::debug!("file not found: {:?}", path.display());
299+
tracing::debug!("file not found: {:?}", path.as_ref().display());
288300
StatusCode::NOT_FOUND
289301
}
290302
io::ErrorKind::PermissionDenied => {
291-
tracing::warn!("file permission denied: {:?}", path.display());
303+
tracing::warn!("file permission denied: {:?}", path.as_ref().display());
292304
StatusCode::FORBIDDEN
293305
}
294306
_ => {
295-
tracing::error!("file open error (path={:?}): {} ", path.display(), err);
307+
tracing::error!(
308+
"file open error (path={:?}): {} ",
309+
path.as_ref().display(),
310+
err
311+
);
296312
StatusCode::INTERNAL_SERVER_ERROR
297313
}
298314
};
@@ -315,7 +331,8 @@ fn get_conditional_headers(header_list: &HeaderMap<HeaderValue>) -> Conditionals
315331
}
316332
}
317333

318-
fn sanitize_path(mut buf: PathBuf, tail: &str) -> Result<PathBuf, StatusCode> {
334+
fn sanitize_path(base: impl AsRef<Path>, tail: &str) -> Result<PathBuf, StatusCode> {
335+
let mut buf = PathBuf::from(base.as_ref());
319336
let p = match percent_decode_str(tail).decode_utf8() {
320337
Ok(p) => p,
321338
Err(err) => {
@@ -400,38 +417,30 @@ impl Conditionals {
400417
}
401418
}
402419

403-
fn file_conditional(
420+
async fn file_conditional(
404421
f: TkFile,
405-
path: PathBuf,
406-
meta: Metadata,
422+
path: ArcPath,
423+
meta: &Metadata,
407424
auto_index: bool,
408425
conditionals: Conditionals,
409-
) -> impl Future<Output = Result<Response<Body>, StatusCode>> + Send {
410-
file_metadata(f, meta, auto_index)
411-
.map_ok(|(file, meta)| response_body(file, &meta, path, conditionals))
412-
}
413-
414-
async fn file_metadata(
415-
f: TkFile,
416-
meta: Metadata,
417-
auto_index: bool,
418-
) -> Result<(TkFile, Metadata), StatusCode> {
426+
) -> Result<Response<Body>, StatusCode> {
419427
if !auto_index {
420-
return Ok((f, meta));
421-
}
422-
match f.metadata().await {
423-
Ok(meta) => Ok((f, meta)),
424-
Err(err) => {
425-
tracing::debug!("file metadata error: {}", err);
426-
Err(StatusCode::INTERNAL_SERVER_ERROR)
428+
Ok(response_body(f, meta, path, conditionals))
429+
} else {
430+
match f.metadata().await {
431+
Ok(meta) => Ok(response_body(f, &meta, path, conditionals)),
432+
Err(err) => {
433+
tracing::debug!("file metadata error: {}", err);
434+
Err(StatusCode::INTERNAL_SERVER_ERROR)
435+
}
427436
}
428437
}
429438
}
430439

431440
fn response_body(
432441
file: TkFile,
433442
meta: &Metadata,
434-
path: PathBuf,
443+
path: ArcPath,
435444
conditionals: Conditionals,
436445
) -> Response<Body> {
437446
let mut len = meta.len();
@@ -623,14 +632,14 @@ mod tests {
623632
}
624633

625634
assert_eq!(
626-
sanitize_path(base.into(), "/foo.html").unwrap(),
635+
sanitize_path(base, "/foo.html").unwrap(),
627636
p("/var/www/foo.html")
628637
);
629638

630639
// bad paths
631-
sanitize_path(base.into(), "/../foo.html").expect_err("dot dot");
640+
sanitize_path(base, "/../foo.html").expect_err("dot dot");
632641

633-
sanitize_path(base.into(), "/C:\\/foo.html").expect_err("C:\\");
642+
sanitize_path(base, "/C:\\/foo.html").expect_err("C:\\");
634643
}
635644

636645
#[test]

0 commit comments

Comments
 (0)