http-fs 0.4.0

HTTP File Service library
Documentation
//!Actix adaptor
//!
//!## Usage
//!
//!```rust, no_run
//!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() {
//!    actix_web::server::new(|| actix_web::App::new().handler("/", StaticFiles::new(DirectoryConfig)).finish()).bind("127.0.0.1:8080").unwrap().run();
//!}
//!```

use crate::ServeEntry;
use crate::config::{StaticFileConfig};

use std::path::Path;

use futures::Stream;
use actix_web::{HttpRequest, HttpResponse};
use actix_web::dev::{AsyncResult, Handler};
use actix_web::error::Error;

impl<S: 'static, C: 'static + StaticFileConfig> Handler<S> for crate::StaticFiles<C> {
    type Result = Result<AsyncResult<HttpResponse>, Error>;

    fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
        if !C::is_method_allowed(req.method()) {
            return Ok(HttpResponse::MethodNotAllowed().header(http::header::CONTENT_TYPE, "text/plain")
                                                      .header(http::header::ALLOW, "GET, HEAD")
                                                      .body("This resource only supports GET and HEAD.")
                                                      .into());
        }

        let path = req.match_info().get_decoded("tail").unwrap_or_else(|| "".to_string());
        let path = Path::new(path.trim_start_matches("/"));
        let result = self.serve(path);

        match result {
            ServeEntry::NotFound | ServeEntry::IoError(_) => {
                let mut response = HttpResponse::new(http::StatusCode::OK);
                let (code, body) = self.handle_not_found(&path, response.headers_mut());
                *response.status_mut() = code;

                if body.len() > 0 {
                    response.set_body(body);
                }

                Ok(response.into())
            },
            ServeEntry::Directory(path, dir) => Ok(HttpResponse::Ok().content_type("text/html; charset=utf-8").body(self.list_dir(&path, dir)).into()),
            ServeEntry::File(file, meta) => {
                let mut response = HttpResponse::new(http::StatusCode::OK);
                let (code, body) = self.serve_file(&path, file, meta, req.method().clone(), req.headers(), response.headers_mut());
                *response.status_mut() = code;

                if response.headers().contains_key(http::header::CONTENT_RANGE) {
                    match code {
                        http::StatusCode::OK | http::StatusCode::PARTIAL_CONTENT => {
                            response.set_content_encoding(actix_web::http::ContentEncoding::Identity);
                        },
                        _ => ()
                    }
                }

                if let Some(body) = body {
                    response.set_body(actix_web::Body::Streaming(Box::new(body.map_err(|e| e.into()))))
                }

                Ok(response.into())
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use futures::{Async, Future};
    use actix_web::test::TestRequest;
    use std::time::{Duration, SystemTime};
    use crate::utils::display_to_header;

    pub struct DirectoryConfig;
    impl StaticFileConfig for DirectoryConfig {
        type FileService = crate::config::DefaultConfig;
        type DirService = crate::config::DefaultConfig;

        fn handle_directory(&self, _path: &Path) -> bool {
            true
        }

        fn thread_pool_builder() -> threadpool::ThreadPool {
            threadpool::Builder::new().num_threads(1).build()
        }
    }

    macro_rules! from_async_result {
        ($resp:expr) => {
            match $resp.poll().unwrap() {
                Async::Ready(res) => res,
                Async::NotReady => panic!("Future is not ready!?")
            }
        }
    }

    #[test]
    fn test_if_modified_since_without_if_none_match() {
        let files = crate::StaticFiles::new(DirectoryConfig);

        let since = SystemTime::now() + Duration::from_secs(60);
        let since = httpdate::HttpDate::from(since);

        let req = TestRequest::with_uri("/Cargo.toml").header(http::header::IF_MODIFIED_SINCE, display_to_header(&since)).finish();
        let mut resp = files.handle(&req).expect("To get response");
        let resp = from_async_result!(resp);
        assert_eq!(resp.status(), http::StatusCode::NOT_MODIFIED);
    }

    #[test]
    fn test_if_modified_since_with_if_none_match() {
        let files = crate::StaticFiles::new(DirectoryConfig);

        let since = SystemTime::now() + Duration::from_secs(60);
        let since = httpdate::HttpDate::from(since);

        let req = TestRequest::with_uri("/Cargo.toml").header(http::header::IF_MODIFIED_SINCE, display_to_header(&since))
                                                      .header(http::header::IF_NONE_MATCH, "wrong etag")
                                                      .finish();
        let mut resp = files.handle(&req).expect("To get response");
        let resp = from_async_result!(resp);
        assert_ne!(resp.status(), http::StatusCode::NOT_MODIFIED);
    }

}