http-fs 0.5.0

HTTP File Service library
Documentation
//!# http-fs
//!
//!## Features
//!
//!- `actix` - Enables `actix-web` integration.
//!- `hyper` - Enables `hyper` integration.
//!
//!## Usage
//!
//!```rust
//!use http_fs::config::{self, StaticFileConfig};
//!use http_fs::{StaticFiles};
//!
//!use std::path::Path;
//!
//!pub struct DirectoryConfig;
//!impl StaticFileConfig for DirectoryConfig {
//!    type FileService = config::DefaultConfig;
//!    type DirService = config::DefaultConfig;
//!
//!    fn handle_directory(&self, _path: &Path) -> bool {
//!        true
//!    }
//!}
//!
//!fn main() {
//!    let static_files = StaticFiles::new(DirectoryConfig);
//!}
//!```

#![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)]
///Entry's in `fs`
pub enum ServeEntry {
    ///Entry is not found
    NotFound,
    ///Error when looking up entry
    ///
    ///Likely because file either doesn't exist or you lacks permissions
    IoError(io::Error),
    ///File entry is found
    File(fs::File, fs::Metadata, PathBuf),
    ///Directory entry is found
    Directory(PathBuf, fs::ReadDir),
}

/// Static files service
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> {
    ///Creates new instance with provided config
    pub fn new(config: C) -> Self {
        Self {
            workers: C::thread_pool_builder(),
            config,
        }
    }

    ///Returns reference to thread pool
    pub fn workers(&self) -> &threadpool::ThreadPool {
        &self.workers
    }

    ///Serves file
    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),
        }
    }

    ///Handles not found directory
    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)
    }

    ///Serve directory routine
    pub fn list_dir(&self, path: &Path, dir: fs::ReadDir) -> bytes::Bytes {
        C::DirService::create_body(self.config.serve_dir(), path, dir)
    }

    ///Serves file routine
    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)
    }
}