From 45cb14b35aecff06f660bd3429026712cea695c9 Mon Sep 17 00:00:00 2001 From: Jose Quintana Date: Wed, 4 Mar 2020 02:10:31 +0100 Subject: [PATCH] Merge pull request #14 from joseluisq/feature/ctrl_c_signals_handling_support Add support for termination signals --- Cargo.lock | 33 ++++++++++++++++++++++++++++++++- Cargo.toml | 2 ++ Makefile | 7 +++++++ src/main.rs | 38 ++++++++++++++++++++++++++------------ src/signal_manager.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 src/signal_manager.rs 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) +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 +} -- libgit2 1.7.2