http-fs 0.1.0

HTTP File Service library
Documentation
//!Actix adaptor

use super::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 super::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(_) => Ok(HttpResponse::NotFound().finish().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;

                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(builder: &mut tokio_threadpool::Builder) -> &mut tokio_threadpool::Builder {
            builder.pool_size(1)
        }
    }

    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);
    }

}