refactor: http-related extension traits (#160)
https://rust-lang.github.io/rfcs/0445-extension-trait-conventions.html
Diff
src/compression.rs | 4 +-
src/directory_listing.rs | 5 +--
src/error_page.rs | 4 +-
src/exts/http.rs | 36 ++++++++++++++++++-
src/exts/mod.rs | 3 ++-
src/handler.rs | 12 +++---
src/lib.rs | 1 +-
src/static_files.rs | 97 +++++++++++++++++++++++--------------------------
8 files changed, 100 insertions(+), 62 deletions(-)
@@ -14,7 +14,7 @@ use std::pin::Pin;
use std::task::{Context, Poll};
use tokio_util::io::{ReaderStream, StreamReader};
use crate::Result;
use crate::{exts::http::MethodExt, Result};
pub const TEXT_MIME_TYPES: [&str; 24] = [
@@ -62,7 +62,7 @@ pub fn auto(
resp: Response<Body>,
) -> Result<Response<Body>> {
if method == Method::HEAD || method == Method::OPTIONS {
if method.is_head() || method.is_options() {
return Ok(resp);
}
@@ -13,7 +13,7 @@ use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
use structopt::clap::arg_enum;
use crate::Result;
use crate::{exts::http::MethodExt, Result};
arg_enum! {
#[derive(Debug, Serialize, Deserialize, Clone)]
@@ -36,8 +36,6 @@ pub fn auto_index<'a>(
dir_listing_order: u8,
dir_listing_format: &'a DirListFmt,
) -> impl Future<Output = Result<Response<Body>, StatusCode>> + Send + 'a {
let is_head = method == Method::HEAD;
@@ -47,6 +45,7 @@ pub fn auto_index<'a>(
tokio::fs::read_dir(parent).then(move |res| match res {
Ok(dir_reader) => Either::Left(async move {
let is_head = method.is_head();
match read_dir_entries(
dir_reader,
current_path,
@@ -2,7 +2,7 @@ use headers::{AcceptRanges, ContentLength, ContentType, HeaderMapExt};
use hyper::{Body, Method, Response, StatusCode, Uri};
use mime_guess::mime;
use crate::Result;
use crate::{exts::http::MethodExt, Result};
pub fn error_response(
@@ -83,7 +83,7 @@ pub fn error_response(
let mut body = Body::empty();
let len = error_page_content.len() as u64;
if method != Method::HEAD {
if !method.is_head() {
body = Body::from(error_page_content)
}
@@ -0,0 +1,36 @@
use hyper::Method;
pub const HTTP_SUPPORTED_METHODS: [Method; 3] = [Method::OPTIONS, Method::HEAD, Method::GET];
pub trait MethodExt {
fn is_allowed(&self) -> bool;
fn is_get(&self) -> bool;
fn is_head(&self) -> bool;
fn is_options(&self) -> bool;
}
impl MethodExt for Method {
fn is_allowed(&self) -> bool {
HTTP_SUPPORTED_METHODS.iter().any(|h| self == h)
}
fn is_get(&self) -> bool {
self == Method::GET
}
fn is_head(&self) -> bool {
self == Method::HEAD
}
fn is_options(&self) -> bool {
self == Method::OPTIONS
}
}
@@ -0,0 +1,3 @@
pub mod http;
@@ -1,11 +1,13 @@
use headers::HeaderValue;
use hyper::{header::WWW_AUTHENTICATE, Body, Method, Request, Response, StatusCode};
use hyper::{header::WWW_AUTHENTICATE, Body, Request, Response, StatusCode};
use std::{future::Future, net::IpAddr, net::SocketAddr, path::PathBuf, sync::Arc};
use crate::{
basic_auth, compression, control_headers, cors, custom_headers,
directory_listing::DirListFmt,
error_page, fallback_page, redirects, rewrites, security_headers,
error_page,
exts::http::MethodExt,
fallback_page, redirects, rewrites, security_headers,
settings::Advanced,
static_files::{self, HandleOpts},
Error, Result,
@@ -86,8 +88,8 @@ impl RequestHandler {
);
async move {
if !(method == Method::GET || method == Method::HEAD || method == Method::OPTIONS) {
if !method.is_allowed() {
return error_page::error_response(
uri,
method,
@@ -246,7 +248,7 @@ impl RequestHandler {
}
Err(status) => {
if method == Method::GET
if method.is_get()
&& status == StatusCode::NOT_FOUND
&& !self.opts.page_fallback.is_empty()
{
@@ -17,6 +17,7 @@ pub mod cors;
pub mod custom_headers;
pub mod directory_listing;
pub mod error_page;
pub mod exts;
pub mod fallback_page;
pub mod handler;
pub mod helpers;
@@ -22,6 +22,7 @@ use std::sync::Mutex;
use std::task::{Context, Poll};
use crate::directory_listing::DirListFmt;
use crate::exts::http::{MethodExt, HTTP_SUPPORTED_METHODS};
use crate::{compression_static, directory_listing, Result};
@@ -44,8 +45,8 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<(Response<Body>, bool),
let method = opts.method;
let uri_path = opts.uri_path;
if !(method == Method::GET || method == Method::HEAD || method == Method::OPTIONS) {
if !method.is_allowed() {
return Err(StatusCode::METHOD_NOT_ALLOWED);
}
@@ -60,59 +61,55 @@ pub async fn handle<'a>(opts: &HandleOpts<'a>) -> Result<(Response<Body>, bool),
let is_precompressed = precompressed_variant.is_some();
if opts.redirect_trailing_slash && is_dir && !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;
if is_dir {
if opts.redirect_trailing_slash && !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);
}
};
tracing::trace!("uri doesn't end with a slash so redirecting permanently");
return Ok((resp, is_precompressed));
}
let mut resp = Response::new(Body::empty());
resp.headers_mut().insert(hyper::header::LOCATION, loc);
*resp.status_mut() = StatusCode::PERMANENT_REDIRECT;
if method == Method::OPTIONS {
let mut resp = Response::new(Body::empty());
*resp.status_mut() = StatusCode::NO_CONTENT;
resp.headers_mut()
.typed_insert(headers::Allow::from_iter(vec![
Method::OPTIONS,
Method::HEAD,
Method::GET,
]));
resp.headers_mut().typed_insert(AcceptRanges::bytes());
tracing::trace!("uri doesn't end with a slash so redirecting permanently");
return Ok((resp, is_precompressed));
}
return Ok((resp, is_precompressed));
}
if method.is_options() {
let mut resp = Response::new(Body::empty());
*resp.status_mut() = StatusCode::NO_CONTENT;
resp.headers_mut()
.typed_insert(headers::Allow::from_iter(HTTP_SUPPORTED_METHODS));
resp.headers_mut().typed_insert(AcceptRanges::bytes());
if opts.dir_listing && is_dir && !file_path.exists() {
let resp = directory_listing::auto_index(
method,
uri_path,
opts.uri_query,
file_path.as_ref(),
opts.dir_listing_order,
opts.dir_listing_format,
)
.await?;
return Ok((resp, is_precompressed));
}
return Ok((resp, is_precompressed));
if opts.dir_listing && !file_path.exists() {
let resp = directory_listing::auto_index(
method,
uri_path,
opts.uri_query,
file_path.as_ref(),
opts.dir_listing_order,
opts.dir_listing_format,
)
.await?;
return Ok((resp, is_precompressed));
}
}