use std::borrow::Cow;
use std::fmt;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use crate::fs::rewrite::*;
use crate::http::{uri::Segments, ContentType, HeaderMap, Method, Status};
use crate::outcome::IntoOutcome;
use crate::response::Responder;
use crate::route::{Handler, Outcome, Route};
use crate::util::Formatter;
use crate::{response, Data, Request, Response};
#[derive(Clone)]
pub struct FileServer {
rewrites: Vec<Arc<dyn Rewriter>>,
rank: isize,
}
impl FileServer {
const DEFAULT_RANK: isize = 10;
pub fn new<P: AsRef<Path>>(path: P) -> Self {
Self::identity()
.filter(|f, _| f.is_visible())
.rewrite(Prefix::checked(path))
.rewrite(TrailingDirs)
.rewrite(DirIndex::unconditional("index.html"))
}
pub fn without_index<P: AsRef<Path>>(path: P) -> Self {
Self::identity()
.filter(|f, _| f.is_visible())
.rewrite(Prefix::checked(path))
.rewrite(TrailingDirs)
}
pub fn identity() -> Self {
Self {
rewrites: vec![],
rank: Self::DEFAULT_RANK,
}
}
pub fn rank(mut self, rank: isize) -> Self {
self.rank = rank;
self
}
pub fn rewrite<R: Rewriter>(mut self, rewriter: R) -> Self {
self.rewrites.push(Arc::new(rewriter));
self
}
pub fn filter<F: Send + Sync + 'static>(self, f: F) -> Self
where
F: Fn(&File<'_>, &Request<'_>) -> bool,
{
struct Filter<F>(F);
impl<F> Rewriter for Filter<F>
where
F: Fn(&File<'_>, &Request<'_>) -> bool + Send + Sync + 'static,
{
fn rewrite<'r>(&self, f: Option<Rewrite<'r>>, r: &Request<'_>) -> Option<Rewrite<'r>> {
f.and_then(|f| match f {
Rewrite::File(f) if self.0(&f, r) => Some(Rewrite::File(f)),
_ => None,
})
}
}
self.rewrite(Filter(f))
}
pub fn map<F: Send + Sync + 'static>(self, f: F) -> Self
where
F: for<'r> Fn(File<'r>, &Request<'_>) -> Rewrite<'r>,
{
struct Map<F>(F);
impl<F> Rewriter for Map<F>
where
F: for<'r> Fn(File<'r>, &Request<'_>) -> Rewrite<'r> + Send + Sync + 'static,
{
fn rewrite<'r>(&self, f: Option<Rewrite<'r>>, r: &Request<'_>) -> Option<Rewrite<'r>> {
f.map(|f| match f {
Rewrite::File(f) => self.0(f, r),
Rewrite::Redirect(r) => Rewrite::Redirect(r),
})
}
}
self.rewrite(Map(f))
}
}
impl From<FileServer> for Vec<Route> {
fn from(server: FileServer) -> Self {
let mut route = Route::ranked(server.rank, Method::Get, "/<path..>", server);
route.name = Some("FileServer".into());
vec![route]
}
}
#[crate::async_trait]
impl Handler for FileServer {
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r> {
use crate::http::uri::fmt::Path as UriPath;
let path: Option<PathBuf> = req
.segments::<Segments<'_, UriPath>>(0..)
.ok()
.and_then(|segments| segments.to_path_buf(true).ok());
let mut response = path.map(|p| Rewrite::File(File::new(p)));
for rewrite in &self.rewrites {
response = rewrite.rewrite(response, req);
}
let (outcome, status) = match response {
Some(Rewrite::File(f)) => (f.open().await.respond_to(req), Status::NotFound),
Some(Rewrite::Redirect(r)) => (r.respond_to(req), Status::InternalServerError),
None => return Outcome::forward(data, Status::NotFound),
};
outcome.or_forward((data, status))
}
}
impl fmt::Debug for FileServer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FileServer")
.field(
"rewrites",
&Formatter(|f| write!(f, "<{} rewrites>", self.rewrites.len())),
)
.field("rank", &self.rank)
.finish()
}
}
impl<'r> File<'r> {
async fn open(self) -> std::io::Result<NamedFile<'r>> {
let file = tokio::fs::File::open(&self.path).await?;
let metadata = file.metadata().await?;
if metadata.is_dir() {
return Err(std::io::Error::other("is a directory"));
}
Ok(NamedFile {
file,
len: metadata.len(),
path: self.path,
headers: self.headers,
})
}
}
struct NamedFile<'r> {
file: tokio::fs::File,
len: u64,
path: Cow<'r, Path>,
headers: HeaderMap<'r>,
}
impl<'r> Responder<'r, 'r> for NamedFile<'r> {
fn respond_to(self, _: &'r Request<'_>) -> response::Result<'r> {
let mut response = Response::new();
response.set_header_map(self.headers);
if !response.headers().contains("Content-Type") {
self.path
.extension()
.and_then(|ext| ext.to_str())
.and_then(ContentType::from_extension)
.map(|content_type| response.set_header(content_type));
}
response.set_sized_body(self.len as usize, self.file);
Ok(response)
}
}