index : static-web-server.git

ascending towards madness

author Philipp Hartenfeller <34486873+phartenfeller@users.noreply.github.com> 2022-08-13 20:53:56.0 +00:00:00
committer GitHub <noreply@github.com> 2022-08-13 20:53:56.0 +00:00:00
commit
904c3cb20ba2922157425a33befc44947c78be6c [patch]
tree
73a7be06b96f9cf30c787dd01b2a559691cb4bea
parent
6840d0fc21a4f5d742c6435987b40ecdf00f5d01
download
904c3cb20ba2922157425a33befc44947c78be6c.tar.gz

New flag to make trailing slash redirect optional (#131)

* added redirect-trailing-slash flag
* add default option to test config
* fixed existing tests
* added tests
* added flag to  docs
* refactor: grouping static-files handle parameters into a new type
* implemented change requests

Co-authored-by: Jose Quintana <joseluisquintana20@gmail.com>

Diff

 docker/public/assets/index.html                      |  18 +-
 docs/content/configuration/command-line-arguments.md |   3 +-
 docs/content/configuration/config-file.md            |   3 +-
 docs/content/configuration/environment-variables.md  |   3 +-
 src/handler.rs                                       |  16 +-
 src/server.rs                                        |   8 +-
 src/settings/cli.rs                                  |   9 +-
 src/settings/file.rs                                 |   2 +-
 src/settings/mod.rs                                  |   5 +-
 src/static_files.rs                                  |  37 +-
 tests/dir_listing.rs                                 |  21 +-
 tests/static_files.rs                                | 437 ++++++++++++++------
 tests/toml/config.toml                               |   3 +-
 13 files changed, 414 insertions(+), 151 deletions(-)

diff --git a/docker/public/assets/index.html b/docker/public/assets/index.html
new file mode 100644
index 0000000..a546342
--- /dev/null
+++ b/docker/public/assets/index.html
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Assets Page</title>
  <link rel="stylesheet" href="/assets/main.css">
  <link rel="shortcut icon" href="/assets/favicon.ico">
</head>

<body>
  <h1>Assets Page</h1>
  <p>A blazing fast and asynchronous web server for static files-serving. ⚡</p>
  <p><a href="https://github.com/joseluisq/static-web-server/" target="_blank">View on GitHub</a></p>
  <script src="/assets/main.js"></script>
</body>

</html>
diff --git a/docs/content/configuration/command-line-arguments.md b/docs/content/configuration/command-line-arguments.md
index 3fd7634..28c5c28 100644
--- a/docs/content/configuration/command-line-arguments.md
+++ b/docs/content/configuration/command-line-arguments.md
@@ -88,6 +88,9 @@ OPTIONS:
            HTML file path for 50x errors. If the path is not specified or simply doesn't exist then the server will use
            a generic HTML error message [env: SERVER_ERROR_PAGE_50X=]  [default: ./public/50x.html]
    -p, --port <port>                                          Host port [env: SERVER_PORT=]  [default: 80]
        --redirect-trailing-slash <redirect-trailing-slash>
            Check for trailing slash in the requested directory uri and redirect permanent (308) to the same path with a
            trailing slash suffix if it is missing [env: REDIRECT_TRAILING_SLASH=]  [default: true]
    -d, --root <root>
            Root directory path of static files [env: SERVER_ROOT=]  [default: ./public]

diff --git a/docs/content/configuration/config-file.md b/docs/content/configuration/config-file.md
index ff83cbb..4c7ef8d 100644
--- a/docs/content/configuration/config-file.md
+++ b/docs/content/configuration/config-file.md
@@ -63,6 +63,9 @@ grace-period = 0
#### Log request Remote Address if available
log-remote-address = false

#### Redirect to trailing slash in the requested directory uri
redirect-trailing-slash = true


### Windows Only

diff --git a/docs/content/configuration/environment-variables.md b/docs/content/configuration/environment-variables.md
index a64e89d..1540342 100644
--- a/docs/content/configuration/environment-variables.md
+++ b/docs/content/configuration/environment-variables.md
@@ -75,6 +75,9 @@ Enable cache control headers for incoming requests based on a set of file types.
### SERVER_BASIC_AUTH
It provides [The "Basic" HTTP Authentication Scheme]https://datatracker.ietf.org/doc/html/rfc7617 using credentials as `user-id:password` pairs, encoded using `Base64`. Password must be encoded using the [BCrypt]https://en.wikipedia.org/wiki/Bcrypt password-hashing function. Default empty (disabled).

### REDIRECT_TRAILING_SLASH
Check for trailing slash in the requested directory uri and redirect permanent (308) to the same path with a trailing slash suffix if it is missing. Default `true` (enabled).

## Windows
The following options and commands are Windows platform-specific.

diff --git a/src/handler.rs b/src/handler.rs
index a68e1e7..fc0cb90 100644
--- a/src/handler.rs
+++ b/src/handler.rs
@@ -4,7 +4,10 @@ use std::{future::Future, net::SocketAddr, path::PathBuf, sync::Arc};

use crate::{
    basic_auth, compression, control_headers, cors, custom_headers, error_page, fallback_page,
    redirects, rewrites, security_headers, settings::Advanced, static_files, Error, Result,
    redirects, rewrites, security_headers,
    settings::Advanced,
    static_files::{self, HandleOpts},
    Error, Result,
};

/// It defines options for a request handler.
@@ -22,6 +25,7 @@ pub struct RequestHandlerOpts {
    pub page_fallback: Vec<u8>,
    pub basic_auth: String,
    pub log_remote_address: bool,
    pub redirect_trailing_slash: bool,

    // Advanced options
    pub advanced_opts: Option<Advanced>,
@@ -43,12 +47,13 @@ impl RequestHandler {
        let headers = req.headers();
        let uri = req.uri();

        let root_dir = &self.opts.root_dir;
        let base_path = &self.opts.root_dir;
        let mut uri_path = uri.path();
        let uri_query = uri.query();
        let dir_listing = self.opts.dir_listing;
        let dir_listing_order = self.opts.dir_listing_order;
        let log_remote_addr = self.opts.log_remote_address;
        let redirect_trailing_slash = self.opts.redirect_trailing_slash;

        let mut cors_headers: Option<http::HeaderMap> = None;

@@ -164,15 +169,16 @@ impl RequestHandler {
            }

            // Static files
            match static_files::handle(
            match static_files::handle(&HandleOpts {
                method,
                headers,
                root_dir,
                base_path,
                uri_path,
                uri_query,
                dir_listing,
                dir_listing_order,
            )
                redirect_trailing_slash,
            })
            .await
            {
                Ok(mut resp) => {
diff --git a/src/server.rs b/src/server.rs
index 9cde513..40a6b76 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -167,6 +167,13 @@ impl Server {
        let log_remote_address = general.log_remote_address;
        tracing::info!("log remote address: enabled={}", log_remote_address);

        // Log redirect trailing slash option
        let redirect_trailing_slash = general.redirect_trailing_slash;
        tracing::info!(
            "redirect trailing slash: enabled={}",
            redirect_trailing_slash
        );

        // Grace period option
        let grace_period = general.grace_period;
        tracing::info!("grace period before graceful shutdown: {}s", grace_period);
@@ -186,6 +193,7 @@ impl Server {
                page_fallback,
                basic_auth,
                log_remote_address,
                redirect_trailing_slash,
                advanced_opts,
            }),
        });
diff --git a/src/settings/cli.rs b/src/settings/cli.rs
index abb83fc..ec3ded3 100644
--- a/src/settings/cli.rs
+++ b/src/settings/cli.rs
@@ -179,6 +179,15 @@ pub struct General {
    /// Log incoming requests information along with its remote address if available using the `info` log level.
    pub log_remote_address: bool,

    #[structopt(
        long,
        parse(try_from_str),
        default_value = "true",
        env = "REDIRECT_TRAILING_SLASH"
    )]
    /// Check for trailing slash in the requested directory uri and redirect permanent (308) to the same path with a trailing slash suffix if it is missing.
    pub redirect_trailing_slash: bool,

    //
    // Windows specific arguments and commands
    //
diff --git a/src/settings/file.rs b/src/settings/file.rs
index fc74816..0197379 100644
--- a/src/settings/file.rs
+++ b/src/settings/file.rs
@@ -128,6 +128,8 @@ pub struct General {

    pub log_remote_address: Option<bool>,

    pub redirect_trailing_slash: Option<bool>,

    #[cfg(windows)]
    pub windows_service: Option<bool>,
}
diff --git a/src/settings/mod.rs b/src/settings/mod.rs
index 77cbfca..9904a37 100644
--- a/src/settings/mod.rs
+++ b/src/settings/mod.rs
@@ -83,6 +83,7 @@ impl Settings {
        let mut grace_period = opts.grace_period;
        let mut page_fallback = opts.page_fallback;
        let mut log_remote_address = opts.log_remote_address;
        let mut redirect_trailing_slash = opts.redirect_trailing_slash;

        // Windows-only options
        #[cfg(windows)]
@@ -174,6 +175,9 @@ impl Settings {
                    if let Some(v) = general.log_remote_address {
                        log_remote_address = v
                    }
                    if let Some(v) = general.redirect_trailing_slash {
                        redirect_trailing_slash = v
                    }

                    // Windows-only options
                    #[cfg(windows)]
@@ -300,6 +304,7 @@ impl Settings {
                grace_period,
                page_fallback,
                log_remote_address,
                redirect_trailing_slash,

                // Windows-only options and commands
                #[cfg(windows)]
diff --git a/src/static_files.rs b/src/static_files.rs
index 7781a5c..f612b7b 100644
--- a/src/static_files.rs
+++ b/src/static_files.rs
@@ -39,30 +39,37 @@ impl AsRef<Path> for ArcPath {
    }
}

/// Defines all options needed by the static-files handler.
pub struct HandleOpts<'a> {
    pub method: &'a Method,
    pub headers: &'a HeaderMap<HeaderValue>,
    pub base_path: &'a PathBuf,
    pub uri_path: &'a str,
    pub uri_query: Option<&'a str>,
    pub dir_listing: bool,
    pub dir_listing_order: u8,
    pub redirect_trailing_slash: bool,
}

/// Entry point to handle incoming requests which map to specific files
/// on file system and return a file response.
pub async fn handle(
    method: &Method,
    headers: &HeaderMap<HeaderValue>,
    base_path: impl Into<PathBuf>,
    uri_path: &str,
    uri_query: Option<&str>,
    dir_listing: bool,
    dir_listing_order: u8,
) -> Result<Response<Body>, StatusCode> {
pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<Response<Body>, StatusCode> {
    let method = opts.method;
    let uri_path = opts.uri_path;

    // Check for disallowed HTTP methods and reject request accordently
    if !(method == Method::GET || method == Method::HEAD || method == Method::OPTIONS) {
        return Err(StatusCode::METHOD_NOT_ALLOWED);
    }

    let base = Arc::new(base_path.into());
    let base = Arc::new(opts.base_path.into());
    let (filepath, meta, auto_index) = path_from_tail(base, uri_path).await?;

    // NOTE: `auto_index` appends an `index.html` to an `uri_path` of kind directory only.

    // Check for a trailing slash on the current directory path
    // and redirect if that path doesn't end with the slash char
    if auto_index && !uri_path.ends_with('/') {
    if opts.redirect_trailing_slash && auto_index && !uri_path.ends_with('/') {
        let uri = [uri_path, "/"].concat();
        let loc = match HeaderValue::from_str(uri.as_str()) {
            Ok(val) => val,
@@ -97,18 +104,18 @@ pub async fn handle(
    // 1. 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 dir_listing && auto_index && !filepath.as_ref().exists() {
    if opts.dir_listing && auto_index && !filepath.as_ref().exists() {
        return directory_listing(
            method,
            uri_path,
            uri_query,
            opts.uri_query,
            filepath.as_ref(),
            dir_listing_order,
            opts.dir_listing_order,
        )
        .await;
    }

    file_reply(headers, (filepath, &meta, auto_index)).await
    file_reply(opts.headers, (filepath, &meta, auto_index)).await
}

/// Convert an incoming uri into a valid and sanitized path then returns a tuple
diff --git a/tests/dir_listing.rs b/tests/dir_listing.rs
index 23cdd53..f0430d3 100644
--- a/tests/dir_listing.rs
+++ b/tests/dir_listing.rs
@@ -9,7 +9,7 @@ mod tests {
    use http::{Method, StatusCode};
    use std::path::PathBuf;

    use static_web_server::static_files;
    use static_web_server::static_files::{self, HandleOpts};

    fn root_dir() -> PathBuf {
        PathBuf::from("docker/public/")
@@ -28,15 +28,16 @@ mod tests {
            Method::TRACE,
        ];
        for method in methods {
            match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "/assets",
                None,
                true,
                6,
            )
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "/assets",
                uri_query: None,
                dir_listing: true,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(res) => {
diff --git a/tests/static_files.rs b/tests/static_files.rs
index 44e1ad9..40a9505 100644
--- a/tests/static_files.rs
+++ b/tests/static_files.rs
@@ -11,7 +11,10 @@ mod tests {
    use std::fs;
    use std::path::PathBuf;

    use static_web_server::{compression, static_files};
    use static_web_server::{
        compression,
        static_files::{self, HandleOpts},
    };

    fn root_dir() -> PathBuf {
        PathBuf::from("docker/public/")
@@ -19,15 +22,16 @@ mod tests {

    #[tokio::test]
    async fn handle_file() {
        let mut res = static_files::handle(
            &Method::GET,
            &HeaderMap::new(),
            root_dir(),
            "index.html",
            None,
            false,
            6,
        )
        let mut res = static_files::handle(&HandleOpts {
            method: &Method::GET,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "index.html",
            uri_query: None,
            dir_listing: false,
            dir_listing_order: 6,
            redirect_trailing_slash: true,
        })
        .await
        .expect("unexpected error response on `handle` function");

@@ -57,15 +61,16 @@ mod tests {

    #[tokio::test]
    async fn handle_file_head() {
        let mut res = static_files::handle(
            &Method::HEAD,
            &HeaderMap::new(),
            root_dir(),
            "index.html",
            None,
            false,
            6,
        )
        let mut res = static_files::handle(&HandleOpts {
            method: &Method::HEAD,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "index.html",
            uri_query: None,
            dir_listing: false,
            dir_listing_order: 6,
            redirect_trailing_slash: true,
        })
        .await
        .expect("unexpected error response on `handle` function");

@@ -96,15 +101,16 @@ mod tests {
    #[tokio::test]
    async fn handle_file_not_found() {
        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "xyz.html",
                None,
                false,
                6,
            )
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "xyz.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(_) => {
@@ -119,15 +125,16 @@ mod tests {

    #[tokio::test]
    async fn handle_trailing_slash_redirection() {
        let mut res = static_files::handle(
            &Method::GET,
            &HeaderMap::new(),
            root_dir(),
            "assets",
            None,
            false,
            0,
        )
        let mut res = static_files::handle(&HandleOpts {
            method: &Method::GET,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "assets",
            uri_query: None,
            dir_listing: false,
            dir_listing_order: 0,
            redirect_trailing_slash: true,
        })
        .await
        .expect("unexpected error response on `handle` function");

@@ -142,6 +149,53 @@ mod tests {
    }

    #[tokio::test]
    async fn handle_trailing_slash_redirection_subdir() {
        match static_files::handle(&HandleOpts {
            method: &Method::GET,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "assets",
            uri_query: None,
            dir_listing: false,
            dir_listing_order: 0,
            redirect_trailing_slash: true,
        })
        .await
        {
            Ok(res) => {
                assert_eq!(res.status(), 308);
                assert_eq!(res.headers()["location"], "assets/");
            }
            Err(status) => {
                panic!("expected a status 308 but not a status {}", status)
            }
        }
    }

    #[tokio::test]
    async fn handle_disabled_trailing_slash_redirection_subdir() {
        match static_files::handle(&HandleOpts {
            method: &Method::GET,
            headers: &HeaderMap::new(),
            base_path: &root_dir(),
            uri_path: "assets",
            uri_query: None,
            dir_listing: false,
            dir_listing_order: 0,
            redirect_trailing_slash: false,
        })
        .await
        {
            Ok(res) => {
                assert_eq!(res.status(), 200);
            }
            Err(status) => {
                panic!("expected a status 200 but not a status {}", status)
            }
        }
    }

    #[tokio::test]
    async fn handle_append_index_on_dir() {
        let buf = fs::read(root_dir().join("index.html"))
            .expect("unexpected error during index.html reading");
@@ -149,15 +203,16 @@ mod tests {

        for method in [Method::HEAD, Method::GET] {
            for uri in ["", "/"] {
                match static_files::handle(
                    &method,
                    &HeaderMap::new(),
                    root_dir(),
                    uri,
                    None,
                    false,
                    6,
                )
                match static_files::handle(&HandleOpts {
                    method: &method,
                    headers: &HeaderMap::new(),
                    base_path: &root_dir(),
                    uri_path: uri,
                    uri_query: None,
                    dir_listing: false,
                    dir_listing_order: 6,
                    redirect_trailing_slash: true,
                })
                .await
                {
                    Ok(mut res) => {
@@ -192,15 +247,16 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "/index%2ehtml",
                None,
                false,
                6,
            )
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "/index%2ehtml",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(res) => {
@@ -217,15 +273,16 @@ mod tests {
    #[tokio::test]
    async fn handle_bad_encoded_path() {
        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "/%2E%2e.html",
                None,
                false,
                6,
            )
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "/%2E%2e.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(_) => {
@@ -245,15 +302,16 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            let res1 = match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "index.html",
                None,
                false,
                6,
            )
            let res1 = match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(res) => {
@@ -273,8 +331,17 @@ mod tests {
                res1.headers()["last-modified"].to_owned(),
            );

            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 304);
@@ -296,8 +363,17 @@ mod tests {
                "Mon, 18 Nov 1974 00:00:00 GMT".parse().unwrap(),
            );

            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 200);
@@ -317,15 +393,16 @@ mod tests {
    #[tokio::test]
    async fn handle_precondition() {
        for method in [Method::HEAD, Method::GET] {
            let res1 = match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "index.html",
                None,
                false,
                6,
            )
            let res1 = match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(res) => {
@@ -344,8 +421,17 @@ mod tests {
                res1.headers()["last-modified"].to_owned(),
            );

            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(res) => {
                    assert_eq!(res.status(), 200);
@@ -362,8 +448,17 @@ mod tests {
                "Mon, 18 Nov 1974 00:00:00 GMT".parse().unwrap(),
            );

            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 412);
@@ -394,15 +489,16 @@ mod tests {
            Method::TRACE,
        ];
        for method in methods {
            match static_files::handle(
                &method,
                &HeaderMap::new(),
                root_dir(),
                "index.html",
                None,
                false,
                6,
            )
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &HeaderMap::new(),
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => match method {
@@ -451,8 +547,17 @@ mod tests {
            let mut headers = HeaderMap::new();
            headers.insert(http::header::ACCEPT_ENCODING, enc.parse().unwrap());

            match static_files::handle(method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(res) => {
                    let res = compression::auto(method, &headers, res)
@@ -503,8 +608,17 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
@@ -535,8 +649,17 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 416);
@@ -568,8 +691,17 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(res) => {
                    assert_eq!(res.status(), 200);
@@ -593,8 +725,17 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
@@ -628,8 +769,17 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
@@ -660,8 +810,17 @@ mod tests {
        let buf = Bytes::from(buf);

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 416);
@@ -695,8 +854,17 @@ mod tests {
        );

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 416);
@@ -728,8 +896,17 @@ mod tests {
        headers.insert("range", "bytes=".parse().unwrap());

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 200);
@@ -756,8 +933,17 @@ mod tests {
        headers.insert("range", format!("bytes=100-{}", buf.len()).parse().unwrap());

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
@@ -795,8 +981,17 @@ mod tests {
        );

        for method in [Method::HEAD, Method::GET] {
            match static_files::handle(&method, &headers, root_dir(), "index.html", None, false, 6)
                .await
            match static_files::handle(&HandleOpts {
                method: &method,
                headers: &headers,
                base_path: &root_dir(),
                uri_path: "index.html",
                uri_query: None,
                dir_listing: false,
                dir_listing_order: 6,
                redirect_trailing_slash: true,
            })
            .await
            {
                Ok(mut res) => {
                    assert_eq!(res.status(), 206);
diff --git a/tests/toml/config.toml b/tests/toml/config.toml
index 34b4ded..e785ac6 100644
--- a/tests/toml/config.toml
+++ b/tests/toml/config.toml
@@ -49,6 +49,9 @@ page-fallback = ""
log-remote-address = false


#### Redirect to trailing slash in the requested directory uri
redirect-trailing-slash = true

### Windows Only

#### Run the web server as a Windows Service