From 464c376a2a22a375301fa9b5b0c02ac44a6f7a1a Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Tue, 24 Dec 2019 01:27:21 +0100 Subject: [PATCH] Merge pull request #5 from joseluisq/develop Error page support (resolves #3 ) --- Makefile | 2 +- README.md | 16 ++++++++++------ public/404.html | 16 ++++++++++++++++ public/50x.html | 16 ++++++++++++++++ src/config.rs | 14 ++++++++++++++ src/error_page.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 21 +++++++++++++++------ src/staticfiles.rs | 56 ++++++++++++++++++++++++++++++++++++++++++-------------- 8 files changed, 175 insertions(+), 27 deletions(-) create mode 100644 public/404.html create mode 100644 public/50x.html create mode 100644 src/error_page.rs diff --git a/Makefile b/Makefile index 4c1e3a2..c09fff7 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ test: .PHONY: test fmt: - @cargo fix --edition + @cargo fix @cargo fmt --all .PHONY: fmt diff --git a/README.md b/README.md index 62bd8fe..292d65d 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ Server is configured either via environment variables: - **SERVER_PORT**: Host port. Default `80`. - **SERVER_ROOT**: Root directory path of static files. Default `./public`. - **SERVER_ASSETS**: Assets directory path for add cache headers functionality. Default `./assets` but relative to the root. +- **SERVER_ERROR_PAGE_404**: HTML file path for 404 errors. Default `./public/404.html`. +- **SERVER_ERROR_PAGE_50X**: HTML file path for 50x errors. Default `./public/50x.html`. Or command line arguments listed with `cargo run -- -h`. @@ -45,12 +47,14 @@ FLAGS: -V, --version Prints version information OPTIONS: - --assets Assets directory path for add cache headers functionality [env: SERVER_ASSETS=] [default: - ./assets] - --host Host address (E.g 127.0.0.1) [env: SERVER_HOST=] [default: [::]] - --name Name for server [env: SERVER_NAME=] [default: my-static-server] - --port Host port [env: SERVER_PORT=] [default: 80] - --root Root directory path of static files [env: SERVER_ROOT=] + --assets Assets directory path for add cache headers functionality [env: SERVER_ASSETS=] + [default: ./assets] + --host Host address (E.g 127.0.0.1) [env: SERVER_HOST=] [default: [::]] + --name Name for server [env: SERVER_NAME=] [default: my-static-server] + --page404 HTML file path for 404 errors [env: SERVER_ERROR_PAGE_404=] [default: ./public/404.html] + --page50x HTML file path for 50x errors [env: SERVER_ERROR_PAGE_50X=] [default: ./public/50x.html] + --port Host port [env: SERVER_PORT=] [default: 80] + --root Root directory path of static files [env: SERVER_ROOT=] [default: ./public] ``` ## Docker stack diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..48cca14 --- /dev/null +++ b/public/404.html @@ -0,0 +1,16 @@ + + + + + + + + 404 Not Found + + + +

404

+

Content was not found.

+ + + diff --git a/public/50x.html b/public/50x.html new file mode 100644 index 0000000..bfe4647 --- /dev/null +++ b/public/50x.html @@ -0,0 +1,16 @@ + + + + + + + + 50x Service Unavailable + + + +

50x

+

Service is temporarily unavailable.

+ + + diff --git a/src/config.rs b/src/config.rs index 24efe3e..a3ff87f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,4 +17,18 @@ pub struct Options { #[structopt(long, default_value = "./assets", env = "SERVER_ASSETS")] /// Assets directory path for add cache headers functionality pub assets: String, + #[structopt( + long, + default_value = "./public/50x.html", + env = "SERVER_ERROR_PAGE_50X" + )] + /// HTML file path for 50x errors + pub page50x: String, + #[structopt( + long, + default_value = "./public/404.html", + env = "SERVER_ERROR_PAGE_404" + )] + /// HTML file path for 404 errors + pub page404: String, } diff --git a/src/error_page.rs b/src/error_page.rs new file mode 100644 index 0000000..7a9e41a --- /dev/null +++ b/src/error_page.rs @@ -0,0 +1,61 @@ +extern crate iron; + +use iron::mime; +use iron::prelude::*; +use iron::status; +use iron::AfterMiddleware; +use std::fs; +use std::path::Path; + +/// Custom Error pages middleware for Iron +pub struct ErrorPage { + /// HTML file content for 50x errors. + pub page50x: std::string::String, + /// HTML file content for 404 errors. + pub page404: std::string::String, +} + +impl ErrorPage { + /// Create a new instance of `ErrorPage` middleware with a given html pages. + pub fn new>(page_50x_path: P, page_404_path: P) -> ErrorPage { + let page50x = fs::read_to_string(page_50x_path).unwrap(); + let page404 = fs::read_to_string(page_404_path).unwrap(); + + ErrorPage { page50x, page404 } + } +} + +impl AfterMiddleware for ErrorPage { + fn after(&self, _: &mut Request, res: Response) -> IronResult { + let content_type = "text/html".parse::().unwrap(); + + match res.status { + Some(status::NotFound) => Ok(Response::with(( + content_type, + status::NotFound, + self.page404.as_str(), + ))), + Some(status::InternalServerError) => Ok(Response::with(( + content_type, + status::InternalServerError, + self.page50x.as_str(), + ))), + Some(status::BadGateway) => Ok(Response::with(( + content_type, + status::BadGateway, + self.page50x.as_str(), + ))), + Some(status::ServiceUnavailable) => Ok(Response::with(( + content_type, + status::ServiceUnavailable, + self.page50x.as_str(), + ))), + Some(status::GatewayTimeout) => Ok(Response::with(( + content_type, + status::GatewayTimeout, + self.page50x.as_str(), + ))), + _ => Ok(res), + } + } +} diff --git a/src/main.rs b/src/main.rs index 84701c2..032caae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,19 +8,21 @@ extern crate iron_staticfile_middleware; extern crate log; extern crate structopt; +mod config; +mod error_page; +mod gzip; +mod logger; +mod staticfiles; + use crate::config::Options; use chrono::Local; use env_logger::Builder; use iron::prelude::*; use log::LevelFilter; +use staticfiles::*; use std::io::Write; use structopt::StructOpt; -mod config; -mod gzip; -mod logger; -mod staticfiles; - fn main() { Builder::new() .format(|buf, record| { @@ -37,9 +39,16 @@ fn main() { let opts = Options::from_args(); + let files = StaticFiles::new(StaticFilesOptions { + root_dir: opts.root, + assets_dir: opts.assets, + page_50x_path: opts.page50x, + page_404_path: opts.page404, + }); + let _address = &format!("{}{}{}", opts.host.to_string(), ":", opts.port.to_string()); - let _server = Iron::new(staticfiles::handler(opts.root, opts.assets)) + let _server = Iron::new(files.handle()) .http(_address) .expect("Unable to start the HTTP Server"); diff --git a/src/staticfiles.rs b/src/staticfiles.rs index 9c058f0..079faab 100644 --- a/src/staticfiles.rs +++ b/src/staticfiles.rs @@ -1,3 +1,4 @@ +use crate::error_page::ErrorPage; use crate::gzip::GzipMiddleware; use crate::logger::Logger; @@ -5,21 +6,48 @@ use iron::prelude::*; use iron_staticfile_middleware::{Cache, GuessContentType, ModifyWith, Prefix, Staticfile}; use std::time::Duration; -pub fn handler(root_dir: String, assets_dir: String) -> Chain { - let mut files = - Chain::new(Staticfile::new(root_dir).expect("Directory to serve files was not found")); +/// An Iron middleware for static files-serving. +pub struct StaticFiles { + opts: StaticFilesOptions, +} + +pub struct StaticFilesOptions { + pub root_dir: String, + pub assets_dir: String, + pub page_50x_path: String, + pub page_404_path: String, +} - let one_day = Duration::new(60 * 60 * 24, 0); - let one_year = Duration::new(60 * 60 * 24 * 365, 0); - let default_content_type = "text/html" - .parse() - .expect("Unable to create a default content type header"); +impl StaticFiles { + /// Create a new instance of `StaticFiles` with given options. + pub fn new(opts: StaticFilesOptions) -> StaticFiles { + StaticFiles { opts } + } - files.link_after(ModifyWith::new(Cache::new(one_day))); - files.link_after(Prefix::new(&[assets_dir], Cache::new(one_year))); - files.link_after(GuessContentType::new(default_content_type)); - files.link_after(GzipMiddleware); - files.link_after(Logger); + /// Handle static files for current `StaticFiles` middleware. + pub fn handle(&self) -> Chain { + let mut chain = Chain::new( + Staticfile::new(self.opts.root_dir.as_str()) + .expect("Directory to serve files was not found"), + ); + let one_day = Duration::new(60 * 60 * 24, 0); + let one_year = Duration::new(60 * 60 * 24 * 365, 0); + let default_content_type = "text/html" + .parse() + .expect("Unable to create a default content type header"); - files + chain.link_after(ModifyWith::new(Cache::new(one_day))); + chain.link_after(Prefix::new( + &[self.opts.assets_dir.as_str()], + Cache::new(one_year), + )); + chain.link_after(GuessContentType::new(default_content_type)); + chain.link_after(GzipMiddleware); + chain.link_after(Logger); + chain.link_after(ErrorPage::new( + self.opts.page_50x_path.as_str(), + self.opts.page_404_path.as_str(), + )); + chain + } } -- libgit2 1.7.2