Skip to content

Commit

Permalink
Allow read/write timeouts to be configured.
Browse files Browse the repository at this point in the history
Resolves #1472.
  • Loading branch information
SergioBenitez committed Nov 9, 2020
1 parent 3970783 commit 86bd7c1
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 23 deletions.
62 changes: 62 additions & 0 deletions core/lib/src/config/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ pub struct ConfigBuilder {
pub workers: u16,
/// Keep-alive timeout in seconds or disabled if 0.
pub keep_alive: u32,
/// Number of seconds to wait without _receiving_ data before closing a
/// connection; disabled when `None`.
pub read_timeout: u32,
/// Number of seconds to wait without _sending_ data before closing a
/// connection; disabled when `None`.
pub write_timeout: u32,
/// How much information to log.
pub log_level: LoggingLevel,
/// The secret key.
Expand Down Expand Up @@ -57,6 +63,8 @@ impl ConfigBuilder {
port: config.port,
workers: config.workers,
keep_alive: config.keep_alive.unwrap_or(0),
read_timeout: config.read_timeout.unwrap_or(0),
write_timeout: config.write_timeout.unwrap_or(0),
log_level: config.log_level,
secret_key: None,
tls: None,
Expand Down Expand Up @@ -148,6 +156,58 @@ impl ConfigBuilder {
self
}

/// Sets the read timeout to `timeout` seconds. If `timeout` is `0`,
/// read timeouts are disabled.
///
/// # Example
///
/// ```rust
/// use rocket::config::{Config, Environment};
///
/// let config = Config::build(Environment::Staging)
/// .read_timeout(10)
/// .unwrap();
///
/// assert_eq!(config.read_timeout, Some(10));
///
/// let config = Config::build(Environment::Staging)
/// .read_timeout(0)
/// .unwrap();
///
/// assert_eq!(config.read_timeout, None);
/// ```
#[inline]
pub fn read_timeout(mut self, timeout: u32) -> Self {
self.read_timeout = timeout;
self
}

/// Sets the write timeout to `timeout` seconds. If `timeout` is `0`,
/// write timeouts are disabled.
///
/// # Example
///
/// ```rust
/// use rocket::config::{Config, Environment};
///
/// let config = Config::build(Environment::Staging)
/// .write_timeout(10)
/// .unwrap();
///
/// assert_eq!(config.write_timeout, Some(10));
///
/// let config = Config::build(Environment::Staging)
/// .write_timeout(0)
/// .unwrap();
///
/// assert_eq!(config.write_timeout, None);
/// ```
#[inline]
pub fn write_timeout(mut self, timeout: u32) -> Self {
self.write_timeout = timeout;
self
}

/// Sets the `log_level` in the configuration being built.
///
/// # Example
Expand Down Expand Up @@ -318,6 +378,8 @@ impl ConfigBuilder {
config.set_port(self.port);
config.set_workers(self.workers);
config.set_keep_alive(self.keep_alive);
config.set_read_timeout(self.read_timeout);
config.set_write_timeout(self.write_timeout);
config.set_log_level(self.log_level);
config.set_extras(self.extras);
config.set_limits(self.limits);
Expand Down
68 changes: 68 additions & 0 deletions core/lib/src/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ pub struct Config {
pub workers: u16,
/// Keep-alive timeout in seconds or None if disabled.
pub keep_alive: Option<u32>,
/// Number of seconds to wait without _receiving_ data before closing a
/// connection; disabled when `None`.
pub read_timeout: Option<u32>,
/// Number of seconds to wait without _sending_ data before closing a
/// connection; disabled when `None`.
pub write_timeout: Option<u32>,
/// How much information to log.
pub log_level: LoggingLevel,
/// The secret key.
Expand Down Expand Up @@ -229,6 +235,8 @@ impl Config {
port: 8000,
workers: default_workers,
keep_alive: Some(5),
read_timeout: Some(5),
write_timeout: Some(5),
log_level: LoggingLevel::Normal,
secret_key: key,
tls: None,
Expand All @@ -245,6 +253,8 @@ impl Config {
port: 8000,
workers: default_workers,
keep_alive: Some(5),
read_timeout: Some(5),
write_timeout: Some(5),
log_level: LoggingLevel::Normal,
secret_key: key,
tls: None,
Expand All @@ -261,6 +271,8 @@ impl Config {
port: 8000,
workers: default_workers,
keep_alive: Some(5),
read_timeout: Some(5),
write_timeout: Some(5),
log_level: LoggingLevel::Critical,
secret_key: key,
tls: None,
Expand Down Expand Up @@ -307,6 +319,8 @@ impl Config {
port => (u16, set_port, ok),
workers => (u16, set_workers, ok),
keep_alive => (u32, set_keep_alive, ok),
read_timeout => (u32, set_read_timeout, ok),
write_timeout => (u32, set_write_timeout, ok),
log => (log_level, set_log_level, ok),
secret_key => (str, set_secret_key, id),
tls => (tls_config, set_raw_tls, id),
Expand Down Expand Up @@ -422,6 +436,60 @@ impl Config {
}
}

/// Sets the read timeout to `timeout` seconds. If `timeout` is `0`, read
/// timeouts are disabled.
///
/// # Example
///
/// ```rust
/// use rocket::config::Config;
///
/// let mut config = Config::development();
///
/// // Set read timeout to 10 seconds.
/// config.set_read_timeout(10);
/// assert_eq!(config.read_timeout, Some(10));
///
/// // Disable read timeouts.
/// config.set_read_timeout(0);
/// assert_eq!(config.read_timeout, None);
/// ```
#[inline]
pub fn set_read_timeout(&mut self, timeout: u32) {
if timeout == 0 {
self.read_timeout = None;
} else {
self.read_timeout = Some(timeout);
}
}

/// Sets the write timeout to `timeout` seconds. If `timeout` is `0`, write
/// timeouts are disabled.
///
/// # Example
///
/// ```rust
/// use rocket::config::Config;
///
/// let mut config = Config::development();
///
/// // Set write timeout to 10 seconds.
/// config.set_write_timeout(10);
/// assert_eq!(config.write_timeout, Some(10));
///
/// // Disable write timeouts.
/// config.set_write_timeout(0);
/// assert_eq!(config.write_timeout, None);
/// ```
#[inline]
pub fn set_write_timeout(&mut self, timeout: u32) {
if timeout == 0 {
self.write_timeout = None;
} else {
self.write_timeout = Some(timeout);
}
}

/// Sets the `secret_key` in `self` to `key` which must be a 256-bit base64
/// encoded string.
///
Expand Down
64 changes: 50 additions & 14 deletions core/lib/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,20 @@
//! not used by Rocket itself but can be used by external libraries. The
//! standard configuration parameters are:
//!
//! | name | type | description | examples |
//! |------------|----------------|-------------------------------------------------------------|----------------------------|
//! | address | string | ip address or host to listen on | `"localhost"`, `"1.2.3.4"` |
//! | port | integer | port number to listen on | `8000`, `80` |
//! | keep_alive | integer | keep-alive timeout in seconds | `0` (disable), `10` |
//! | workers | integer | number of concurrent thread workers | `36`, `512` |
//! | log | string | max log level: `"off"`, `"normal"`, `"debug"`, `"critical"` | `"off"`, `"normal"` |
//! | secret_key | 256-bit base64 | secret key for private cookies | `"8Xui8SI..."` (44 chars) |
//! | tls | table | tls config table with two keys (`certs`, `key`) | _see below_ |
//! | tls.certs | string | path to certificate chain in PEM format | `"private/cert.pem"` |
//! | tls.key | string | path to private key for `tls.certs` in PEM format | `"private/key.pem"` |
//! | limits | table | map from data type (string) to data limit (integer: bytes) | `{ forms = 65536 }` |
//! | name | type | description | examples |
//! |------------ |----------------|-------------------------------------------------------------|----------------------------|
//! | address | string | ip address or host to listen on | `"localhost"`, `"1.2.3.4"` |
//! | port | integer | port number to listen on | `8000`, `80` |
//! | keep_alive | integer | keep-alive timeout in seconds | `0` (disable), `10` |
//! | read_timeout | integer | data read timeout in seconds | `0` (disable), `5` |
//! | write_timeout | integer | data write timeout in seconds | `0` (disable), `5` |
//! | workers | integer | number of concurrent thread workers | `36`, `512` |
//! | log | string | max log level: `"off"`, `"normal"`, `"debug"`, `"critical"` | `"off"`, `"normal"` |
//! | secret_key | 256-bit base64 | secret key for private cookies | `"8Xui8SI..."` (44 chars) |
//! | tls | table | tls config table with two keys (`certs`, `key`) | _see below_ |
//! | tls.certs | string | path to certificate chain in PEM format | `"private/cert.pem"` |
//! | tls.key | string | path to private key for `tls.certs` in PEM format | `"private/key.pem"` |
//! | limits | table | map from data type (string) to data limit (integer: bytes) | `{ forms = 65536 }` |
//!
//! ### Rocket.toml
//!
Expand All @@ -64,6 +66,8 @@
//! port = 8000
//! workers = [number_of_cpus * 2]
//! keep_alive = 5
//! read_timeout = 5
//! write_timeout = 5
//! log = "normal"
//! secret_key = [randomly generated at launch]
//! limits = { forms = 32768 }
Expand All @@ -73,6 +77,8 @@
//! port = 8000
//! workers = [number_of_cpus * 2]
//! keep_alive = 5
//! read_timeout = 5
//! write_timeout = 5
//! log = "normal"
//! secret_key = [randomly generated at launch]
//! limits = { forms = 32768 }
Expand All @@ -82,6 +88,8 @@
//! port = 8000
//! workers = [number_of_cpus * 2]
//! keep_alive = 5
//! read_timeout = 5
//! write_timeout = 5
//! log = "critical"
//! secret_key = [randomly generated at launch]
//! limits = { forms = 32768 }
Expand Down Expand Up @@ -579,6 +587,8 @@ mod test {
workers = 21
log = "critical"
keep_alive = 0
read_timeout = 1
write_timeout = 0
secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
template_dir = "mine"
json = true
Expand All @@ -591,6 +601,8 @@ mod test {
.workers(21)
.log_level(LoggingLevel::Critical)
.keep_alive(0)
.read_timeout(1)
.write_timeout(0)
.secret_key("8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=")
.extra("template_dir", "mine")
.extra("json", true)
Expand Down Expand Up @@ -866,7 +878,7 @@ mod test {
}

#[test]
fn test_good_keep_alives() {
fn test_good_keep_alives_and_timeouts() {
// Take the lock so changing the environment doesn't cause races.
let _env_lock = ENV_LOCK.lock().unwrap();
env::set_var(CONFIG_ENV, "stage");
Expand Down Expand Up @@ -898,10 +910,24 @@ mod test {
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).keep_alive(0)
});

check_config!(RocketConfig::parse(r#"
[stage]
read_timeout = 10
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).read_timeout(10)
});

check_config!(RocketConfig::parse(r#"
[stage]
write_timeout = 4
"#.to_string(), TEST_CONFIG_FILENAME), {
default_config(Staging).write_timeout(4)
});
}

#[test]
fn test_bad_keep_alives() {
fn test_bad_keep_alives_and_timeouts() {
// Take the lock so changing the environment doesn't cause races.
let _env_lock = ENV_LOCK.lock().unwrap();
env::remove_var(CONFIG_ENV);
Expand All @@ -925,6 +951,16 @@ mod test {
[dev]
keep_alive = 4294967296
"#.to_string(), TEST_CONFIG_FILENAME).is_err());

assert!(RocketConfig::parse(r#"
[dev]
read_timeout = true
"#.to_string(), TEST_CONFIG_FILENAME).is_err());

assert!(RocketConfig::parse(r#"
[dev]
write_timeout = None
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
}

#[test]
Expand Down
8 changes: 6 additions & 2 deletions core/lib/src/data/data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,10 @@ impl Data {
}

// FIXME: This is absolutely terrible (downcasting!), thanks to Hyper.
crate fn from_hyp(mut body: HyperBodyReader) -> Result<Data, &'static str> {
crate fn from_hyp(
req: &crate::Request<'_>,
mut body: HyperBodyReader
) -> Result<Data, &'static str> {
#[inline(always)]
#[cfg(feature = "tls")]
fn concrete_stream(stream: &mut dyn NetworkStream) -> Option<NetStream> {
Expand All @@ -117,7 +120,8 @@ impl Data {
};

// Set the read timeout to 5 seconds.
let _ = net_stream.set_read_timeout(Some(Duration::from_secs(5)));
let timeout = req.state.config.read_timeout.map(|s| Duration::from_secs(s as u64));
let _ = net_stream.set_read_timeout(timeout);

// Steal the internal, undecoded data buffer from Hyper.
let (mut hyper_buf, pos, cap) = body.get_mut().take_buf();
Expand Down
1 change: 0 additions & 1 deletion core/lib/src/response/responder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,6 @@ impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
/// If `self` is `Ok`, responds with the wrapped `Responder`. Otherwise prints
/// an error message with the `Err` value returns an `Err` of
/// `Status::InternalServerError`.
#[deprecated(since = "0.4.3")]
impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
default fn respond_to(self, req: &Request) -> response::Result<'r> {
self.map(|r| r.respond_to(req)).unwrap_or_else(|e| {
Expand Down
Loading

0 comments on commit 86bd7c1

Please sign in to comment.