index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2019-12-24 0:27:21.0 +00:00:00
committer GitHub <noreply@github.com> 2019-12-24 0:27:21.0 +00:00:00
commit
464c376a2a22a375301fa9b5b0c02ac44a6f7a1a [patch]
tree
24302223becf45a2e2ba22131268c942557b36d8
parent
6fb65b2e9090c6302796300880ae78839e06576a
parent
1b3f2da29b6778fae92b35ab4f282b1b7238a4be
download
464c376a2a22a375301fa9b5b0c02ac44a6f7a1a.tar.gz

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(-)

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>    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
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 @@
<!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>
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 @@
<!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>
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<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),
        }
    }
}
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
    }
}