#![warn(missing_docs)]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
pub extern crate threadpool;
pub extern crate http;
pub extern crate etag;
pub extern crate httpdate;
#[cfg(feature = "actix")]
pub extern crate actix_web;
pub mod config;
pub mod headers;
pub mod file;
pub mod utils;
pub mod adaptors;
pub use config::{FileServeConfig, DirectoryListingConfig, StaticFileConfig};
use std::io;
use std::fs;
use std::path::{PathBuf, Path};
#[derive(Debug)]
pub enum ServeEntry {
NotFound,
IoError(io::Error),
File(fs::File, fs::Metadata, PathBuf),
Directory(PathBuf, fs::ReadDir),
}
pub struct StaticFiles<C = config::DefaultConfig> {
workers: threadpool::ThreadPool,
config: C,
}
impl<C: Clone> Clone for StaticFiles<C> {
fn clone(&self) -> StaticFiles<C> {
Self {
workers: self.workers.clone(),
config: self.config.clone(),
}
}
}
impl<C: StaticFileConfig> StaticFiles<C> {
pub fn new(config: C) -> Self {
Self {
workers: C::thread_pool_builder(),
config,
}
}
pub fn workers(&self) -> &threadpool::ThreadPool {
&self.workers
}
pub fn serve(&self, path: &Path) -> ServeEntry {
let mut full_path = self.config.serve_dir().join(path);
let mut meta = match full_path.metadata() {
Ok(meta) => meta,
Err(_) => return ServeEntry::NotFound,
};
if meta.is_dir() {
if let Some(name) = self.config.index_file(path) {
full_path = full_path.join(name);
meta = match full_path.metadata() {
Ok(meta) => meta,
Err(_) => return ServeEntry::NotFound,
};
} else if self.config.handle_directory(path) {
return match full_path.read_dir() {
Ok(dir) => ServeEntry::Directory(path.to_path_buf(), dir),
Err(error) => ServeEntry::IoError(error),
}
} else {
return ServeEntry::NotFound
}
}
match fs::File::open(&full_path) {
Ok(file) => ServeEntry::File(file, meta, full_path),
Err(error) => ServeEntry::IoError(error),
}
}
pub fn handle_not_found(&self, path: &Path, out_headers: &mut http::HeaderMap) -> (http::StatusCode, bytes::Bytes) {
self.config.handle_not_found(path, out_headers)
}
pub fn list_dir(&self, path: &Path, dir: fs::ReadDir) -> bytes::Bytes {
C::DirService::create_body(self.config.serve_dir(), path, dir)
}
pub fn serve_file(&self, path: &Path, file: fs::File, meta: fs::Metadata, method: http::Method, headers: &http::HeaderMap, out_headers: &mut http::HeaderMap) -> (http::StatusCode, Option<file::ChunkedReadFile<C::FileService>>) {
let file_name = match path.file_name().and_then(|file_name| file_name.to_str()) {
Some(file_name) => file_name,
None => return (http::StatusCode::NOT_FOUND, None)
};
file::ServeFile::<C::FileService>::from_parts_with_cfg(file_name, file, meta).prepare(path, method, headers, out_headers, self.workers())
}
}
impl Default for StaticFiles<config::DefaultConfig> {
#[inline]
fn default() -> Self {
Self::new(config::DefaultConfig)
}
}