Apply deadline across redirects. (#313)

Previously, each redirect could take timeout time, so a series of slow
redirects could run for longer than expected, or indefinitely.
This commit is contained in:
Jacob Hoffman-Andrews
2021-02-07 12:29:35 -08:00
committed by GitHub
parent d627ef9704
commit b246f0a9d2
3 changed files with 53 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
use std::fmt;
use std::io::Read;
use std::{fmt, time};
use url::{form_urlencoded, Url};
@@ -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 {

View File

@@ -1,6 +1,8 @@
use std::{
io::{self, Write},
net::TcpStream,
thread,
time::Duration,
};
use testserver::{self, TestServer};
@@ -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 {

View File

@@ -38,6 +38,7 @@ impl Unit {
url: &Url,
headers: &Vec<Header>,
body: &SizedReader,
deadline: Option<time::Instant>,
) -> Self {
//
@@ -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(),
@@ -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)