Loading page…
Rust walkthroughs
Loading page…
reqwest::ClientBuilder::timeout differ from reqwest::ClientBuilder::connect_timeout for request timing?reqwest::ClientBuilder::timeout sets a total time limit for the entire HTTP request lifecycle, including DNS resolution, connection establishment, request transmission, and response reception. reqwest::ClientBuilder::connect_timeout sets a time limit specifically for establishing a TCP connection to the server. A request will fail if either timeout is exceeded, but they control different phases of the request: timeout governs the end-to-end duration while connect_timeout governs only the initial connection establishment phase. The total timeout must be longer than the connect timeout to be meaningful, and connect_timeout is particularly important for failing fast on unreachable hosts.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.timeout(Duration::from_secs(5)) // Total request time limit
.build()?;
// This timeout covers:
// - DNS resolution
// - TCP connection
// - TLS handshake (for HTTPS)
// - Sending request headers and body
// - Receiving response headers and body
let result = client.get("https://httpbin.org/delay/10")
.send()
.await;
match result {
Ok(response) => println!("Got response: {}", response.status()),
Err(e) => println!("Error: {}", e), // Will timeout after 5 seconds
}
Ok(())
}timeout limits the entire request from start to finish.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.connect_timeout(Duration::from_secs(2)) // Only connection establishment
.build()?;
// This timeout covers only:
// - DNS resolution
// - TCP handshake
// - TLS handshake (for HTTPS)
// After connection is established, there's no time limit
// (unless timeout is also set)
let result = client.get("https://httpbin.org/delay/10")
.send()
.await;
// If connection succeeds within 2 seconds, the 10-second delay
// will be waited out fully (no timeout)
match result {
Ok(response) => println!("Got response: {}", response.status()),
Err(e) => println!("Error: {}", e),
}
Ok(())
}connect_timeout limits only the connection establishment phase.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.connect_timeout(Duration::from_secs(3)) // Connect within 3 seconds
.timeout(Duration::from_secs(10)) // Total request within 10 seconds
.build()?;
// Timeline:
// - Must connect within 3 seconds
// - After connection, must complete within remaining time (up to 10 total)
let result = client.get("https://httpbin.org/get")
.send()
.await?;
println!("Status: {}", result.status());
Ok(())
}Combining both provides granular control over different phases.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Without connect_timeout, connecting to unreachable host takes OS default
let client = Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
// This may take a long time before timing out
// The OS retries SYN packets with exponential backoff
let start = std::time::Instant::now();
let result = client.get("http://10.255.255.1:9999") // Unreachable IP
.send()
.await;
println!("Took: {:?}", start.elapsed());
println!("Result: {:?}", result);
Ok(())
}Without connect_timeout, unreachable hosts may take OS default time to fail.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// With connect_timeout, fail fast on unreachable hosts
let client = Client::builder()
.connect_timeout(Duration::from_secs(2))
.timeout(Duration::from_secs(30))
.build()?;
let start = std::time::Instant::now();
let result = client.get("http://10.255.255.1:9999") // Unreachable IP
.send()
.await;
println!("Took: {:?}", start.elapsed()); // ~2 seconds, not 30
match result {
Ok(_) => println!("Connected (unexpected)"),
Err(e) => println!("Failed as expected: {}", e),
}
Ok(())
}With connect_timeout, connection failures happen quickly.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.connect_timeout(Duration::from_secs(5)) // Allow 5 seconds to connect
.timeout(Duration::from_secs(3)) // But total time only 3 seconds
.build()?;
// Note: timeout < connect_timeout means timeout takes precedence
// If connection takes more than 3 seconds, timeout fires first
let result = client.get("https://httpbin.org/delay/5")
.send()
.await;
match result {
Ok(response) => println!("Got response: {}", response.status()),
Err(e) => println!("Error (expected): {}", e), // Timeout after 3 seconds
}
Ok(())
}When timeout < connect_timeout, timeout takes precedence.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.timeout(Duration::from_secs(30)) // Default timeout
.build()?;
// Override timeout for specific request
let response = client
.get("https://httpbin.org/delay/5")
.timeout(Duration::from_secs(10)) // Per-request timeout
.send()
.await?;
println!("Status: {}", response.status());
Ok(())
}Individual requests can override client-level timeouts.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.connect_timeout(Duration::from_secs(5))
.timeout(Duration::from_secs(30))
.build()?;
// Per-request timeout overrides client timeout
// But connect_timeout from client still applies
let response = client
.get("https://httpbin.org/get")
.timeout(Duration::from_secs(5)) // Override total timeout
.send()
.await?;
println!("Status: {}", response.status());
Ok(())
}Per-request .timeout() overrides client-level timeout, but connect_timeout remains.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Without any timeout, requests wait indefinitely
let client = Client::new();
// This could hang forever if server never responds
// (In practice, most servers do send responses eventually)
println!("Client created with no timeouts");
// Best practice: Always set timeouts for production code
Ok(())
}Without explicit timeouts, requests have no time limits.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() {
let client = Client::builder()
.connect_timeout(Duration::from_secs(1))
.timeout(Duration::from_secs(2))
.build()
.unwrap();
// Test connection timeout
let result = client.get("http://10.255.255.1:80").send().await;
match result {
Err(e) => {
if e.is_timeout() {
println!("Timeout error: {}", e);
if e.is_connect() {
println!("This was a connection timeout");
}
}
}
Ok(_) => println!("Unexpected success"),
}
// Test total timeout
let result = client.get("https://httpbin.org/delay/10").send().await;
match result {
Err(e) => {
if e.is_timeout() {
println!("Request timed out: {}", e);
// This is a total timeout, not a connect timeout
}
}
Ok(_) => println!("Got response"),
}
}Both timeout types result in timeout errors; is_connect() distinguishes them.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// reqwest also has read_timeout and write_timeout
let client = Client::builder()
.connect_timeout(Duration::from_secs(5))
.read_timeout(Duration::from_secs(10)) // Time to read response
.write_timeout(Duration::from_secs(10)) // Time to write request
.timeout(Duration::from_secs(30)) // Overall timeout
.build()?;
// read_timeout: time between reading chunks of response
// write_timeout: time between writing chunks of request
// timeout: total time for everything
println!("Client configured with all timeout types");
Ok(())
}reqwest provides additional timeout granularity.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.timeout(Duration::from_secs(5))
.build()?;
// For streaming, timeout applies to initial response
let response = client
.get("https://httpbin.org/stream/10")
.send()
.await?;
println!("Got streaming response: {}", response.status());
// Streaming the body can take longer than timeout
// The timeout applies to getting the response, not reading the stream
// (Use read_timeout for stream reading time limits)
Ok(())
}timeout applies to getting response headers, not streaming the body.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.connect_timeout(Duration::from_secs(5))
.timeout(Duration::from_secs(10))
.pool_max_idle_per_host(5) // Connection pooling
.build()?;
// First request: establishes connection (subject to connect_timeout)
let resp1 = client.get("https://httpbin.org/get").send().await?;
println!("First request: {}", resp1.status());
// Second request: reuses connection (no connect_timeout needed)
let resp2 = client.get("https://httpbin.org/get").send().await?;
println!("Second request: {}", resp2.status());
// Pooled connections bypass connect_timeout since already connected
Ok(())
}Pooled connections don't incur connect_timeout since they're already established.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Typical production values
let client = Client::builder()
.connect_timeout(Duration::from_secs(5)) // Quick fail for unreachable
.timeout(Duration::from_secs(30)) // Reasonable for most APIs
.build()?;
// For fast APIs
let fast_client = Client::builder()
.connect_timeout(Duration::from_secs(2))
.timeout(Duration::from_secs(5))
.build()?;
// For slow APIs or large uploads/downloads
let slow_client = Client::builder()
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(120))
.build()?;
println!("Created clients with different timeout profiles");
Ok(())
}Choose timeout values based on expected response times.
| Aspect | timeout | connect_timeout |
|--------|-----------|-------------------|
| Scope | Entire request lifecycle | Only connection establishment |
| Covers | DNS + TCP + TLS + request + response | DNS + TCP + TLS |
| Applies to | Every request | Only new connections |
| Pooled connections | Still applies | Not applicable |
| Typical values | 10-60 seconds | 1-10 seconds |
| Per-request override | Yes via .timeout() | No, client-level only |
The two timeouts protect against different failure scenarios:
connect_timeout protects against unreachable or unresponsive hosts. It limits how long the client waits for a TCP connection. Without it, connection attempts may retry for minutes following OS defaults. A short connect_timeout (1-5 seconds) ensures fast failure when the network or host is unreachable, allowing the application to try alternatives or report errors quickly.
timeout protects against slow servers and hanging responses. It limits the entire request duration from start to finish. This includes connection establishment, sending the request, and receiving the response. A reasonable timeout (10-60 seconds for most APIs) prevents the application from waiting indefinitely for responses that may never arrive.
Key insight: connect_timeout is about network reachability, while timeout is about server responsiveness. A server might be reachable (connection succeeds) but slow to respond (timeout fires during response reception). Conversely, a server might be unreachable (connect_timeout fires) before timeout ever matters. The two timeouts work together: connect_timeout fails fast on unreachable hosts, while timeout prevents indefinite waits on slow but reachable servers.
The relationship matters: if timeout <= connect_timeout, the timeout fires first even during connection. If timeout > connect_timeout, connection has time to succeed before the overall deadline. For typical use, set connect_timeout short (1-5 seconds) and timeout longer (10-60 seconds), giving connection a chance to succeed while still failing fast on truly unreachable hosts.