use std::path::PathBuf;
use std::sync::Arc;
use futures::{future, Future};
use hyper::{
self,
service::{NewService, Service},
Body, Error as HyperError, Method, Request, Response, Server, StatusCode,
};
use mime_guess;
use tokio_fs;
use tokio_io;
use crate::error::*;
type FutureResponse = Box<Future<Item = Response<Body>, Error = HyperError> + Send>;
static NOTFOUND: &[u8] = b"Not Found";
struct Config {
root_folder: PathBuf,
}
pub struct StaticFiles {
config: Arc<Config>,
}
impl Config {
fn new<T>(path: T) -> Result<Self, Error>
where
T: Into<PathBuf>,
{
Ok(Self {
root_folder: path.into().canonicalize()?,
})
}
}
impl NewService for StaticFiles {
type ReqBody = Body;
type ResBody = Body;
type Error = HyperError;
type InitError = HyperError;
type Service = StaticFiles;
type Future = Box<Future<Item = Self::Service, Error = Self::InitError> + Send>;
fn new_service(&self) -> Self::Future {
Box::new(future::ok(Self {
config: self.config.clone(),
}))
}
}
impl Service for StaticFiles {
type ReqBody = Body;
type ResBody = Body;
type Error = HyperError;
type Future = FutureResponse;
fn call(&mut self, req: Request<Self::ReqBody>) -> Self::Future {
match (req.method(), req.uri().path()) {
(&Method::GET, "/") => self.serve_file("/index.html"),
(&Method::GET, path) => self.serve_file(path),
_ => unreachable!(),
}
}
}
impl StaticFiles {
pub fn new<T>(path: T) -> Result<Self, Error>
where
T: Into<PathBuf>,
{
Ok(Self {
config: Arc::new(Config::new(path)?),
})
}
pub fn start(self, port: u16) {
let addr = format!("0.0.0.0:{}", port)
.parse()
.expect("Address not valid");
let server = Server::bind(&addr)
.serve(self)
.map_err(|e| eprintln!("error: {}", e));
println!("\nServing at {}", addr);
hyper::rt::run(server);
}
pub fn serve_file(&mut self, path: &str) -> FutureResponse {
let mut file_path = self.config.root_folder.clone();
if path.starts_with("/") {
file_path.push(&path[1..]);
} else {
file_path.push(path);
}
if file_path.extension().is_none() && !file_path.exists() {
file_path.set_extension("js");
}
let ext = file_path
.extension()
.map(|s| s.to_str().unwrap_or(""))
.unwrap_or("")
.to_string();
println!("Serving {}", path);
Box::new(
tokio_fs::file::File::open(file_path)
.and_then(move |file| {
let buf: Vec<u8> = Vec::new();
tokio_io::io::read_to_end(file, buf)
.and_then(move |item| {
let mime_type =
mime_guess::get_mime_type_str(&ext).unwrap_or_else(|| {
if ext == "wasm" {
"application/wasm"
} else {
"*"
}
});
let res = Response::builder()
.status(StatusCode::OK)
.header("Content-Type", mime_type)
.body(item.1.into())
.unwrap_or_else(|_| {
Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::empty())
.unwrap()
});
Ok(res)
})
.or_else(|_| {
Ok(Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
.body(Body::empty())
.unwrap())
})
})
.or_else(|_| {
Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(NOTFOUND.into())
.unwrap())
}),
)
}
}