Merge pull request #5 from joseluisq/develop
Error page support (resolves #3 )
Diff
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(-)
@@ -40,7 +40,7 @@ test:
.PHONY: test
fmt:
@cargo fix --edition
@cargo fix
@cargo fmt --all
.PHONY: fmt
@@ -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> Assets directory path for add cache headers functionality [env: SERVER_ASSETS=] [default:
./assets]
--host <host> Host address (E.g 127.0.0.1) [env: SERVER_HOST=] [default: [::]]
--name <name> Name for server [env: SERVER_NAME=] [default: my-static-server]
--port <port> Host port [env: SERVER_PORT=] [default: 80]
--root <root> Root directory path of static files [env: SERVER_ROOT=]
--assets <assets> Assets directory path for add cache headers functionality [env: SERVER_ASSETS=]
[default: ./assets]
--host <host> Host address (E.g 127.0.0.1) [env: SERVER_HOST=] [default: [::]]
--name <name> Name for server [env: SERVER_NAME=] [default: my-static-server]
--page404 <page404> HTML file path for 404 errors [env: SERVER_ERROR_PAGE_404=] [default: ./public/404.html]
--page50x <page50x> HTML file path for 50x errors [env: SERVER_ERROR_PAGE_50X=] [default: ./public/50x.html]
--port <port> Host port [env: SERVER_PORT=] [default: 80]
--root <root> Root directory path of static files [env: SERVER_ROOT=] [default: ./public]
```
## Docker stack
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>404 Not Found</title>
</head>
<body>
<h1>404</h1>
<p>Content was not found.</p>
</body>
</html>
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>50x Service Unavailable</title>
</head>
<body>
<h1>50x</h1>
<p>Service is temporarily unavailable.</p>
</body>
</html>
@@ -17,4 +17,18 @@ pub struct Options {
#[structopt(long, default_value = "./assets", env = "SERVER_ASSETS")]
pub assets: String,
#[structopt(
long,
default_value = "./public/50x.html",
env = "SERVER_ERROR_PAGE_50X"
)]
pub page50x: String,
#[structopt(
long,
default_value = "./public/404.html",
env = "SERVER_ERROR_PAGE_404"
)]
pub page404: String,
}
@@ -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;
pub struct ErrorPage {
pub page50x: std::string::String,
pub page404: std::string::String,
}
impl ErrorPage {
pub fn new<P: AsRef<Path>>(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<Response> {
let content_type = "text/html".parse::<mime::Mime>().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),
}
}
}
@@ -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");
@@ -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"));
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 {
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);
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
}
}