#![warn(missing_docs)]
#![cfg_attr(feature = "cargo-clippy", allow(clippy::style))]
pub extern crate tokio_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;
#[cfg(feature = "actix")]
pub mod actix;
#[cfg(feature = "hyper")]
pub mod hyper;
pub use config::{FileServeConfig, DirectoryListingConfig, StaticFileConfig};
use std::mem;
use std::ptr;
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),
Directory(PathBuf, fs::ReadDir),
}
pub struct StaticFiles<C = config::DefaultConfig> {
workers: mem::ManuallyDrop<tokio_threadpool::ThreadPool>,
config: C,
}
impl<C: StaticFileConfig> StaticFiles<C> {
pub fn new(config: C) -> Self {
Self {
workers: mem::ManuallyDrop::new(C::thread_pool_builder(&mut tokio_threadpool::Builder::new()).build()),
config,
}
}
pub fn workers(&self) -> &tokio_threadpool::ThreadPool {
&self.workers
}
pub fn serve(&self, path: &Path) -> ServeEntry {
let mut full_path = self.config.serve_dir().join(path);
let 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);
} 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),
Err(error) => ServeEntry::IoError(error),
}
}
pub fn list_dir(&self, path: &Path, dir: fs::ReadDir) -> String {
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)
}
}
impl<C> Drop for StaticFiles<C> {
fn drop(&mut self) {
let to_drop = unsafe { ptr::read(&self.workers as *const _) };
let workers: tokio_threadpool::ThreadPool = mem::ManuallyDrop::into_inner(to_drop);
workers.shutdown_now();
}
}