Skip to content

Commit

Permalink
Apply deadline across redirects. (#313)
Browse files Browse the repository at this point in the history
Previously, each redirect could take timeout time, so a series of slow
redirects could run for longer than expected, or indefinitely.
  • Loading branch information
jsha committed Feb 7, 2021
1 parent d627ef9 commit b246f0a
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 11 deletions.
19 changes: 17 additions & 2 deletions src/request.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use std::fmt;
use std::io::Read;
use std::{fmt, time};

use url::{form_urlencoded, Url};

Expand Down Expand Up @@ -116,8 +116,23 @@ impl Request {
for (name, value) in self.query_params.clone() {
url.query_pairs_mut().append_pair(&name, &value);
}
let deadline = match self.agent.config.timeout {
None => None,
Some(timeout) => {
let now = time::Instant::now();
Some(now.checked_add(timeout).unwrap())
}
};

let reader = payload.into_read();
let unit = Unit::new(&self.agent, &self.method, &url, &self.headers, &reader);
let unit = Unit::new(
&self.agent,
&self.method,
&url,
&self.headers,
&reader,
deadline,
);
let response = unit::connect(unit, true, reader).map_err(|e| e.url(url.clone()))?;

if response.status() >= 400 {
Expand Down
27 changes: 27 additions & 0 deletions src/test/redirect.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use std::{
io::{self, Write},
net::TcpStream,
thread,
time::Duration,
};
use testserver::{self, TestServer};

Expand Down Expand Up @@ -158,6 +160,31 @@ fn redirect_308() {
assert_eq!(resp.get_url(), "test://host/valid_response");
}

#[test]
fn redirects_hit_timeout() {
// A chain of 2 redirects and an OK, each of which takes 50ms; we set a
// timeout of 100ms and should error.
test::set_handler("/redirect_sleep1", |_| {
thread::sleep(Duration::from_millis(50));
test::make_response(302, "Go here", vec!["Location: /redirect_sleep2"], vec![])
});
test::set_handler("/redirect_sleep2", |_| {
thread::sleep(Duration::from_millis(50));
test::make_response(302, "Go here", vec!["Location: /ok"], vec![])
});
test::set_handler("/ok", |_| {
thread::sleep(Duration::from_millis(50));
test::make_response(200, "Go here", vec![], vec![])
});
let req = crate::builder().timeout(Duration::from_millis(100)).build();
let result = req.get("test://host/redirect_sleep1").call();
assert!(
matches!(result, Err(Error::Transport(_))),
"expected Transport error, got {:?}",
result
);
}

#[test]
fn too_many_redirects() {
for i in 0..10_000 {
Expand Down
18 changes: 9 additions & 9 deletions src/unit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ impl Unit {
url: &Url,
headers: &Vec<Header>,
body: &SizedReader,
deadline: Option<time::Instant>,
) -> Self {
//

Expand Down Expand Up @@ -99,14 +100,6 @@ impl Unit {
.cloned()
.collect();

let deadline = match agent.config.timeout {
None => None,
Some(timeout) => {
let now = time::Instant::now();
Some(now.checked_add(timeout).unwrap())
}
};

Unit {
agent: agent.clone(),
method: method.to_string(),
Expand Down Expand Up @@ -213,7 +206,14 @@ pub(crate) fn connect(
history.push(unit.url.to_string());
body = Payload::Empty.into_read();
// recreate the unit to get a new hostname and cookies for the new host.
unit = Unit::new(&unit.agent, &new_method, &new_url, &unit.headers, &body);
unit = Unit::new(
&unit.agent,
&new_method,
&new_url,
&unit.headers,
&body,
unit.deadline,
);
};
resp.history = history;
Ok(resp)
Expand Down

0 comments on commit b246f0a

Please sign in to comment.