Skip to content

Commit 86bd7c1

Browse files
committed
Allow read/write timeouts to be configured.
Resolves #1472.
1 parent 3970783 commit 86bd7c1

File tree

9 files changed

+215
-23
lines changed

9 files changed

+215
-23
lines changed

core/lib/src/config/builder.rs

+62
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ pub struct ConfigBuilder {
1616
pub workers: u16,
1717
/// Keep-alive timeout in seconds or disabled if 0.
1818
pub keep_alive: u32,
19+
/// Number of seconds to wait without _receiving_ data before closing a
20+
/// connection; disabled when `None`.
21+
pub read_timeout: u32,
22+
/// Number of seconds to wait without _sending_ data before closing a
23+
/// connection; disabled when `None`.
24+
pub write_timeout: u32,
1925
/// How much information to log.
2026
pub log_level: LoggingLevel,
2127
/// The secret key.
@@ -57,6 +63,8 @@ impl ConfigBuilder {
5763
port: config.port,
5864
workers: config.workers,
5965
keep_alive: config.keep_alive.unwrap_or(0),
66+
read_timeout: config.read_timeout.unwrap_or(0),
67+
write_timeout: config.write_timeout.unwrap_or(0),
6068
log_level: config.log_level,
6169
secret_key: None,
6270
tls: None,
@@ -148,6 +156,58 @@ impl ConfigBuilder {
148156
self
149157
}
150158

159+
/// Sets the read timeout to `timeout` seconds. If `timeout` is `0`,
160+
/// read timeouts are disabled.
161+
///
162+
/// # Example
163+
///
164+
/// ```rust
165+
/// use rocket::config::{Config, Environment};
166+
///
167+
/// let config = Config::build(Environment::Staging)
168+
/// .read_timeout(10)
169+
/// .unwrap();
170+
///
171+
/// assert_eq!(config.read_timeout, Some(10));
172+
///
173+
/// let config = Config::build(Environment::Staging)
174+
/// .read_timeout(0)
175+
/// .unwrap();
176+
///
177+
/// assert_eq!(config.read_timeout, None);
178+
/// ```
179+
#[inline]
180+
pub fn read_timeout(mut self, timeout: u32) -> Self {
181+
self.read_timeout = timeout;
182+
self
183+
}
184+
185+
/// Sets the write timeout to `timeout` seconds. If `timeout` is `0`,
186+
/// write timeouts are disabled.
187+
///
188+
/// # Example
189+
///
190+
/// ```rust
191+
/// use rocket::config::{Config, Environment};
192+
///
193+
/// let config = Config::build(Environment::Staging)
194+
/// .write_timeout(10)
195+
/// .unwrap();
196+
///
197+
/// assert_eq!(config.write_timeout, Some(10));
198+
///
199+
/// let config = Config::build(Environment::Staging)
200+
/// .write_timeout(0)
201+
/// .unwrap();
202+
///
203+
/// assert_eq!(config.write_timeout, None);
204+
/// ```
205+
#[inline]
206+
pub fn write_timeout(mut self, timeout: u32) -> Self {
207+
self.write_timeout = timeout;
208+
self
209+
}
210+
151211
/// Sets the `log_level` in the configuration being built.
152212
///
153213
/// # Example
@@ -318,6 +378,8 @@ impl ConfigBuilder {
318378
config.set_port(self.port);
319379
config.set_workers(self.workers);
320380
config.set_keep_alive(self.keep_alive);
381+
config.set_read_timeout(self.read_timeout);
382+
config.set_write_timeout(self.write_timeout);
321383
config.set_log_level(self.log_level);
322384
config.set_extras(self.extras);
323385
config.set_limits(self.limits);

core/lib/src/config/config.rs

+68
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ pub struct Config {
4646
pub workers: u16,
4747
/// Keep-alive timeout in seconds or None if disabled.
4848
pub keep_alive: Option<u32>,
49+
/// Number of seconds to wait without _receiving_ data before closing a
50+
/// connection; disabled when `None`.
51+
pub read_timeout: Option<u32>,
52+
/// Number of seconds to wait without _sending_ data before closing a
53+
/// connection; disabled when `None`.
54+
pub write_timeout: Option<u32>,
4955
/// How much information to log.
5056
pub log_level: LoggingLevel,
5157
/// The secret key.
@@ -229,6 +235,8 @@ impl Config {
229235
port: 8000,
230236
workers: default_workers,
231237
keep_alive: Some(5),
238+
read_timeout: Some(5),
239+
write_timeout: Some(5),
232240
log_level: LoggingLevel::Normal,
233241
secret_key: key,
234242
tls: None,
@@ -245,6 +253,8 @@ impl Config {
245253
port: 8000,
246254
workers: default_workers,
247255
keep_alive: Some(5),
256+
read_timeout: Some(5),
257+
write_timeout: Some(5),
248258
log_level: LoggingLevel::Normal,
249259
secret_key: key,
250260
tls: None,
@@ -261,6 +271,8 @@ impl Config {
261271
port: 8000,
262272
workers: default_workers,
263273
keep_alive: Some(5),
274+
read_timeout: Some(5),
275+
write_timeout: Some(5),
264276
log_level: LoggingLevel::Critical,
265277
secret_key: key,
266278
tls: None,
@@ -307,6 +319,8 @@ impl Config {
307319
port => (u16, set_port, ok),
308320
workers => (u16, set_workers, ok),
309321
keep_alive => (u32, set_keep_alive, ok),
322+
read_timeout => (u32, set_read_timeout, ok),
323+
write_timeout => (u32, set_write_timeout, ok),
310324
log => (log_level, set_log_level, ok),
311325
secret_key => (str, set_secret_key, id),
312326
tls => (tls_config, set_raw_tls, id),
@@ -422,6 +436,60 @@ impl Config {
422436
}
423437
}
424438

439+
/// Sets the read timeout to `timeout` seconds. If `timeout` is `0`, read
440+
/// timeouts are disabled.
441+
///
442+
/// # Example
443+
///
444+
/// ```rust
445+
/// use rocket::config::Config;
446+
///
447+
/// let mut config = Config::development();
448+
///
449+
/// // Set read timeout to 10 seconds.
450+
/// config.set_read_timeout(10);
451+
/// assert_eq!(config.read_timeout, Some(10));
452+
///
453+
/// // Disable read timeouts.
454+
/// config.set_read_timeout(0);
455+
/// assert_eq!(config.read_timeout, None);
456+
/// ```
457+
#[inline]
458+
pub fn set_read_timeout(&mut self, timeout: u32) {
459+
if timeout == 0 {
460+
self.read_timeout = None;
461+
} else {
462+
self.read_timeout = Some(timeout);
463+
}
464+
}
465+
466+
/// Sets the write timeout to `timeout` seconds. If `timeout` is `0`, write
467+
/// timeouts are disabled.
468+
///
469+
/// # Example
470+
///
471+
/// ```rust
472+
/// use rocket::config::Config;
473+
///
474+
/// let mut config = Config::development();
475+
///
476+
/// // Set write timeout to 10 seconds.
477+
/// config.set_write_timeout(10);
478+
/// assert_eq!(config.write_timeout, Some(10));
479+
///
480+
/// // Disable write timeouts.
481+
/// config.set_write_timeout(0);
482+
/// assert_eq!(config.write_timeout, None);
483+
/// ```
484+
#[inline]
485+
pub fn set_write_timeout(&mut self, timeout: u32) {
486+
if timeout == 0 {
487+
self.write_timeout = None;
488+
} else {
489+
self.write_timeout = Some(timeout);
490+
}
491+
}
492+
425493
/// Sets the `secret_key` in `self` to `key` which must be a 256-bit base64
426494
/// encoded string.
427495
///

core/lib/src/config/mod.rs

+50-14
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,20 @@
3131
//! not used by Rocket itself but can be used by external libraries. The
3232
//! standard configuration parameters are:
3333
//!
34-
//! | name | type | description | examples |
35-
//! |------------|----------------|-------------------------------------------------------------|----------------------------|
36-
//! | address | string | ip address or host to listen on | `"localhost"`, `"1.2.3.4"` |
37-
//! | port | integer | port number to listen on | `8000`, `80` |
38-
//! | keep_alive | integer | keep-alive timeout in seconds | `0` (disable), `10` |
39-
//! | workers | integer | number of concurrent thread workers | `36`, `512` |
40-
//! | log | string | max log level: `"off"`, `"normal"`, `"debug"`, `"critical"` | `"off"`, `"normal"` |
41-
//! | secret_key | 256-bit base64 | secret key for private cookies | `"8Xui8SI..."` (44 chars) |
42-
//! | tls | table | tls config table with two keys (`certs`, `key`) | _see below_ |
43-
//! | tls.certs | string | path to certificate chain in PEM format | `"private/cert.pem"` |
44-
//! | tls.key | string | path to private key for `tls.certs` in PEM format | `"private/key.pem"` |
45-
//! | limits | table | map from data type (string) to data limit (integer: bytes) | `{ forms = 65536 }` |
34+
//! | name | type | description | examples |
35+
//! |------------ |----------------|-------------------------------------------------------------|----------------------------|
36+
//! | address | string | ip address or host to listen on | `"localhost"`, `"1.2.3.4"` |
37+
//! | port | integer | port number to listen on | `8000`, `80` |
38+
//! | keep_alive | integer | keep-alive timeout in seconds | `0` (disable), `10` |
39+
//! | read_timeout | integer | data read timeout in seconds | `0` (disable), `5` |
40+
//! | write_timeout | integer | data write timeout in seconds | `0` (disable), `5` |
41+
//! | workers | integer | number of concurrent thread workers | `36`, `512` |
42+
//! | log | string | max log level: `"off"`, `"normal"`, `"debug"`, `"critical"` | `"off"`, `"normal"` |
43+
//! | secret_key | 256-bit base64 | secret key for private cookies | `"8Xui8SI..."` (44 chars) |
44+
//! | tls | table | tls config table with two keys (`certs`, `key`) | _see below_ |
45+
//! | tls.certs | string | path to certificate chain in PEM format | `"private/cert.pem"` |
46+
//! | tls.key | string | path to private key for `tls.certs` in PEM format | `"private/key.pem"` |
47+
//! | limits | table | map from data type (string) to data limit (integer: bytes) | `{ forms = 65536 }` |
4648
//!
4749
//! ### Rocket.toml
4850
//!
@@ -64,6 +66,8 @@
6466
//! port = 8000
6567
//! workers = [number_of_cpus * 2]
6668
//! keep_alive = 5
69+
//! read_timeout = 5
70+
//! write_timeout = 5
6771
//! log = "normal"
6872
//! secret_key = [randomly generated at launch]
6973
//! limits = { forms = 32768 }
@@ -73,6 +77,8 @@
7377
//! port = 8000
7478
//! workers = [number_of_cpus * 2]
7579
//! keep_alive = 5
80+
//! read_timeout = 5
81+
//! write_timeout = 5
7682
//! log = "normal"
7783
//! secret_key = [randomly generated at launch]
7884
//! limits = { forms = 32768 }
@@ -82,6 +88,8 @@
8288
//! port = 8000
8389
//! workers = [number_of_cpus * 2]
8490
//! keep_alive = 5
91+
//! read_timeout = 5
92+
//! write_timeout = 5
8593
//! log = "critical"
8694
//! secret_key = [randomly generated at launch]
8795
//! limits = { forms = 32768 }
@@ -579,6 +587,8 @@ mod test {
579587
workers = 21
580588
log = "critical"
581589
keep_alive = 0
590+
read_timeout = 1
591+
write_timeout = 0
582592
secret_key = "8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg="
583593
template_dir = "mine"
584594
json = true
@@ -591,6 +601,8 @@ mod test {
591601
.workers(21)
592602
.log_level(LoggingLevel::Critical)
593603
.keep_alive(0)
604+
.read_timeout(1)
605+
.write_timeout(0)
594606
.secret_key("8Xui8SN4mI+7egV/9dlfYYLGQJeEx4+DwmSQLwDVXJg=")
595607
.extra("template_dir", "mine")
596608
.extra("json", true)
@@ -866,7 +878,7 @@ mod test {
866878
}
867879

868880
#[test]
869-
fn test_good_keep_alives() {
881+
fn test_good_keep_alives_and_timeouts() {
870882
// Take the lock so changing the environment doesn't cause races.
871883
let _env_lock = ENV_LOCK.lock().unwrap();
872884
env::set_var(CONFIG_ENV, "stage");
@@ -898,10 +910,24 @@ mod test {
898910
"#.to_string(), TEST_CONFIG_FILENAME), {
899911
default_config(Staging).keep_alive(0)
900912
});
913+
914+
check_config!(RocketConfig::parse(r#"
915+
[stage]
916+
read_timeout = 10
917+
"#.to_string(), TEST_CONFIG_FILENAME), {
918+
default_config(Staging).read_timeout(10)
919+
});
920+
921+
check_config!(RocketConfig::parse(r#"
922+
[stage]
923+
write_timeout = 4
924+
"#.to_string(), TEST_CONFIG_FILENAME), {
925+
default_config(Staging).write_timeout(4)
926+
});
901927
}
902928

903929
#[test]
904-
fn test_bad_keep_alives() {
930+
fn test_bad_keep_alives_and_timeouts() {
905931
// Take the lock so changing the environment doesn't cause races.
906932
let _env_lock = ENV_LOCK.lock().unwrap();
907933
env::remove_var(CONFIG_ENV);
@@ -925,6 +951,16 @@ mod test {
925951
[dev]
926952
keep_alive = 4294967296
927953
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
954+
955+
assert!(RocketConfig::parse(r#"
956+
[dev]
957+
read_timeout = true
958+
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
959+
960+
assert!(RocketConfig::parse(r#"
961+
[dev]
962+
write_timeout = None
963+
"#.to_string(), TEST_CONFIG_FILENAME).is_err());
928964
}
929965

930966
#[test]

core/lib/src/data/data.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,10 @@ impl Data {
9191
}
9292

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

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

122126
// Steal the internal, undecoded data buffer from Hyper.
123127
let (mut hyper_buf, pos, cap) = body.get_mut().take_buf();

core/lib/src/response/responder.rs

-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,6 @@ impl<'r, R: Responder<'r>> Responder<'r> for Option<R> {
271271
/// If `self` is `Ok`, responds with the wrapped `Responder`. Otherwise prints
272272
/// an error message with the `Err` value returns an `Err` of
273273
/// `Status::InternalServerError`.
274-
#[deprecated(since = "0.4.3")]
275274
impl<'r, R: Responder<'r>, E: fmt::Debug> Responder<'r> for Result<R, E> {
276275
default fn respond_to(self, req: &Request) -> response::Result<'r> {
277276
self.map(|r| r.respond_to(req)).unwrap_or_else(|e| {

0 commit comments

Comments
 (0)