Reqwest, the popular HTTP client for Rust, provides multiple timeout options to control request timing behavior. You can set timeouts at both the client level and individual request level to prevent your application from hanging on slow or unresponsive servers.
Basic Request Timeout
Use the timeout()
method on a RequestBuilder
to set a timeout for individual requests:
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
let response = client
.get("https://httpbin.org/delay/5")
.timeout(Duration::from_secs(10))
.send()
.await;
match response {
Ok(res) => {
println!("Status: {}", res.status());
let body = res.text().await?;
println!("Body: {}", body);
}
Err(err) => {
if err.is_timeout() {
println!("Request timed out after 10 seconds");
} else {
println!("Request failed: {}", err);
}
}
}
Ok(())
}
Client-Level Timeouts
Set default timeouts for all requests made by a client using ClientBuilder
:
use reqwest::ClientBuilder;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = ClientBuilder::new()
.timeout(Duration::from_secs(30)) // Total request timeout
.connect_timeout(Duration::from_secs(5)) // Connection establishment timeout
.build()?;
let response = client
.get("https://httpbin.org/get")
.send()
.await?;
println!("Response: {}", response.text().await?);
Ok(())
}
Multiple Timeout Types
Reqwest supports different timeout types for fine-grained control:
use reqwest::ClientBuilder;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = ClientBuilder::new()
.connect_timeout(Duration::from_secs(10)) // Time to establish connection
.timeout(Duration::from_secs(60)) // Total request timeout
.read_timeout(Duration::from_secs(30)) // Time to read response data
.build()?;
let response = client
.post("https://httpbin.org/post")
.json(&serde_json::json!({"key": "value"}))
.send()
.await?;
println!("Posted successfully: {}", response.status());
Ok(())
}
Override Client Timeouts
Individual requests can override client-level timeouts:
use reqwest::ClientBuilder;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
// Client with 30-second default timeout
let client = ClientBuilder::new()
.timeout(Duration::from_secs(30))
.build()?;
// This request uses a shorter 5-second timeout
let quick_response = client
.get("https://httpbin.org/delay/2")
.timeout(Duration::from_secs(5))
.send()
.await;
// This request uses the default 30-second timeout
let normal_response = client
.get("https://httpbin.org/delay/10")
.send()
.await;
match quick_response {
Ok(_) => println!("Quick request succeeded"),
Err(e) if e.is_timeout() => println!("Quick request timed out"),
Err(e) => println!("Quick request error: {}", e),
}
Ok(())
}
Handling Timeout Errors
Properly handle different types of errors, including timeouts:
use reqwest::{Client, Error};
use std::time::Duration;
async fn make_request_with_retry(url: &str, max_retries: u32) -> Result<String, Error> {
let client = Client::new();
for attempt in 1..=max_retries {
let result = client
.get(url)
.timeout(Duration::from_secs(10))
.send()
.await;
match result {
Ok(response) => return response.text().await,
Err(err) => {
if err.is_timeout() {
println!("Attempt {} timed out, retrying...", attempt);
if attempt == max_retries {
return Err(err);
}
tokio::time::sleep(Duration::from_secs(1)).await;
} else {
return Err(err); // Non-timeout error, don't retry
}
}
}
}
unreachable!()
}
#[tokio::main]
async fn main() {
match make_request_with_retry("https://httpbin.org/delay/15", 3).await {
Ok(body) => println!("Success: {}", body),
Err(e) => println!("Failed after retries: {}", e),
}
}
Timeout Best Practices
- Connect timeout: 5-10 seconds for most applications
- Read timeout: 30-60 seconds depending on expected response size
- Total timeout: Should be longer than read timeout to account for connection time
- Override when needed: Use request-level timeouts for specific endpoints that need different timing
- Handle timeout errors gracefully: Implement retry logic for timeout scenarios where appropriate
Cargo.toml Dependencies
Add these dependencies to your Cargo.toml
:
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
serde_json = "1.0"
The timeout functionality covers the entire request lifecycle, including DNS resolution, connection establishment, sending the request, and reading the response. This ensures your application remains responsive even when dealing with slow or unresponsive servers.