index : static-web-server.git

ascending towards madness

author Jose Quintana <joseluisquintana20@gmail.com> 2020-03-04 1:10:31.0 +00:00:00
committer GitHub <noreply@github.com> 2020-03-04 1:10:31.0 +00:00:00
commit
45cb14b35aecff06f660bd3429026712cea695c9 [patch]
tree
7d59dd4d739ef51c7a519b7e3ec04953dd946894
parent
478d1009756ca2fe514ed857f60218998012ea6d
parent
a2a4e989a2f28ffe47ee479c5c7e6f680a3c920e
download
45cb14b35aecff06f660bd3429026712cea695c9.tar.gz

Merge pull request #14 from joseluisq/feature/ctrl_c_signals_handling_support

Add support for termination signals

Diff

 Cargo.lock            | 33 ++++++++++++++++++++++++++++++-
 Cargo.toml            |  2 ++-
 Makefile              |  7 +++++++-
 src/main.rs           | 38 +++++++++++++++++++++++------------
 src/signal_manager.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 123 insertions(+), 13 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index e42157b..f5dd5a2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -306,7 +306,7 @@ dependencies = [
[[package]]
name = "iron_staticfile_middleware"
version = "0.2.0"
source = "git+https://github.com/joseluisq/iron-staticfile-middleware.git#562b241a80a7e216ccdbe1d4d603f1a0b621d5b1"
source = "git+https://github.com/joseluisq/iron-staticfile-middleware.git#4e02bc6eda9cf88bf4b7866581ae83efda7f3110"
dependencies = [
 "iron",
 "log 0.4.8",
@@ -420,6 +420,19 @@ dependencies = [
]

[[package]]
name = "nix"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
dependencies = [
 "bitflags",
 "cc",
 "cfg-if",
 "libc",
 "void",
]

[[package]]
name = "num-integer"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -840,6 +853,16 @@ dependencies = [
]

[[package]]
name = "signal"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f6ce83b159ab6984d2419f495134972b48754d13ff2e3f8c998339942b56ed9"
dependencies = [
 "libc",
 "nix",
]

[[package]]
name = "siphasher"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -862,7 +885,9 @@ dependencies = [
 "iron",
 "iron_staticfile_middleware",
 "log 0.4.8",
 "nix",
 "openssl",
 "signal",
 "structopt",
]

@@ -1081,6 +1106,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"

[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"

[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 9cab7d8..50ad453 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,6 +29,8 @@ structopt = "0.3"
flate2 = "1.0"
iron_staticfile_middleware = { git = "https://github.com/joseluisq/iron-staticfile-middleware.git" }
hyper-native-tls = "0.3"
nix = "0.14"
signal = "0.7"

[dev-dependencies]
openssl = { version = "0.10", features = ["vendored"] }
diff --git a/Makefile b/Makefile
index 82147ca..39ba5a6 100644
--- a/Makefile
+++ b/Makefile
@@ -225,3 +225,10 @@ prod.release.dockerfiles:
	# Update docker files to latest tag per platform
	./docker/version.sh v$(PKG_TAG)
.ONESHELL: prod.release.dockerfiles

loadtest:
	@echo "GET http://localhost:1234" | \
		vegeta -cpus=12 attack -workers=10 -duration=5s -connections=10000 -rate=200 -http2=false > results.bin
	@cat results.bin | vegeta report -type='hist[0,2ms,4ms,6ms]'
	@cat results.bin | vegeta plot > plot.html
.PHONY: loadtest
diff --git a/src/main.rs b/src/main.rs
index cd0cfac..08c6c61 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,6 +4,8 @@ extern crate flate2;
extern crate hyper_native_tls;
extern crate iron;
extern crate iron_staticfile_middleware;
extern crate nix;
extern crate signal;

#[macro_use]
extern crate log;
@@ -13,6 +15,7 @@ mod config;
mod error_page;
mod gzip;
mod logger;
mod signal_manager;
mod staticfiles;

use crate::config::Options;
@@ -25,6 +28,23 @@ use staticfiles::*;
use std::io::Write;
use structopt::StructOpt;

fn on_server_running(server_name: &str, proto: &str, addr: &str) {
    // Notify when server is running
    info!(
        "Static {} Server `{}` is running on {}",
        proto, server_name, addr
    );

    // Wait for incoming signals (E.g Ctrl+C (SIGINT), SIGTERM, etc
    signal_manager::wait_for_signal(|sig: signal::Signal| {
        let code = signal_manager::signal_to_int(sig);

        println!();
        warn!("SIGINT {} caught. HTTP Server execution exited.", code);
        std::process::exit(code)
    })
}

fn main() {
    Builder::new()
        .format(|buf, record| {
@@ -40,6 +60,10 @@ fn main() {
        .init();

    let opts = Options::from_args();
    let addr = &format!("{}{}{}", opts.host.to_string(), ":", opts.port.to_string());
    let proto = if opts.tls { "HTTPS" } else { "HTTP" };

    // Configure & launch the HTTP server

    let files = StaticFiles::new(StaticFilesOptions {
        root_dir: opts.root,
@@ -48,26 +72,16 @@ fn main() {
        page_404_path: opts.page404,
    });

    let addr = &format!("{}{}{}", opts.host.to_string(), ":", opts.port.to_string());
    let proto = if opts.tls { "HTTPS" } else { "HTTP" };

    let server_info = |server_name: &String, proto: &str, addr: &String| {
        info!(
            "Static {} Server `{}` is running on {}",
            proto, server_name, addr
        );
    };

    if opts.tls {
        let ssl = NativeTlsServer::new(opts.tls_pkcs12, &opts.tls_pkcs12_passwd).unwrap();

        match Iron::new(files.handle()).https(addr, ssl) {
            Result::Ok(_) => server_info(&opts.name, &proto, addr),
            Result::Ok(_) => on_server_running(&opts.name, &proto, addr),
            Result::Err(err) => panic!("{:?}", err),
        }
    } else {
        match Iron::new(files.handle()).http(addr) {
            Result::Ok(_) => server_info(&opts.name, &proto, addr),
            Result::Ok(_) => on_server_running(&opts.name, &proto, addr),
            Result::Err(err) => panic!("{:?}", err),
        }
    }
diff --git a/src/signal_manager.rs b/src/signal_manager.rs
new file mode 100644
index 0000000..06be40c
--- /dev/null
+++ b/src/signal_manager.rs
@@ -0,0 +1,56 @@
use nix::errno::Errno;
use nix::libc::c_int;
use nix::sys::signal::{SIGCHLD, SIGINT, SIGTERM};
use nix::sys::wait::WaitStatus::{Exited, Signaled, StillAlive};
use nix::sys::wait::{waitpid, WaitPidFlag};
use nix::Error;
use signal::trap::Trap;

/// It waits for an incoming Termination Signal like Ctrl+C (SIGINT), SIGTERM, etc
pub fn wait_for_signal<F>(f: F)
where
    F: Fn(signal::Signal),
{
    let signal_trap = Trap::trap(&[SIGTERM, SIGINT, SIGCHLD]);

    for sig in signal_trap {
        match sig {
            SIGCHLD => {
                // Current std::process::Command ip does not have a way to find
                // process id, so we just wait until we have no children
                loop {
                    match waitpid(None, Some(WaitPidFlag::WNOHANG)) {
                        Ok(Exited(pid, status)) => {
                            println!("{} exited with status {}", pid, status);
                            continue;
                        }
                        Ok(Signaled(pid, sig, _)) => {
                            println!("{} killed by {}", pid, sig as c_int);
                            continue;
                        }
                        Ok(StillAlive) => {
                            break;
                        }
                        Ok(status) => {
                            println!("Temporary status {:?}", status);
                            continue;
                        }
                        Err(Error::Sys(Errno::ECHILD)) => {
                            return;
                        }
                        Err(e) => {
                            panic!("Error {:?}", e);
                        }
                    }
                }
            }

            sig => f(sig),
        }
    }
}

/// It casts a `signal::Signal` to `i32`
pub fn signal_to_int(sig: signal::Signal) -> i32 {
    sig as c_int
}