use crate::ServeEntry;
use crate::config::{StaticFileConfig};
use std::path::Path;
use futures::Stream;
use futures::future::FutureResult;
use actix_service::{NewService, Service};
use actix_web::dev::{PayloadStream, ResourceDef, HttpServiceFactory, ServiceConfig, ServiceRequest, ServiceResponse, Body};
use actix_web::middleware::encoding::BodyEncoding;
use actix_http::body::SizedStream;
use percent_encoding::{percent_decode};
use actix_web::{HttpResponse};
use actix_web::error::Error;
impl<C: 'static + StaticFileConfig + Clone> HttpServiceFactory<PayloadStream> for crate::StaticFiles<C> {
fn register(self, config: &mut ServiceConfig<PayloadStream>) {
let r_def = match config.is_root() {
true => ResourceDef::root_prefix(self.config.router_prefix()),
false => ResourceDef::prefix(self.config.router_prefix()),
};
config.register_service(r_def, None, self, None);
}
}
impl<C: 'static + StaticFileConfig + Clone> NewService for crate::StaticFiles<C> {
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Service = Self;
type InitError = ();
type Future = FutureResult<Self::Service, Self::InitError>;
fn new_service(&self, _: &()) -> Self::Future {
futures::future::ok(self.clone())
}
}
impl<C: 'static + StaticFileConfig + Clone> Service for crate::StaticFiles<C> {
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Future = FutureResult<Self::Response, Self::Error>;
fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> {
Ok(futures::Async::Ready(()))
}
fn call(&mut self, req: ServiceRequest) -> Self::Future {
let (req, _) = req.into_parts();
if !C::is_method_allowed(req.method()) {
let response = HttpResponse::MethodNotAllowed().header(http::header::CONTENT_TYPE, "text/plain")
.header(http::header::ALLOW, "GET, HEAD")
.body("This resource only supports GET and HEAD.");
return futures::future::ok(ServiceResponse::new(req, response));
}
let path = req.match_info().get("tail").unwrap_or_else(|| "");
let path = percent_decode(path.as_bytes()).decode_utf8().expect("To percent decode");
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 {
let body = actix_web::dev::ResponseBody::Body(Body::Bytes(body));
futures::future::ok(ServiceResponse::new(req, response.map_body(|_, _| body)))
} else {
futures::future::ok(ServiceResponse::new(req, response))
}
},
ServeEntry::Directory(path, dir) => {
let response = HttpResponse::Ok().content_type("text/html; charset=utf-8").body(self.list_dir(&path, dir));
futures::future::ok(ServiceResponse::new(req, response))
},
ServeEntry::File(file, meta, path) => {
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.encoding(actix_web::http::ContentEncoding::Identity);
},
_ => ()
}
}
let res = match body {
Some(body) => {
let size = body.size;
let body = SizedStream::new(size as usize, body.map_err(|err| err.into()));
let body = actix_web::dev::ResponseBody::Body(Body::Message(Box::new(body)));
ServiceResponse::new(req, response.map_body(|_, _| body))
},
None => ServiceResponse::new(req, response),
};
futures::future::ok(res)
}
}
}
}
#[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;
#[derive(Clone)]
pub struct DirectoryConfig;
impl StaticFileConfig for DirectoryConfig {
type FileService = crate::config::DefaultConfig;
type DirService = crate::config::DefaultConfig;
fn index_file(&self, _path: &Path) -> Option<&Path> {
Some(Path::new("Cargo.toml"))
}
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_index_file() {
let mut files = crate::StaticFiles::new(DirectoryConfig);
let since = SystemTime::now() + Duration::from_secs(60);
let since = httpdate::HttpDate::from(since);
let req = TestRequest::with_uri("/").header(http::header::IF_MODIFIED_SINCE, display_to_header(&since)).to_srv_request();
let mut resp = files.call(req);
let resp = from_async_result!(resp);
assert_eq!(resp.status(), http::StatusCode::NOT_MODIFIED);
}
#[test]
fn test_if_modified_since_without_if_none_match() {
let mut 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)).to_srv_request();
let mut resp = files.call(req);
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 mut 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")
.to_srv_request();
let mut resp = files.call(req);
let resp = from_async_result!(resp);
assert_ne!(resp.status(), http::StatusCode::NOT_MODIFIED);
}
}