use ::util::{open_with_metadata, OpenWithMetadataFuture, RequestedPath};
use futures::{Async::*, Future, Poll};
use http::{Method, Request};
use std::convert::AsRef;
use std::fs::Metadata;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
use tokio::fs::File;
#[derive(Debug)]
pub enum ResolveResult {
MethodNotMatched,
UriNotMatched,
NotFound,
PermissionDenied,
IsDirectory,
Found(File, Metadata),
}
enum ResolveState {
MethodNotMatched,
UriNotMatched,
WaitOpen(OpenWithMetadataFuture),
WaitOpenIndex(OpenWithMetadataFuture),
}
fn map_open_err(err: Error) -> Poll<ResolveResult, Error> {
match err.kind() {
ErrorKind::NotFound => Ok(Ready(ResolveResult::NotFound)),
ErrorKind::PermissionDenied => Ok(Ready(ResolveResult::PermissionDenied)),
_ => Err(err),
}
}
pub struct ResolveFuture {
full_path: Option<PathBuf>,
is_dir_request: bool,
state: ResolveState,
}
impl Future for ResolveFuture {
type Item = ResolveResult;
type Error = Error;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
loop {
self.state = match self.state {
ResolveState::MethodNotMatched => {
return Ok(Ready(ResolveResult::MethodNotMatched));
},
ResolveState::UriNotMatched => {
return Ok(Ready(ResolveResult::UriNotMatched));
},
ResolveState::WaitOpen(ref mut future) => {
let (file, metadata) = match future.poll() {
Ok(Ready(pair)) => pair,
Ok(NotReady) => return Ok(NotReady),
Err(err) => return map_open_err(err),
};
if self.is_dir_request && !metadata.is_dir() {
return Ok(Ready(ResolveResult::NotFound));
}
if !self.is_dir_request && metadata.is_dir() {
return Ok(Ready(ResolveResult::IsDirectory));
}
if !self.is_dir_request {
return Ok(Ready(ResolveResult::Found(file, metadata)));
}
let mut full_path = self.full_path.take().expect("invalid state");
full_path.push("index.html");
ResolveState::WaitOpenIndex(open_with_metadata(full_path))
},
ResolveState::WaitOpenIndex(ref mut future) => {
let (file, metadata) = match future.poll() {
Ok(Ready(pair)) => pair,
Ok(NotReady) => return Ok(NotReady),
Err(err) => return map_open_err(err),
};
if metadata.is_dir() {
return Ok(Ready(ResolveResult::NotFound));
}
return Ok(Ready(ResolveResult::Found(file, metadata)));
},
}
}
}
}
pub fn resolve<B, P: AsRef<Path>>(root: P, req: &Request<B>) -> ResolveFuture {
match *req.method() {
Method::HEAD | Method::GET => {},
_ => {
return ResolveFuture {
full_path: None,
is_dir_request: false,
state: ResolveState::MethodNotMatched,
};
},
}
if req.uri().scheme_part().is_some() || req.uri().host().is_some() {
return ResolveFuture {
full_path: None,
is_dir_request: false,
state: ResolveState::UriNotMatched,
};
}
let RequestedPath { full_path, is_dir_request } =
RequestedPath::resolve(root.as_ref(), req.uri().path());
let state = ResolveState::WaitOpen(open_with_metadata(full_path.clone()));
let full_path = Some(full_path);
ResolveFuture { full_path, is_dir_request, state }
}