index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2022-09-27 19:56:55.0 +00:00:00
committer Jose Quintana <joseluisquintana20@gmail.com> 2022-09-27 21:11:26.0 +00:00:00
commit
eee45f9de3121fc65fd4bceed89cfb59e4203091 [patch]
tree
97776a1eb472f8d86ba6a769f8a1bf11cbbcb874
parent
76fd7ea9fb86fe1b672767a461a960a76d64894f
download
eee45f9de3121fc65fd4bceed89cfb59e4203091.tar.gz

refactor: remove indirections on static file module



Diff

 src/compression_static.rs | 11 ++------
 src/static_files.rs       | 70 +++++++++++++++---------------------------------
 2 files changed, 26 insertions(+), 55 deletions(-)

diff --git a/src/compression_static.rs b/src/compression_static.rs
index 37782f0..041ac4e 100644
--- a/src/compression_static.rs
+++ b/src/compression_static.rs
@@ -1,16 +1,13 @@
use headers::{ContentCoding, HeaderMap, HeaderValue};
use std::{fs::Metadata, path::PathBuf, sync::Arc};
use std::{fs::Metadata, path::PathBuf};

use crate::{
    compression,
    static_files::{file_metadata, ArcPath},
};
use crate::{compression, static_files::file_metadata};

/// Search for the pre-compressed variant of the given file path.
pub async fn precompressed_variant(
    file_path: PathBuf,
    headers: &HeaderMap<HeaderValue>,
) -> Option<(ArcPath, Metadata, &str)> {
) -> Option<(PathBuf, Metadata, &str)> {
    let mut precompressed = None;

    tracing::trace!(
@@ -47,7 +44,7 @@ pub async fn precompressed_variant(
            tracing::trace!("pre-compressed file variant found, serving it directly");

            let encoding = if ext == "gz" { "gzip" } else { ext };
            precompressed = Some((ArcPath(Arc::new(filepath_precomp)), meta, encoding));
            precompressed = Some((filepath_precomp, meta, encoding));
        }

        // Note: In error case like "no such file or dir" the workflow just continues
diff --git a/src/static_files.rs b/src/static_files.rs
index 6ef4bd4..b6c65dd 100644
--- a/src/static_files.rs
+++ b/src/static_files.rs
@@ -17,7 +17,6 @@ use std::io;
use std::ops::Bound;
use std::path::{Component, PathBuf};
use std::pin::Pin;
use std::sync::Arc;
use std::task::Poll;
use std::{cmp, path::Path};
use tokio::fs::File as TkFile;
@@ -26,16 +25,6 @@ use tokio_util::io::poll_read_buf;

use crate::{compression_static, directory_listing, Result};

/// Arc `PathBuf` reference wrapper since Arc<PathBuf> doesn't implement AsRef<Path>.
#[derive(Clone, Debug)]
pub struct ArcPath(pub Arc<PathBuf>);

impl AsRef<Path> for ArcPath {
    fn as_ref(&self) -> &Path {
        (*self.0).as_ref()
    }
}

/// Defines all options needed by the static-files handler.
pub struct HandleOpts<'a> {
    pub method: &'a Method,
@@ -63,11 +52,10 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<(Response<Body>, bool),
    let headers_opt = opts.headers;
    let compression_static_opt = opts.compression_static;

    let base = Arc::<PathBuf>::new(opts.base_path.into());
    let file_path = sanitize_path(base.as_ref(), uri_path)?;
    let mut file_path = sanitize_path(opts.base_path, uri_path)?;

    let (file_path, meta, is_dir, precompressed_variant) =
        composed_file_metadata(file_path, headers_opt, compression_static_opt).await?;
        composed_file_metadata(&mut file_path, headers_opt, compression_static_opt).await?;

    // `is_precompressed` relates to `opts.compression_static` value
    let is_precompressed = precompressed_variant.is_some();
@@ -113,7 +101,7 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<(Response<Body>, bool),
    // Check if "directory listing" feature is enabled,
    // if current path is a valid directory and
    // if it does not contain an `index.html` file (if a proper auto index is generated)
    if opts.dir_listing && is_dir && !file_path.as_ref().exists() {
    if opts.dir_listing && is_dir && !file_path.exists() {
        let resp = directory_listing::auto_index(
            method,
            uri_path,
@@ -148,11 +136,11 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<(Response<Body>, bool),
/// Returns the final composed metadata information (tuple) containing
/// the Arc `PathBuf` reference wrapper for the current `file_path` with its file metadata
/// as well as its optional pre-compressed variant.
async fn composed_file_metadata(
    mut file_path: PathBuf,
    headers: &HeaderMap<HeaderValue>,
async fn composed_file_metadata<'a>(
    file_path: &'a mut PathBuf,
    headers: &'a HeaderMap<HeaderValue>,
    compression_static: bool,
) -> Result<(ArcPath, Metadata, bool, Option<(ArcPath, &str)>), StatusCode> {
) -> Result<(&'a PathBuf, Metadata, bool, Option<(PathBuf, &'a str)>), StatusCode> {
    // First pre-compressed variant check for the given file path
    let mut tried_precompressed = false;
    if compression_static {
@@ -160,7 +148,7 @@ async fn composed_file_metadata(
        if let Some((path, meta, ext)) =
            compression_static::precompressed_variant(file_path.clone(), headers).await
        {
            return Ok((ArcPath(Arc::new(file_path)), meta, false, Some((path, ext))));
            return Ok((file_path, meta, false, Some((path, ext))));
        }
    }

@@ -180,7 +168,7 @@ async fn composed_file_metadata(
                }
            }

            Ok((ArcPath(Arc::new(file_path)), meta, is_dir, None))
            Ok((file_path, meta, is_dir, None))
        }
        Err(err) => {
            // Second pre-compressed variant check for the given file path
@@ -188,7 +176,7 @@ async fn composed_file_metadata(
                if let Some((path, meta, ext)) =
                    compression_static::precompressed_variant(file_path.clone(), headers).await
                {
                    return Ok((ArcPath(Arc::new(file_path)), meta, false, Some((path, ext))));
                    return Ok((file_path, meta, false, Some((path, ext))));
                }
            }

@@ -220,35 +208,28 @@ pub async fn file_metadata(file_path: &Path) -> Result<(Metadata, bool), StatusC
/// the `meta` param  value will belong to the `path_precompressed` (precompressed file variant).
fn file_reply<'a>(
    headers: &'a HeaderMap<HeaderValue>,
    path: ArcPath,
    path: &'a PathBuf,
    meta: &'a Metadata,
    path_precompressed: Option<ArcPath>,
    path_precompressed: Option<PathBuf>,
) -> impl Future<Output = Result<Response<Body>, StatusCode>> + Send + 'a {
    let conditionals = get_conditional_headers(headers);

    let file_path = path_precompressed.unwrap_or_else(|| path.clone());

    TkFile::open(file_path).then(move |res| match res {
        Ok(file) => Either::Left(file_conditional(file, path, meta, conditionals)),
        Ok(file) => Either::Left(response_body(file, path, meta, conditionals)),
        Err(err) => {
            let status = match err.kind() {
                io::ErrorKind::NotFound => {
                    tracing::debug!(
                        "file can't be opened or not found: {:?}",
                        path.as_ref().display()
                    );
                    tracing::debug!("file can't be opened or not found: {:?}", path.display());
                    StatusCode::NOT_FOUND
                }
                io::ErrorKind::PermissionDenied => {
                    tracing::warn!("file permission denied: {:?}", path.as_ref().display());
                    tracing::warn!("file permission denied: {:?}", path.display());
                    StatusCode::FORBIDDEN
                }
                _ => {
                    tracing::error!(
                        "file open error (path={:?}): {} ",
                        path.as_ref().display(),
                        err
                    );
                    tracing::error!("file open error (path={:?}): {} ", path.display(), err);
                    StatusCode::INTERNAL_SERVER_ERROR
                }
            };
@@ -372,24 +353,15 @@ impl Conditionals {
    }
}

async fn file_conditional(
async fn response_body(
    file: TkFile,
    path: ArcPath,
    path: &PathBuf,
    meta: &Metadata,
    conditionals: Conditionals,
) -> Result<Response<Body>, StatusCode> {
    Ok(response_body(file, meta, path, conditionals))
}

fn response_body(
    file: TkFile,
    meta: &Metadata,
    path: ArcPath,
    conditionals: Conditionals,
) -> Response<Body> {
    let mut len = meta.len();
    let modified = meta.modified().ok().map(LastModified::from);
    match conditionals.check(modified) {
    let resp = match conditionals.check(modified) {
        Cond::NoBody(resp) => resp,
        Cond::WithBody(range) => {
            bytes_range(range, len)
@@ -431,7 +403,9 @@ fn response_body(
                    resp
                })
        }
    }
    };

    Ok(resp)
}

struct BadRange;