Add overall timeout for requests. (#67)
This deprecates timeout_read() and timeout_write() in favor of timeout(). The new timeout method on Request takes a Duration instead of a number of milliseconds, and is measured against overall request time, not per-read time. Once a request is started, the timeout is turned into a deadline specific to that call. The deadline is used in conjunction with the new DeadlineStream class, which sets a timeout on each read according to the remaining time for the request. Once the request is done, the DeadlineStream is unwrapped via .into::<Stream>() to become an undecorated Stream again for return to the pool. Timeouts on the stream are unset at this point. Still to be done: Add a setting on Agent for default timeout. Change header-writing code to apply overall deadline rather than per-write timeout. Fixes #28.
This commit is contained in:
committed by
GitHub
parent
d6b712f56f
commit
57be414d97
@@ -1,7 +1,9 @@
|
||||
use crate::test;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::super::*;
|
||||
use std::thread;
|
||||
|
||||
#[test]
|
||||
fn agent_reuse_headers() {
|
||||
@@ -57,8 +59,6 @@ fn agent_cookies() {
|
||||
// Start a test server on an available port, that times out idle connections at 2 seconds.
|
||||
// Return the port this server is listening on.
|
||||
fn start_idle_timeout_server() -> u16 {
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
use std::time::Duration;
|
||||
let listener = std::net::TcpListener::bind("localhost:0").unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
thread::spawn(move || {
|
||||
@@ -88,9 +88,6 @@ fn start_idle_timeout_server() -> u16 {
|
||||
|
||||
#[test]
|
||||
fn connection_reuse() {
|
||||
use std::io::Read;
|
||||
use std::time::Duration;
|
||||
|
||||
let port = start_idle_timeout_server();
|
||||
let url = format!("http://localhost:{}", port);
|
||||
let agent = Agent::default().build();
|
||||
|
||||
@@ -14,6 +14,7 @@ mod query_string;
|
||||
mod range;
|
||||
mod redirect;
|
||||
mod simple;
|
||||
mod timeout;
|
||||
|
||||
type RequestHandler = dyn Fn(&Unit) -> Result<Stream, Error> + Send + 'static;
|
||||
|
||||
|
||||
125
src/test/timeout.rs
Normal file
125
src/test/timeout.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
|
||||
use crate::test;
|
||||
use std::io::{self, BufRead, BufReader, Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
// Send an HTTP response on the TcpStream at a rate of two bytes every 10
|
||||
// milliseconds, for a total of 600 bytes.
|
||||
fn dribble_body_respond(stream: &mut TcpStream) -> io::Result<()> {
|
||||
let contents = [b'a'; 300];
|
||||
let headers = format!(
|
||||
"HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n",
|
||||
contents.len() * 2
|
||||
);
|
||||
stream.write_all(headers.as_bytes())?;
|
||||
for i in 0..contents.len() {
|
||||
stream.write_all(&contents[i..i + 1])?;
|
||||
stream.write_all(&[b'\n'; 1])?;
|
||||
stream.flush()?;
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Read a stream until reaching a blank line, in order to consume
|
||||
// request headers.
|
||||
fn read_headers(stream: &TcpStream) {
|
||||
for line in BufReader::new(stream).lines() {
|
||||
let line = match line {
|
||||
Ok(x) => x,
|
||||
Err(_) => return,
|
||||
};
|
||||
if line == "" {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Start a test server on an available port, that dribbles out a response at 1 write per 10ms.
|
||||
// Return the port this server is listening on.
|
||||
fn start_dribble_body_server() -> u16 {
|
||||
let listener = std::net::TcpListener::bind("localhost:0").unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let dribble_handler = |mut stream: TcpStream| {
|
||||
read_headers(&stream);
|
||||
if let Err(e) = dribble_body_respond(&mut stream) {
|
||||
eprintln!("sending dribble repsonse: {}", e);
|
||||
}
|
||||
};
|
||||
thread::spawn(move || {
|
||||
for stream in listener.incoming() {
|
||||
thread::spawn(move || dribble_handler(stream.unwrap()));
|
||||
}
|
||||
});
|
||||
port
|
||||
}
|
||||
|
||||
fn get_and_expect_timeout(url: String) {
|
||||
let agent = Agent::default().build();
|
||||
let timeout = Duration::from_millis(500);
|
||||
let resp = agent.get(&url).timeout(timeout).call();
|
||||
|
||||
let mut reader = resp.into_reader();
|
||||
let mut bytes = vec![];
|
||||
let result = reader.read_to_end(&mut bytes);
|
||||
|
||||
match result {
|
||||
Err(io_error) => match io_error.kind() {
|
||||
io::ErrorKind::WouldBlock => Ok(()),
|
||||
io::ErrorKind::TimedOut => Ok(()),
|
||||
_ => Err(format!("{:?}", io_error)),
|
||||
},
|
||||
Ok(_) => Err("successful response".to_string()),
|
||||
}
|
||||
.expect("expected timeout but got something else");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overall_timeout_during_body() {
|
||||
let port = start_dribble_body_server();
|
||||
let url = format!("http://localhost:{}/", port);
|
||||
|
||||
get_and_expect_timeout(url);
|
||||
}
|
||||
|
||||
// Send HTTP headers on the TcpStream at a rate of one header every 100
|
||||
// milliseconds, for a total of 30 headers.
|
||||
fn dribble_headers_respond(stream: &mut TcpStream) -> io::Result<()> {
|
||||
stream.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n")?;
|
||||
for _ in 0..30 {
|
||||
stream.write_all(b"a: b\n")?;
|
||||
stream.flush()?;
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Start a test server on an available port, that dribbles out response *headers* at 1 write per 10ms.
|
||||
// Return the port this server is listening on.
|
||||
fn start_dribble_headers_server() -> u16 {
|
||||
let listener = std::net::TcpListener::bind("localhost:0").unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
let dribble_handler = |mut stream: TcpStream| {
|
||||
read_headers(&stream);
|
||||
if let Err(e) = dribble_headers_respond(&mut stream) {
|
||||
eprintln!("sending dribble repsonse: {}", e);
|
||||
}
|
||||
};
|
||||
thread::spawn(move || {
|
||||
for stream in listener.incoming() {
|
||||
thread::spawn(move || dribble_handler(stream.unwrap()));
|
||||
}
|
||||
});
|
||||
port
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overall_timeout_during_headers() {
|
||||
let port = start_dribble_headers_server();
|
||||
let url = format!("http://localhost:{}/", port);
|
||||
get_and_expect_timeout(url);
|
||||
}
|
||||
Reference in New Issue
Block a user