Use Staticfile default handler on all error paths

This commit is contained in:
tessereth 2018-07-02 12:49:52 +10:00
parent 15b7981c86
commit dedd7cc1ac
No known key found for this signature in database
GPG Key ID: DF640BCE2E866153
2 changed files with 63 additions and 66 deletions

View File

@ -626,6 +626,21 @@ impl From<UrlParseError> for UrlGenerationError {
} }
} }
/// Errors which can occur when serving static files.
#[derive(Fail, Debug, PartialEq)]
pub enum StaticFileError {
/// Cannot render directory
#[fail(display = "Cannot render directory")]
IsDirectory,
}
/// Return `NotFound` for `StaticFileError`
impl ResponseError for StaticFileError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::NOT_FOUND)
}
}
/// Helper type that can wrap any error and generate custom response. /// Helper type that can wrap any error and generate custom response.
/// ///
/// In following example any `io::Error` will be converted into "BAD REQUEST" /// In following example any `io::Error` will be converted into "BAD REQUEST"

114
src/fs.rs
View File

@ -18,7 +18,7 @@ use mime;
use mime_guess::{get_mime_type, guess_mime_type}; use mime_guess::{get_mime_type, guess_mime_type};
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use error::Error; use error::{Error, StaticFileError};
use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler}; use handler::{AsyncResult, Handler, Responder, RouteHandler, WrapHandler};
use header; use header;
use http::{ContentEncoding, Method, StatusCode}; use http::{ContentEncoding, Method, StatusCode};
@ -561,7 +561,6 @@ fn directory_listing<S>(
/// ``` /// ```
pub struct StaticFiles<S> { pub struct StaticFiles<S> {
directory: PathBuf, directory: PathBuf,
accessible: bool,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
cpu_pool: CpuPool, cpu_pool: CpuPool,
@ -589,24 +588,21 @@ impl<S: 'static> StaticFiles<S> {
pub fn with_pool<T: Into<PathBuf>>(dir: T, pool: CpuPool) -> StaticFiles<S> { pub fn with_pool<T: Into<PathBuf>>(dir: T, pool: CpuPool) -> StaticFiles<S> {
let dir = dir.into(); let dir = dir.into();
let (dir, access) = match dir.canonicalize() { let dir = match dir.canonicalize() {
Ok(dir) => { Ok(dir) => {
if dir.is_dir() { if !dir.is_dir() {
(dir, true)
} else {
warn!("Is not directory `{:?}`", dir); warn!("Is not directory `{:?}`", dir);
(dir, false) };
} dir
} }
Err(err) => { Err(err) => {
warn!("Static files directory `{:?}` error: {}", dir, err); warn!("Static files directory `{:?}` error: {}", dir, err);
(dir, false) dir
} }
}; };
StaticFiles { StaticFiles {
directory: dir, directory: dir,
accessible: access,
index: None, index: None,
show_index: false, show_index: false,
cpu_pool: pool, cpu_pool: pool,
@ -652,62 +648,51 @@ impl<S: 'static> StaticFiles<S> {
self.default = Box::new(WrapHandler::new(handler)); self.default = Box::new(WrapHandler::new(handler));
self self
} }
fn try_handle(&self, req: &HttpRequest<S>) -> Result<AsyncResult<HttpResponse>, Error> {
let tail: String = req.match_info().query("tail")?;
let relpath = PathBuf::from_param(tail.trim_left_matches('/'))?;
// full filepath
let path = self.directory.join(&relpath).canonicalize()?;
if path.is_dir() {
if let Some(ref redir_index) = self.index {
// TODO: Don't redirect, just return the index content.
// TODO: It'd be nice if there were a good usable URL manipulation
// library
let mut new_path: String = req.path().to_owned();
if !new_path.ends_with('/') {
new_path.push('/');
}
new_path.push_str(redir_index);
HttpResponse::Found()
.header(header::LOCATION, new_path.as_str())
.finish()
.respond_to(&req)
} else if self.show_index {
let dir = Directory::new(self.directory.clone(), path);
Ok((*self.renderer)(&dir, &req)?.into())
} else {
Err(StaticFileError::IsDirectory.into())
}
} else {
NamedFile::open(path)?
.set_cpu_pool(self.cpu_pool.clone())
.respond_to(&req)?
.respond_to(&req)
}
}
} }
impl<S: 'static> Handler<S> for StaticFiles<S> { impl<S: 'static> Handler<S> for StaticFiles<S> {
type Result = Result<AsyncResult<HttpResponse>, Error>; type Result = Result<AsyncResult<HttpResponse>, Error>;
fn handle(&self, req: &HttpRequest<S>) -> Self::Result { fn handle(&self, req: &HttpRequest<S>) -> Self::Result {
if !self.accessible { self.try_handle(req).or_else(|e| {
debug!("StaticFiles: Failed to handle {}: {}", req.path(), e);
Ok(self.default.handle(req)) Ok(self.default.handle(req))
} else { })
let relpath = match req
.match_info()
.get("tail")
.map(|tail| PathBuf::from_param(tail.trim_left_matches('/')))
{
Some(Ok(path)) => path,
_ => {
return Ok(self.default.handle(req));
}
};
// full filepath
let path = match self.directory.join(&relpath).canonicalize() {
Ok(path) => path,
_ => return Ok(self.default.handle(req))
};
if path.is_dir() {
if let Some(ref redir_index) = self.index {
// TODO: Don't redirect, just return the index content.
// TODO: It'd be nice if there were a good usable URL manipulation
// library
let mut new_path: String = req.path().to_owned();
if !new_path.ends_with('/') {
new_path.push('/');
}
new_path.push_str(redir_index);
HttpResponse::Found()
.header(header::LOCATION, new_path.as_str())
.finish()
.respond_to(&req)
} else if self.show_index {
let dir = Directory::new(self.directory.clone(), path);
Ok((*self.renderer)(&dir, &req)?.into())
} else {
Ok(self.default.handle(req))
}
} else {
let file = match NamedFile::open(path) {
Ok(file) => file,
_ => return Ok(self.default.handle(req))
};
file.set_cpu_pool(self.cpu_pool.clone())
.respond_to(&req)?
.respond_to(&req)
}
}
} }
} }
@ -1191,13 +1176,11 @@ mod tests {
#[test] #[test]
fn test_static_files() { fn test_static_files() {
let mut st = StaticFiles::new(".").show_files_listing(); let mut st = StaticFiles::new(".").show_files_listing();
st.accessible = false; let req = TestRequest::with_uri("/missing").param("tail", "missing").finish();
let req = TestRequest::default().finish();
let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = st.handle(&req).respond_to(&req).unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::NOT_FOUND); assert_eq!(resp.status(), StatusCode::NOT_FOUND);
st.accessible = true;
st.show_index = false; st.show_index = false;
let req = TestRequest::default().finish(); let req = TestRequest::default().finish();
let resp = st.handle(&req).respond_to(&req).unwrap(); let resp = st.handle(&req).respond_to(&req).unwrap();
@ -1220,11 +1203,10 @@ mod tests {
#[test] #[test]
fn test_default_handler_file_missing() { fn test_default_handler_file_missing() {
let st = StaticFiles::new(".") let st = StaticFiles::new(".")
.default_handler(|_req| "default content"); .default_handler(|_: &_| "default content");
let req = TestRequest::with_uri("/missing") let req = TestRequest::with_uri("/missing").param("tail", "missing").finish();
.param("tail", "missing").finish();
let resp = st.handle(req).respond_to(&HttpRequest::default()).unwrap(); let resp = st.handle(&req).respond_to(&req).unwrap();
let resp = resp.as_msg(); let resp = resp.as_msg();
assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.body(), &Body::Binary(Binary::Slice(b"default content"))); assert_eq!(resp.body(), &Body::Binary(Binary::Slice(b"default content")));