Add 'keep_alive' configuration parameter.

The 'keep_alive' configuration parameter allows HTTP keep-alive timeouts
to be configured or for keep-alive to be disabled entirely.
This commit is contained in:
Sergio Benitez 2018-02-17 04:06:05 -08:00
parent c6841ba67a
commit c620411d92
7 changed files with 189 additions and 5 deletions

View File

@ -10,6 +10,7 @@ msgpack = 1048576 # this is an extra used by the msgpack contrib module
address = "localhost" address = "localhost"
port = 8000 port = 8000
workers = 1 workers = 1
keep_alive = 5
log = "normal" log = "normal"
hi = "Hello!" # this is an unused extra; maybe application specific? hi = "Hello!" # this is an unused extra; maybe application specific?
is_extra = true # this is an unused extra; maybe application specific? is_extra = true # this is an unused extra; maybe application specific?
@ -17,8 +18,9 @@ is_extra = true # this is an unused extra; maybe application specific?
[staging] [staging]
address = "0.0.0.0" address = "0.0.0.0"
port = 8000 port = 8000
log = "normal"
workers = 8 workers = 8
keep_alive = 5
log = "normal"
# don't use this key! generate your own and keep it private! # don't use this key! generate your own and keep it private!
secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=" secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
@ -26,6 +28,7 @@ secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
address = "0.0.0.0" address = "0.0.0.0"
port = 8000 port = 8000
workers = 12 workers = 12
keep_alive = 5
log = "critical" log = "critical"
# don't use this key! generate your own and keep it private! # don't use this key! generate your own and keep it private!
secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk=" secret_key = "hPRYyVRiMyxpw5sBB1XeCMN1kFsDCqKvBi2QJxBVHQk="

View File

@ -14,6 +14,8 @@ pub struct ConfigBuilder {
pub port: u16, pub port: u16,
/// The number of workers to run in parallel. /// The number of workers to run in parallel.
pub workers: u16, pub workers: u16,
/// Keep-alive timeout in seconds or None if disabled.
pub keep_alive: Option<u32>,
/// How much information to log. /// How much information to log.
pub log_level: LoggingLevel, pub log_level: LoggingLevel,
/// The secret key. /// The secret key.
@ -63,6 +65,7 @@ impl ConfigBuilder {
address: config.address, address: config.address,
port: config.port, port: config.port,
workers: config.workers, workers: config.workers,
keep_alive: config.keep_alive,
log_level: config.log_level, log_level: config.log_level,
secret_key: None, secret_key: None,
tls: None, tls: None,
@ -128,6 +131,32 @@ impl ConfigBuilder {
self self
} }
/// Set the keep-alive timeout to `timeout` seconds. If `timeout` is `None`,
/// keep-alive is disabled.
///
/// # Example
///
/// ```rust
/// use rocket::config::{Config, Environment};
///
/// let config = Config::build(Environment::Staging)
/// .keep_alive(10)
/// .unwrap();
///
/// assert_eq!(config.keep_alive, Some(10));
///
/// let config = Config::build(Environment::Staging)
/// .keep_alive(None)
/// .unwrap();
///
/// assert_eq!(config.keep_alive, None);
/// ```
#[inline]
pub fn keep_alive<T: Into<Option<u32>>>(mut self, timeout: T) -> Self {
self.keep_alive = timeout.into();
self
}
/// Sets the `log_level` in the configuration being built. /// Sets the `log_level` in the configuration being built.
/// ///
/// # Example /// # Example
@ -283,6 +312,7 @@ impl ConfigBuilder {
/// .address("127.0.0.1") /// .address("127.0.0.1")
/// .port(700) /// .port(700)
/// .workers(12) /// .workers(12)
/// .keep_alive(None)
/// .finalize(); /// .finalize();
/// ///
/// assert!(config.is_ok()); /// assert!(config.is_ok());
@ -298,6 +328,7 @@ impl ConfigBuilder {
config.set_address(self.address)?; config.set_address(self.address)?;
config.set_port(self.port); config.set_port(self.port);
config.set_workers(self.workers); config.set_workers(self.workers);
config.set_keep_alive(self.keep_alive);
config.set_log_level(self.log_level); config.set_log_level(self.log_level);
config.set_extras(self.extras); config.set_extras(self.extras);
config.set_root(self.root); config.set_root(self.root);

View File

@ -47,6 +47,8 @@ pub struct Config {
pub port: u16, pub port: u16,
/// The number of workers to run concurrently. /// The number of workers to run concurrently.
pub workers: u16, pub workers: u16,
/// Keep-alive timeout in seconds or None if disabled.
pub keep_alive: Option<u32>,
/// How much information to log. /// How much information to log.
pub log_level: LoggingLevel, pub log_level: LoggingLevel,
/// The secret key. /// The secret key.
@ -63,7 +65,7 @@ pub struct Config {
macro_rules! config_from_raw { macro_rules! config_from_raw {
($config:expr, $name:expr, $value:expr, ($config:expr, $name:expr, $value:expr,
$($key:ident => ($type:ident, $set:ident, $map:expr)),+ | _ => $rest:expr) => ( $($key:ident => ($type:ident, $set:ident, $map:expr),)+ | _ => $rest:expr) => (
match $name { match $name {
$(stringify!($key) => { $(stringify!($key) => {
super::custom_values::$type($config, $name, $value) super::custom_values::$type($config, $name, $value)
@ -213,6 +215,7 @@ impl Config {
address: "localhost".to_string(), address: "localhost".to_string(),
port: 8000, port: 8000,
workers: default_workers, workers: default_workers,
keep_alive: Some(5),
log_level: LoggingLevel::Normal, log_level: LoggingLevel::Normal,
secret_key: key, secret_key: key,
tls: None, tls: None,
@ -227,6 +230,7 @@ impl Config {
address: "0.0.0.0".to_string(), address: "0.0.0.0".to_string(),
port: 8000, port: 8000,
workers: default_workers, workers: default_workers,
keep_alive: Some(5),
log_level: LoggingLevel::Normal, log_level: LoggingLevel::Normal,
secret_key: key, secret_key: key,
tls: None, tls: None,
@ -241,6 +245,7 @@ impl Config {
address: "0.0.0.0".to_string(), address: "0.0.0.0".to_string(),
port: 8000, port: 8000,
workers: default_workers, workers: default_workers,
keep_alive: Some(5),
log_level: LoggingLevel::Critical, log_level: LoggingLevel::Critical,
secret_key: key, secret_key: key,
tls: None, tls: None,
@ -275,6 +280,7 @@ impl Config {
/// * **address**: String /// * **address**: String
/// * **port**: Integer (16-bit unsigned) /// * **port**: Integer (16-bit unsigned)
/// * **workers**: Integer (16-bit unsigned) /// * **workers**: Integer (16-bit unsigned)
/// * **keep_alive**: Integer or Boolean (false) or String ('none')
/// * **log**: String /// * **log**: String
/// * **secret_key**: String (192-bit base64) /// * **secret_key**: String (192-bit base64)
/// * **tls**: Table (`certs` (path as String), `key` (path as String)) /// * **tls**: Table (`certs` (path as String), `key` (path as String))
@ -284,10 +290,11 @@ impl Config {
address => (str, set_address, id), address => (str, set_address, id),
port => (u16, set_port, ok), port => (u16, set_port, ok),
workers => (u16, set_workers, ok), workers => (u16, set_workers, ok),
secret_key => (str, set_secret_key, id), keep_alive => (u32_option, set_keep_alive, ok),
log => (log_level, set_log_level, ok), log => (log_level, set_log_level, ok),
secret_key => (str, set_secret_key, id),
tls => (tls_config, set_raw_tls, id), tls => (tls_config, set_raw_tls, id),
limits => (limits, set_limits, ok) limits => (limits, set_limits, ok),
| _ => { | _ => {
self.extras.insert(name.into(), val.clone()); self.extras.insert(name.into(), val.clone());
Ok(()) Ok(())
@ -390,6 +397,31 @@ impl Config {
self.workers = workers; self.workers = workers;
} }
/// Set the keep-alive timeout to `timeout` seconds. If `timeout` is `None`,
/// keep-alive is disabled.
///
/// # Example
///
/// ```rust
/// use rocket::config::Config;
///
/// # use rocket::config::ConfigError;
/// # fn config_test() -> Result<(), ConfigError> {
/// let mut config = Config::development()?;
///
/// // Set keep-alive timeout to 10 seconds.
/// config.set_keep_alive(10);
///
/// // Disable keep-alive.
/// config.set_keep_alive(None);
/// # Ok(())
/// # }
/// ```
#[inline]
pub fn set_keep_alive<T: Into<Option<u32>>>(&mut self, timeout: T) {
self.keep_alive = timeout.into();
}
/// Sets the `secret_key` in `self` to `key` which must be a 192-bit base64 /// Sets the `secret_key` in `self` to `key` which must be a 192-bit base64
/// encoded string. /// encoded string.
/// ///
@ -853,6 +885,7 @@ impl fmt::Debug for Config {
s.field("address", &self.address); s.field("address", &self.address);
s.field("port", &self.port); s.field("port", &self.port);
s.field("workers", &self.workers); s.field("workers", &self.workers);
s.field("keep_alive", &self.keep_alive);
s.field("log_level", &self.log_level); s.field("log_level", &self.log_level);
for (key, value) in self.extras() { for (key, value) in self.extras() {
@ -870,6 +903,7 @@ impl PartialEq for Config {
&& self.port == other.port && self.port == other.port
&& self.workers == other.workers && self.workers == other.workers
&& self.log_level == other.log_level && self.log_level == other.log_level
&& self.keep_alive == other.keep_alive
&& self.environment == other.environment && self.environment == other.environment
&& self.extras == other.extras && self.extras == other.extras
} }

View File

@ -3,6 +3,7 @@ use std::fmt;
#[cfg(feature = "tls")] use rustls::{Certificate, PrivateKey}; #[cfg(feature = "tls")] use rustls::{Certificate, PrivateKey};
use config::{Result, Config, Value, ConfigError, LoggingLevel}; use config::{Result, Config, Value, ConfigError, LoggingLevel};
use http::uncased::uncased_eq;
use http::Key; use http::Key;
#[derive(Clone)] #[derive(Clone)]
@ -260,3 +261,21 @@ pub fn limits(conf: &Config, name: &str, value: &Value) -> Result<Limits> {
Ok(limits) Ok(limits)
} }
pub fn u32_option(conf: &Config, name: &str, value: &Value) -> Result<Option<u32>> {
let expect = "a 32-bit unsigned integer or 'none' or 'false'";
let err = Err(conf.bad_type(name, value.type_str(), expect));
match value.as_integer() {
Some(x) if x >= 0 && x <= (u32::max_value() as i64) => Ok(Some(x as u32)),
Some(_) => err,
None => match value.as_str() {
Some(v) if uncased_eq(v, "none") => Ok(None),
Some(_) => err,
_ => match value.as_bool() {
Some(false) => Ok(None),
_ => err
}
}
}
}

View File

@ -38,6 +38,9 @@
//! * examples: `"8000"`, `"80"`, `"4242"` //! * examples: `"8000"`, `"80"`, `"4242"`
//! * **workers**: _[integer]_ the number of concurrent workers to use //! * **workers**: _[integer]_ the number of concurrent workers to use
//! * examples: `12`, `1`, `4` //! * examples: `12`, `1`, `4`
//! * **keep_alive**: _[integer, 'false', or 'none']_ timeout, in seconds, for
//! HTTP keep-alive. disabled on 'false' or 'none'
//! * examples: `5`, `60`, `false`, `"none"`
//! * **log**: _[string]_ how much information to log; one of `"normal"`, //! * **log**: _[string]_ how much information to log; one of `"normal"`,
//! `"debug"`, or `"critical"` //! `"debug"`, or `"critical"`
//! * **secret_key**: _[string]_ a 256-bit base64 encoded string (44 //! * **secret_key**: _[string]_ a 256-bit base64 encoded string (44
@ -72,6 +75,7 @@
//! address = "localhost" //! address = "localhost"
//! port = 8000 //! port = 8000
//! workers = [number_of_cpus * 2] //! workers = [number_of_cpus * 2]
//! keep_alive = 5
//! log = "normal" //! log = "normal"
//! secret_key = [randomly generated at launch] //! secret_key = [randomly generated at launch]
//! limits = { forms = 32768 } //! limits = { forms = 32768 }
@ -80,6 +84,7 @@
//! address = "0.0.0.0" //! address = "0.0.0.0"
//! port = 8000 //! port = 8000
//! workers = [number_of_cpus * 2] //! workers = [number_of_cpus * 2]
//! keep_alive = 5
//! log = "normal" //! log = "normal"
//! secret_key = [randomly generated at launch] //! secret_key = [randomly generated at launch]
//! limits = { forms = 32768 } //! limits = { forms = 32768 }
@ -88,6 +93,7 @@
//! address = "0.0.0.0" //! address = "0.0.0.0"
//! port = 8000 //! port = 8000
//! workers = [number_of_cpus * 2] //! workers = [number_of_cpus * 2]
//! keep_alive = 5
//! log = "critical" //! log = "critical"
//! secret_key = [randomly generated at launch] //! secret_key = [randomly generated at launch]
//! limits = { forms = 32768 } //! limits = { forms = 32768 }
@ -601,6 +607,7 @@ mod test {
port = 7810 port = 7810
workers = 21 workers = 21
log = "critical" log = "critical"
keep_alive = false
secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=" secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
template_dir = "mine" template_dir = "mine"
json = true json = true
@ -612,6 +619,7 @@ mod test {
.port(7810) .port(7810)
.workers(21) .workers(21)
.log_level(LoggingLevel::Critical) .log_level(LoggingLevel::Critical)
.keep_alive(None)
.secret_key("8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=") .secret_key("8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=")
.extra("template_dir", "mine") .extra("template_dir", "mine")
.extra("json", true) .extra("json", true)
@ -886,6 +894,82 @@ mod test {
"#.to_string(), TEST_CONFIG_FILENAME).is_err()); "#.to_string(), TEST_CONFIG_FILENAME).is_err());
} }
#[test]
fn test_good_keep_alives() {
// Take the lock so changing the environment doesn't cause races.
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "stage");
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = 10
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(10)
});
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = 0
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(0)
});
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = 348
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(348)
});
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = false
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(None)
});
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = "none"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(None)
});
check_config!(RocketConfig::parse(r#"
[stage]
keep_alive = "None"
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(None)
});
}
#[test]
fn test_bad_keep_alives() {
// Take the lock so changing the environment doesn't cause races.
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
assert!(RocketConfig::parse(r#"
[dev]
keep_alive = true
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
[dev]
keep_alive = -10
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
[dev]
keep_alive = "Some(10)"
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
assert!(RocketConfig::parse(r#"
[dev]
keep_alive = 4294967296
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
}
#[test] #[test]
fn test_good_log_levels() { fn test_good_log_levels() {
// Take the lock so changing the environment doesn't cause races. // Take the lock so changing the environment doesn't cause races.

View File

@ -4,6 +4,7 @@ use std::collections::BTreeMap;
use config::Value; use config::Value;
pub fn parse_simple_toml_value(string: &str) -> Result<Value, &'static str> { pub fn parse_simple_toml_value(string: &str) -> Result<Value, &'static str> {
let string = string.trim();
if string.is_empty() { if string.is_empty() {
return Err("value is empty") return Err("value is empty")
} }

View File

@ -2,12 +2,15 @@ use std::collections::HashMap;
use std::str::from_utf8; use std::str::from_utf8;
use std::cmp::min; use std::cmp::min;
use std::io::{self, Write}; use std::io::{self, Write};
use std::time::Duration;
use std::mem; use std::mem;
use yansi::Paint; use yansi::Paint;
use state::Container; use state::Container;
#[cfg(feature = "tls")] use hyper_sync_rustls::TlsServer; #[cfg(feature = "tls")]
use hyper_sync_rustls::TlsServer;
use {logger, handler}; use {logger, handler};
use ext::ReadExt; use ext::ReadExt;
use config::{self, Config, LoggedValue}; use config::{self, Config, LoggedValue};
@ -383,6 +386,11 @@ impl Rocket {
launch_info_!("secret key: {}", Paint::white(&config.secret_key)); launch_info_!("secret key: {}", Paint::white(&config.secret_key));
launch_info_!("limits: {}", Paint::white(&config.limits)); launch_info_!("limits: {}", Paint::white(&config.limits));
match config.keep_alive {
Some(v) => launch_info_!("keep-alive: {}", Paint::white(format!("{}s", v))),
None => launch_info_!("keep-alive: {}", Paint::white("disabled")),
}
let tls_configured = config.tls.is_some(); let tls_configured = config.tls.is_some();
if tls_configured && cfg!(feature = "tls") { if tls_configured && cfg!(feature = "tls") {
launch_info_!("tls: {}", Paint::white("enabled")); launch_info_!("tls: {}", Paint::white("enabled"));
@ -669,6 +677,10 @@ impl Rocket {
Err(e) => return LaunchError::from(e), Err(e) => return LaunchError::from(e),
} }
// Set the keep-alive.
let timeout = self.config.keep_alive.map(|s| Duration::from_secs(s as u64));
server.keep_alive(timeout);
// Run the launch fairings. // Run the launch fairings.
self.fairings.handle_launch(&self); self.fairings.handle_launch(&self);