Loading pageâŚ
Rust walkthroughs
Loading pageâŚ
reqwest::ClientBuilder::timeout and connect_timeout for request lifecycle control?timeout sets a deadline for the entire request lifecycleâincluding DNS resolution, connection establishment, TLS negotiation, request transmission, and response receptionâwhile connect_timeout specifically limits how long the client waits for a TCP connection to be established with the server. The key distinction is scope: timeout covers end-to-end request completion, whereas connect_timeout governs only the initial connection phase. A request can succeed within timeout but still fail connect_timeout if the connection takes too long, or fail timeout even after a fast connection if the response is slow.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Understanding the phases:
// 1. DNS resolution - resolve hostname to IP address
// 2. TCP connection - establish TCP socket with server
// 3. TLS handshake - negotiate HTTPS (if applicable)
// 4. Request transmission - send HTTP request
// 5. Server processing - wait for server to generate response
// 6. Response reception - receive HTTP response body
// connect_timeout covers: TCP connection (phase 2)
// timeout covers: ALL phases (1-6)
Ok(())
}Understanding which phase each timeout controls is essential for proper configuration.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// timeout: Total request time
let client = Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
// The entire request must complete within 30 seconds
// Including: DNS, connection, TLS, request, response
// connect_timeout: Only TCP connection phase
let client = Client::builder()
.connect_timeout(Duration::from_secs(5))
.build()?;
// The TCP connection must be established within 5 seconds
// But the overall request can take longer
// Both can be combined:
let client = Client::builder()
.connect_timeout(Duration::from_secs(5))
.timeout(Duration::from_secs(30))
.build()?;
// Connection: max 5 seconds
// Total request: max 30 seconds
Ok(())
}Configure both timeouts for comprehensive control over request behavior.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() {
// Scenario 1: Server is slow to respond
// - connect_timeout won't trigger (connection is fast)
// - timeout WILL trigger (response is slow)
// Scenario 2: Server is unreachable (network issues)
// - connect_timeout WILL trigger (can't establish connection)
// - timeout would also trigger, but connect_timeout fires first
// Scenario 3: DNS resolution hangs
// - Neither timeout covers DNS directly
// - But timeout indirectly covers it (total request time)
// Scenario 4: TLS handshake is slow
// - connect_timeout won't cover TLS (only TCP)
// - timeout covers the entire TLS negotiation
// Scenario 5: Slow response body streaming
// - connect_timeout passed during connection
// - timeout covers slow streaming
}Different failure modes require different timeout strategies.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() {
let client = Client::builder()
.connect_timeout(Duration::from_secs(2))
.timeout(Duration::from_secs(5))
.build()
.unwrap();
// Case 1: Connection takes 3 seconds
// connect_timeout fires at 2 seconds
// Error: "error sending request for url ... connect timeout"
// Case 2: Connection takes 1 second, response takes 6 seconds
// connect_timeout passes (1s < 2s)
// timeout fires at 5 seconds total
// Error: "error sending request for url ... operation timed out"
// Case 3: Connection takes 1 second, response takes 3 seconds
// Both timeouts pass
// Success!
}The timeout that fires depends on which phase is slow.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() {
// reqwest maintains a connection pool
// Pooled connections skip the connect_timeout check
let client = Client::builder()
.connect_timeout(Duration::from_secs(5))
.pool_max_idle_per_host(10) // Keep connections alive
.pool_idle_timeout(Duration::from_secs(60))
.build()
.unwrap();
// First request: New connection -> connect_timeout applies
// Subsequent requests: Pooled connection -> connect_timeout skipped
// timeout ALWAYS applies, regardless of pooling
// This means:
// - First request to slow server: may fail connect_timeout
// - Second request (reused connection): skips connect_timeout
// - But timeout still limits total request time
}Pooled connections bypass connect_timeout but not timeout.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() {
let client = Client::builder()
.connect_timeout(Duration::from_secs(2))
.timeout(Duration::from_secs(5))
.build()
.unwrap();
let result = client.get("https://httpbin.org/delay/10")
.send()
.await;
match result {
Ok(response) => {
println!("Success: {}", response.status());
}
Err(e) => {
if e.is_timeout() {
if e.to_string().contains("connect") {
println!("Connection timeout - server unreachable or slow");
} else {
println!("Request timeout - response took too long");
}
} else if e.is_connect() {
println!("Connection failed - network or DNS issue");
} else {
println!("Other error: {}", e);
}
}
}
}Error messages distinguish between connection timeouts and request timeouts.
use reqwest::Client;
#[tokio::main]
async fn main() {
// Without any timeout configuration:
let client = Client::new();
// connect_timeout: Uses system default (usually ~60-120 seconds)
// timeout: No limit - request can hang indefinitely
// This is problematic for:
// - Long-running operations (can hang forever)
// - Production services (need bounded request times)
// - Resource management (open connections consume memory)
// Recommendation: Always set at least timeout
}Without explicit timeouts, requests can hang indefinitely.
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() {
// reqwest doesn't have separate read_timeout
// timeout covers: connection + read + write
// For more granular control, you might want:
// - connect_timeout: How long to wait for TCP connection
// - read_timeout: How long to wait for data on established connection
// - write_timeout: How long to wait to send data
// reqwest's timeout combines these
// If you need read_timeout specifically, use the lower-level
// hyper or tower services
let client = Client::builder()
.connect_timeout(Duration::from_secs(5))
// .read_timeout(...) // Not available in reqwest
.timeout(Duration::from_secs(30))
.build()
.unwrap();
}reqwest uses a single timeout for the entire request; connect_timeout is the only granular option.
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)) // Client default
.build()?;
// Override timeout for specific request
let response = client
.get("https://example.com/large-file")
.timeout(Duration::from_secs(300)) // 5 minutes for large file
.send()
.await?;
// This request uses 300s timeout
// Other requests still use 30s default
// Note: connect_timeout is client-level only
// Cannot be overridden per-request
Ok(())
}Per-request timeouts override client defaults for specific use cases.
use reqwest::Client;
use std::time::Duration;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.timeout(Duration::from_secs(5))
.build()?;
// When streaming response body:
// timeout covers: request + response headers + initial body
// It may or may not cover slow body streaming
let response = client
.get("https://example.com/streaming-endpoint")
.send()
.await?;
// timeout covers getting to this point
// Streaming the body afterward may not be covered
// For streaming, consider:
let mut stream = response.bytes_stream();
while let Some(bytes) = stream.next().await {
let bytes = bytes?;
// Process bytes
// This might not be covered by the original timeout
}
Ok(())
}timeout applies to the request/response; streaming body may need separate handling.
use reqwest::Client;
use std::time::Duration;
fn create_client_for_api() -> Result<Client, reqwest::Error> {
// For typical API calls
Client::builder()
.connect_timeout(Duration::from_secs(5)) // Network issues detected quickly
.timeout(Duration::from_secs(30)) // Reasonable for API responses
.build()
}
fn create_client_for_file_uploads() -> Result<Client, reqwest::Error> {
// For file uploads
Client::builder()
.connect_timeout(Duration::from_secs(10)) // Same or slightly longer
.timeout(Duration::from_secs(300)) // 5 minutes for large files
.build()
}
fn create_client_for_health_checks() -> Result<Client, reqwest::Error> {
// For health checks (should be fast)
Client::builder()
.connect_timeout(Duration::from_secs(2)) // Quick failure detection
.timeout(Duration::from_secs(5)) // Health checks should be fast
.build()
}
fn create_client_for_background_jobs() -> Result<Client, reqwest::Error> {
// For background processing
Client::builder()
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(600)) // 10 minutes
.build()
}Choose timeout values based on expected request characteristics.
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()?;
let url = "https://example.com/api";
// Manual retry with timeout awareness
let mut attempts = 0;
let max_attempts = 3;
loop {
attempts += 1;
match client.get(url).send().await {
Ok(response) => {
// Success - process response
break Ok(response);
}
Err(e) if e.is_timeout() && attempts < max_attempts => {
// Timeout occurred - retry
println!("Timeout on attempt {}, retrying...", attempts);
tokio::time::sleep(Duration::from_secs(1)).await;
continue;
}
Err(e) if e.is_connect() && attempts < max_attempts => {
// Connection issue - retry
println!("Connection error on attempt {}, retrying...", attempts);
tokio::time::sleep(Duration::from_secs(2)).await;
continue;
}
Err(e) => {
// Final failure
break Err(e.into());
}
}
}
}Distinguishing timeout types helps implement appropriate retry strategies.
// Summary comparison:
// connect_timeout:
// - Scope: TCP connection establishment only
// - Phase: After DNS, before TLS
// - Pooled connections: Skipped for reused connections
// - Per-request override: Not available
// - Typical value: 5-10 seconds
// timeout:
// - Scope: Entire request lifecycle
// - Phase: DNS + connection + TLS + request + response
// - Pooled connections: Always applies
// - Per-request override: Available via .timeout()
// - Typical value: 30-300 seconds (depends on use case)
// Key insight: connect_timeout is for network issues
// timeout is for overall request durationuse reqwest::Client;
use std::time::Duration;
use tracing::{info, error};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let client = Client::builder()
.connect_timeout(Duration::from_secs(5))
.timeout(Duration::from_secs(30))
.build()
.unwrap();
let start = std::time::Instant::now();
match client.get("https://example.com").send().await {
Ok(response) => {
info!(
elapsed_ms = start.elapsed().as_millis(),
status = ?response.status(),
"Request completed"
);
}
Err(e) => {
error!(
elapsed_ms = start.elapsed().as_millis(),
error = ?e,
is_timeout = e.is_timeout(),
is_connect = e.is_connect(),
"Request failed"
);
}
}
}Timing information helps identify which phase is slow.
Timeout Scope Comparison:
| Phase | connect_timeout | timeout |
|-------|-------------------|-----------|
| DNS resolution | No | Yes |
| TCP connection | Yes | Yes |
| TLS handshake | No | Yes |
| Request transmission | No | Yes |
| Server processing | No | Yes |
| Response reception | No | Yes |
When to use each:
connect_timeout: Detect unreachable servers quickly; don't wait for TCP timeoutstimeout: Bound total request time; prevent indefinite hangsConfiguration recommendations:
| Use Case | connect_timeout | timeout |
|----------|-------------------|-----------|
| API calls | 5s | 30s |
| File uploads | 10s | 300s |
| Health checks | 2s | 5s |
| Background jobs | 10s | 600s |
Key insight: connect_timeout and timeout serve different purposes. connect_timeout protects against network connectivity issuesâthe time to establish a TCP socket. timeout protects against slow responsesâthe total time for the entire request. Use both: a short connect_timeout (5-10s) for fast failure detection on unreachable servers, and an appropriate timeout (varies by use case) for overall request duration bounds. The connect_timeout fires first if the server is unreachable; timeout fires first if the connection succeeds but the response is slow. For pooled connections, connect_timeout is skipped but timeout always applies.