refactor: trailing slash redirection for directories
it check for a trailing slash on the current directory uri and
redirect permanently (308) if that path doesn't end with the slash char
resolves #73
Diff
src/static_files.rs | 44 +++++++++++++++++++++++---------------------
tests/static_files.rs | 43 ++++++++++++++++++++++++++++++++++++++++---
2 files changed, 63 insertions(+), 24 deletions(-)
@@ -58,30 +58,32 @@ pub async fn handle(
let base = Arc::new(base_path.into());
let (filepath, meta, auto_index) = path_from_tail(base, uri_path).await?;
if auto_index && !uri_path.ends_with('/') {
let uri = [uri_path, "/"].concat();
let loc = match HeaderValue::from_str(uri.as_str()) {
Ok(val) => val,
Err(err) => {
tracing::error!("invalid header value from current uri: {:?}", err);
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};
let mut resp = Response::new(Body::empty());
resp.headers_mut().insert(hyper::header::LOCATION, loc);
*resp.status_mut() = StatusCode::PERMANENT_REDIRECT;
tracing::trace!("uri doesn't end with a slash so redirecting permanently");
return Ok(resp);
}
if dir_listing && auto_index && !filepath.as_ref().exists() {
if !uri_path.ends_with('/') {
let uri = [uri_path, "/"].concat();
let loc = match HeaderValue::from_str(uri.as_str()) {
Ok(val) => val,
Err(err) => {
tracing::error!("invalid header value from current uri: {:?}", err);
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};
let mut resp = Response::new(Body::empty());
resp.headers_mut().insert(hyper::header::LOCATION, loc);
*resp.status_mut() = StatusCode::PERMANENT_REDIRECT;
tracing::trace!("uri doesn't end with a slash so redirect permanently");
return Ok(resp);
}
return directory_listing(
method,
uri_path,
@@ -116,6 +116,30 @@ 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,
)
.await
.expect("unexpected error response on `handle` function");
assert_eq!(res.status(), 308);
assert_eq!(res.headers()["location"], "assets/");
let body = hyper::body::to_bytes(res.body_mut())
.await
.expect("unexpected bytes error during `body` conversion");
assert_eq!(body, Bytes::new());
}
#[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");
@@ -134,9 +158,22 @@ mod tests {
)
.await
{
Ok(res) => {
assert_eq!(res.status(), 200);
assert_eq!(res.headers()["content-length"], buf.len().to_string());
Ok(mut res) => {
if uri.is_empty() {
assert_eq!(res.status(), 308);
assert_eq!(res.headers()["location"], "/");
let body = hyper::body::to_bytes(res.body_mut())
.await
.expect("unexpected bytes error during `body` conversion");
assert_eq!(body, Bytes::new());
} else {
assert_eq!(res.status(), 200);
assert_eq!(res.headers()["content-length"], buf.len().to_string());
}
}
Err(_) => {
panic!("expected a status 200 but not a status error")