How to set a timeout on a request
Short answer: reqwest sets no timeout by default — a request waits for as long as the server takes. Put a deadline on every request with Client::builder().timeout(Duration::from_secs(2)), or on one request with .timeout(...) on the request builder.
The dependencies
Section titled “The dependencies”This picks up the fetch project from How to make a GET request. The code below needs two crates, and no features beyond reqwest’s defaults:
[dependencies]reqwest = "0.13.5"tokio = { version = "1.53.3", features = ["macros", "rt-multi-thread"] }If serde is still in that file from the POST article, leave it; nothing here uses it.
No timeout is the default
Section titled “No timeout is the default”Client::new() and the free reqwest::get build a client with no timeout set. Nothing in the request expires: if the server accepts the connection and then goes quiet, send().await waits for as long as it takes. The process looks hung, because it is.
A deadline on every request
Section titled “A deadline on every request”ClientBuilder::timeout covers the whole request — connecting, waiting for the response headers, and reading the body. httpbin’s /delay/10 waits ten seconds before it answers, so the deadline is easy to watch:
use std::time::Duration;
#[tokio::main]async fn main() -> Result<(), reqwest::Error> { let client = reqwest::Client::builder() .timeout(Duration::from_secs(2)) .build()?;
match client.get("https://httpbin.org/delay/10").send().await { Ok(response) => println!("{}", response.status()), Err(error) => { println!("{error:?}"); println!("is_timeout: {}", error.is_timeout()); } } Ok(())}cargo run prints (cargo’s own Compiling and Finished lines are not shown):
reqwest::Error { kind: Request, url: "https://httpbin.org/delay/10", source: TimedOut }is_timeout: trueA timeout arrives as a reqwest::Error, like every other failure from send(), and ? propagates it the same way. is_timeout() tells it apart from the rest when the reaction differs — a timeout is worth another attempt, a malformed URL is not. Retrying properly is a later article’s subject.
One request, its own deadline
Section titled “One request, its own deadline”The request builder carries a timeout of its own, and it replaces the client’s for that request. The client here allows thirty seconds; the request gives up after two:
use std::time::Duration;
#[tokio::main]async fn main() -> Result<(), reqwest::Error> { let client = reqwest::Client::builder() .timeout(Duration::from_secs(30)) .build()?;
match client .get("https://httpbin.org/delay/10") .timeout(Duration::from_secs(2)) .send() .await { Ok(response) => println!("{}", response.status()), Err(error) => println!("{error:?}"), } Ok(())}Same error as above, two seconds in. reqwest::get(url).timeout(...) behaves the same way, because the free function hands back the same builder.
Two narrower deadlines exist for the cases a total timeout handles badly. When the delay sits in the connection rather than the response, connect_timeout bounds that phase alone. And a body that stalls halfway through is not a slow request but a dead one: read_timeout bounds each read and resets after a successful one, so a slow trickle of bytes never trips it.
- How to make a GET request — the request this one puts a deadline on.
- How to POST JSON to an API — a body on the same client.
- How to deserialize JSON into a struct — what to do with the body once it arrives.
The next article in this chapter reads the headers off the response.