use std::path::{PathBuf, AsPath};
use std::fs::PathExt;
use std::time::Duration;
use std::error::Error;
use std::fmt;
use time::{self, Timespec};
use iron::prelude::*;
use iron::{Handler, status};
use iron::modifier::Modifier;
use iron::modifiers::Redirect;
use mount::OriginalUrl;
use requested_path::RequestedPath;
#[derive(Clone)]
pub struct Static {
pub root: PathBuf,
cache: Option<Cache>,
}
impl Static {
pub fn new<P: AsPath>(root: P) -> Static {
Static { root: root.as_path().to_path_buf(), cache: None }
}
pub fn cache(self, duration: Duration) -> Static {
self.set(Cache::new(duration))
}
fn try_cache<P: AsPath>(&self, req: &mut Request, path: P) -> IronResult<Response> {
match self.cache {
None => Ok(Response::with((status::Ok, path.as_path()))),
Some(ref cache) => cache.handle(req, path.as_path()),
}
}
}
impl Handler for Static {
fn handle(&self, req: &mut Request) -> IronResult<Response> {
let requested_path = RequestedPath::new(&self.root, req);
if requested_path.should_redirect(req) {
let mut redirect_path = match req.extensions.get::<OriginalUrl>() {
None => &req.url,
Some(original_url) => original_url,
}.clone();
redirect_path.path.push("".to_string());
return Ok(Response::with((status::MovedPermanently,
format!("Redirecting to {}", redirect_path),
Redirect(redirect_path))));
}
match requested_path.get_file() {
None => Err(IronError::new(NoFile, status::NotFound)),
Some(path) => self.try_cache(req, path),
}
}
}
impl Set for Static {}
#[derive(Clone)]
pub struct Cache {
pub duration: Duration,
}
impl Cache {
pub fn new(duration: Duration) -> Cache {
Cache { duration: duration }
}
fn handle<P: AsPath>(&self, req: &mut Request, path: P) -> IronResult<Response> {
use iron::headers::IfModifiedSince;
let path = path.as_path();
let last_modified_time = match path.metadata() {
Err(error) => return Err(IronError::new(error, status::InternalServerError)),
Ok(metadata) => Timespec::new((metadata.modified() / 1000) as i64, 0),
};
let if_modified_since = match req.headers.get::<IfModifiedSince>().cloned() {
None => return Ok(self.response_with_cache(path, last_modified_time)),
Some(time) => time.to_timespec(),
};
if last_modified_time <= if_modified_since {
Ok(Response::with(status::NotModified))
} else {
Ok(self.response_with_cache(path, last_modified_time))
}
}
fn response_with_cache<P: AsPath>(&self, path: P, modified: Timespec) -> Response {
use iron::headers::{CacheControl, LastModified, CacheDirective};
let mut response = Response::with((status::Ok, path.as_path()));
let seconds = self.duration.num_seconds() as u32;
let cache = vec![CacheDirective::Public, CacheDirective::MaxAge(seconds)];
response.headers.set(CacheControl(cache));
response.headers.set(LastModified(time::at(modified)));
response
}
}
impl Modifier<Static> for Cache {
fn modify(self, static_handler: &mut Static) {
static_handler.cache = Some(self);
}
}
#[derive(Debug)]
pub struct NoFile;
impl Error for NoFile {
fn description(&self) -> &str { "File not found" }
}
impl fmt::Display for NoFile {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(self.description())
}
}