use hyper::{Body, Request, Response, StatusCode};
use std::{
future::Future,
net::{IpAddr, SocketAddr},
path::PathBuf,
sync::Arc,
};
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
use crate::{compression, compression_static};
#[cfg(feature = "basic-auth")]
use crate::basic_auth;
#[cfg(feature = "fallback-page")]
use crate::fallback_page;
#[cfg(all(unix, feature = "experimental"))]
use crate::metrics;
#[cfg(feature = "experimental")]
use crate::mem_cache::cache::MemCacheOpts;
use crate::{
control_headers, cors, custom_headers, error_page, health,
http_ext::MethodExt,
log_addr, maintenance_mode, redirects, rewrites, security_headers,
settings::Advanced,
static_files::{self, HandleOpts},
virtual_hosts, Error, Result,
};
#[cfg(feature = "directory-listing")]
use crate::directory_listing::DirListFmt;
#[cfg(feature = "directory-listing-download")]
use crate::directory_listing_download::DirDownloadFmt;
pub struct RequestHandlerOpts {
pub root_dir: PathBuf,
#[cfg(feature = "experimental")]
pub memory_cache: Option<MemCacheOpts>,
pub compression: bool,
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
pub compression_level: crate::settings::CompressionLevel,
pub compression_static: bool,
#[cfg(feature = "directory-listing")]
#[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
pub dir_listing: bool,
#[cfg(feature = "directory-listing")]
#[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
pub dir_listing_order: u8,
#[cfg(feature = "directory-listing")]
#[cfg_attr(docsrs, doc(cfg(feature = "directory-listing")))]
pub dir_listing_format: DirListFmt,
#[cfg(feature = "directory-listing-download")]
#[cfg_attr(docsrs, doc(cfg(feature = "directory-listing-download")))]
pub dir_listing_download: Vec<DirDownloadFmt>,
pub cors: Option<cors::Configured>,
pub security_headers: bool,
pub cache_control_headers: bool,
pub page404: PathBuf,
pub page50x: PathBuf,
#[cfg(feature = "fallback-page")]
#[cfg_attr(docsrs, doc(cfg(feature = "fallback-page")))]
pub page_fallback: Vec<u8>,
#[cfg(feature = "basic-auth")]
#[cfg_attr(docsrs, doc(cfg(feature = "basic-auth")))]
pub basic_auth: String,
pub index_files: Vec<String>,
pub log_remote_address: bool,
pub log_x_real_ip: bool,
pub log_forwarded_for: bool,
pub trusted_proxies: Vec<IpAddr>,
pub redirect_trailing_slash: bool,
pub ignore_hidden_files: bool,
pub disable_symlinks: bool,
pub health: bool,
#[cfg(all(unix, feature = "experimental"))]
pub experimental_metrics: bool,
pub maintenance_mode: bool,
pub maintenance_mode_status: StatusCode,
pub maintenance_mode_file: PathBuf,
pub advanced_opts: Option<Advanced>,
}
impl Default for RequestHandlerOpts {
fn default() -> Self {
Self {
root_dir: PathBuf::from("./public"),
compression: true,
compression_static: false,
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
compression_level: crate::settings::CompressionLevel::Default,
#[cfg(feature = "directory-listing")]
dir_listing: false,
#[cfg(feature = "directory-listing")]
dir_listing_order: 6, #[cfg(feature = "directory-listing")]
dir_listing_format: DirListFmt::Html,
#[cfg(feature = "directory-listing-download")]
dir_listing_download: Vec::new(),
cors: None,
#[cfg(feature = "experimental")]
memory_cache: None,
security_headers: false,
cache_control_headers: true,
page404: PathBuf::from("./404.html"),
page50x: PathBuf::from("./50x.html"),
#[cfg(feature = "fallback-page")]
page_fallback: Vec::new(),
#[cfg(feature = "basic-auth")]
basic_auth: String::new(),
index_files: vec!["index.html".into()],
log_remote_address: false,
log_x_real_ip: false,
log_forwarded_for: false,
trusted_proxies: Vec::new(),
redirect_trailing_slash: true,
ignore_hidden_files: false,
disable_symlinks: false,
health: false,
#[cfg(all(unix, feature = "experimental"))]
experimental_metrics: false,
maintenance_mode: false,
maintenance_mode_status: StatusCode::SERVICE_UNAVAILABLE,
maintenance_mode_file: PathBuf::new(),
advanced_opts: None,
}
}
}
pub struct RequestHandler {
pub opts: Arc<RequestHandlerOpts>,
}
impl RequestHandler {
pub fn handle<'a>(
&'a self,
req: &'a mut Request<Body>,
remote_addr: Option<SocketAddr>,
) -> impl Future<Output = Result<Response<Body>, Error>> + Send + 'a {
let mut base_path = &self.opts.root_dir;
#[cfg(feature = "directory-listing")]
let dir_listing = self.opts.dir_listing;
#[cfg(feature = "directory-listing")]
let dir_listing_order = self.opts.dir_listing_order;
#[cfg(feature = "directory-listing")]
let dir_listing_format = &self.opts.dir_listing_format;
#[cfg(feature = "directory-listing-download")]
let dir_listing_download = &self.opts.dir_listing_download;
let redirect_trailing_slash = self.opts.redirect_trailing_slash;
let compression_static = self.opts.compression_static;
let ignore_hidden_files = self.opts.ignore_hidden_files;
let disable_symlinks = self.opts.disable_symlinks;
let index_files: Vec<&str> = self.opts.index_files.iter().map(|s| s.as_str()).collect();
#[cfg(feature = "experimental")]
let memory_cache = self.opts.memory_cache.as_ref();
log_addr::pre_process(&self.opts, req, remote_addr);
async move {
if !req.method().is_allowed() {
return error_page::error_response(
req.uri(),
req.method(),
&StatusCode::METHOD_NOT_ALLOWED,
&self.opts.page404,
&self.opts.page50x,
);
}
if let Some(result) = health::pre_process(&self.opts, req) {
return result;
}
#[cfg(all(unix, feature = "experimental"))]
if let Some(result) = metrics::pre_process(&self.opts, req) {
return result;
}
if let Some(result) = cors::pre_process(&self.opts, req) {
return result;
}
#[cfg(feature = "basic-auth")]
if let Some(response) = basic_auth::pre_process(&self.opts, req) {
return response;
}
if let Some(response) = maintenance_mode::pre_process(&self.opts, req) {
return response;
}
if let Some(result) = redirects::pre_process(&self.opts, req) {
return result;
}
if let Some(result) = rewrites::pre_process(&self.opts, req) {
return result;
}
if let Some(advanced) = &self.opts.advanced_opts {
if let Some(root) =
virtual_hosts::get_real_root(req, advanced.virtual_hosts.as_deref())
{
base_path = root;
}
}
let index_files = index_files.as_ref();
let (resp, file_path) = match static_files::handle(&HandleOpts {
method: req.method(),
headers: req.headers(),
#[cfg(feature = "experimental")]
memory_cache,
base_path,
uri_path: req.uri().path(),
uri_query: req.uri().query(),
#[cfg(feature = "directory-listing")]
dir_listing,
#[cfg(feature = "directory-listing")]
dir_listing_order,
#[cfg(feature = "directory-listing")]
dir_listing_format,
#[cfg(feature = "directory-listing-download")]
dir_listing_download,
redirect_trailing_slash,
compression_static,
ignore_hidden_files,
index_files,
disable_symlinks,
})
.await
{
Ok(result) => (result.resp, Some(result.file_path)),
Err(status) => (
error_page::error_response(
req.uri(),
req.method(),
&status,
&self.opts.page404,
&self.opts.page50x,
)?,
None,
),
};
#[cfg(feature = "fallback-page")]
let resp = fallback_page::post_process(&self.opts, req, resp)?;
let resp = cors::post_process(&self.opts, req, resp)?;
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
let resp = compression_static::post_process(&self.opts, req, resp)?;
#[cfg(any(
feature = "compression",
feature = "compression-gzip",
feature = "compression-brotli",
feature = "compression-zstd",
feature = "compression-deflate"
))]
let resp = compression::post_process(&self.opts, req, resp)?;
let resp = control_headers::post_process(&self.opts, req, resp)?;
let resp = security_headers::post_process(&self.opts, req, resp)?;
let resp = custom_headers::post_process(&self.opts, req, resp, file_path.as_ref())?;
Ok(resp)
}
}
}