Proper error handling in Reqwest is essential for building robust Rust applications that interact with web services. Reqwest operations can fail due to network issues, timeouts, invalid URLs, DNS resolution problems, or HTTP status errors. This guide covers comprehensive error handling strategies.
Understanding Reqwest Error Types
Reqwest uses the reqwest::Error
type to represent all possible failures. This error type implements std::error::Error
and provides specific methods to identify error categories:
use reqwest::Error;
// Common error checking methods
error.is_timeout() // Request timed out
error.is_connect() // Connection failed
error.is_request() // Invalid request (bad URL, etc.)
error.is_redirect() // Too many redirects
error.is_status() // HTTP status error (4xx, 5xx)
error.is_body() // Response body error
error.is_decode() // JSON/text decode error
Basic Error Handling Patterns
1. Simple Match Pattern
The most straightforward approach using pattern matching:
use reqwest;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let response = reqwest::get("https://httpbin.org/get").await;
match response {
Ok(resp) => {
let text = resp.text().await?;
println!("Success: {}", text);
}
Err(err) => {
eprintln!("Request failed: {}", err);
return Err(err.into());
}
}
Ok(())
}
2. Error Propagation with ?
Operator
For functions that return Result
, use the ?
operator for cleaner code:
use reqwest;
async fn fetch_data(url: &str) -> Result<String, reqwest::Error> {
let response = reqwest::get(url).await?;
let body = response.text().await?;
Ok(body)
}
#[tokio::main]
async fn main() {
match fetch_data("https://httpbin.org/json").await {
Ok(data) => println!("Data: {}", data),
Err(e) => eprintln!("Failed to fetch data: {}", e),
}
}
Specific Error Type Handling
Comprehensive Error Classification
Handle different error types with specific recovery strategies:
use reqwest;
use std::time::Duration;
async fn robust_request(url: &str) -> Result<String, Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let response = client.get(url).send().await;
match response {
Ok(resp) => {
// Check HTTP status
if resp.status().is_success() {
Ok(resp.text().await?)
} else {
Err(format!("HTTP error: {}", resp.status()).into())
}
}
Err(err) => {
if err.is_timeout() {
Err("Request timed out - server may be overloaded".into())
} else if err.is_connect() {
Err("Connection failed - check network or server availability".into())
} else if err.is_request() {
Err("Invalid request - check URL format".into())
} else if err.is_redirect() {
Err("Too many redirects - possible redirect loop".into())
} else {
Err(format!("Unexpected error: {}", err).into())
}
}
}
}
Handling HTTP Status Errors
Distinguish between client errors (4xx) and server errors (5xx):
use reqwest::{Client, StatusCode};
use std::time::Duration;
async fn handle_http_errors(url: &str) -> Result<String, Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get(url).send().await?;
match response.status() {
StatusCode::OK => Ok(response.text().await?),
StatusCode::NOT_FOUND => Err("Resource not found (404)".into()),
StatusCode::UNAUTHORIZED => Err("Authentication required (401)".into()),
StatusCode::FORBIDDEN => Err("Access forbidden (403)".into()),
StatusCode::TOO_MANY_REQUESTS => Err("Rate limit exceeded (429)".into()),
status if status.is_server_error() => {
Err(format!("Server error ({}): {}", status.as_u16(),
response.text().await.unwrap_or_default()).into())
}
status if status.is_client_error() => {
Err(format!("Client error ({}): {}", status.as_u16(),
response.text().await.unwrap_or_default()).into())
}
_ => Err(format!("Unexpected status: {}", response.status()).into()),
}
}
Advanced Error Handling Techniques
Retry Logic with Exponential Backoff
Implement retry logic for transient failures:
use reqwest::Client;
use std::time::Duration;
use tokio::time::sleep;
async fn retry_request(
client: &Client,
url: &str,
max_retries: u32,
) -> Result<String, reqwest::Error> {
let mut attempts = 0;
loop {
match client.get(url).send().await {
Ok(response) => {
if response.status().is_success() {
return response.text().await;
} else if response.status().is_server_error() && attempts < max_retries {
// Retry server errors
attempts += 1;
let delay = Duration::from_millis(1000 * 2_u64.pow(attempts - 1));
println!("Server error, retrying in {:?}...", delay);
sleep(delay).await;
continue;
} else {
return Err(reqwest::Error::from(
reqwest::Error::from(response.error_for_status().unwrap_err())
));
}
}
Err(err) => {
if (err.is_connect() || err.is_timeout()) && attempts < max_retries {
attempts += 1;
let delay = Duration::from_millis(1000 * 2_u64.pow(attempts - 1));
println!("Network error, retrying in {:?}...", delay);
sleep(delay).await;
continue;
} else {
return Err(err);
}
}
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.timeout(Duration::from_secs(10))
.build()?;
match retry_request(&client, "https://httpbin.org/status/503", 3).await {
Ok(body) => println!("Success: {}", body),
Err(e) => eprintln!("Failed after retries: {}", e),
}
Ok(())
}
Custom Error Types
Create application-specific error types for better error handling:
use reqwest;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ScrapingError {
#[error("Network error: {0}")]
Network(#[from] reqwest::Error),
#[error("Parse error: {0}")]
Parse(String),
#[error("Rate limit exceeded")]
RateLimit,
#[error("Authentication failed")]
Auth,
}
async fn scrape_data(url: &str) -> Result<serde_json::Value, ScrapingError> {
let response = reqwest::get(url).await?;
match response.status() {
reqwest::StatusCode::TOO_MANY_REQUESTS => Err(ScrapingError::RateLimit),
reqwest::StatusCode::UNAUTHORIZED => Err(ScrapingError::Auth),
status if status.is_success() => {
let json: serde_json::Value = response.json().await
.map_err(|e| ScrapingError::Parse(e.to_string()))?;
Ok(json)
}
_ => Err(ScrapingError::Network(
reqwest::Error::from(response.error_for_status().unwrap_err())
))
}
}
Best Practices
- Always handle both network and HTTP errors - Check connection issues and status codes
- Use appropriate timeouts - Prevent hanging requests with reasonable timeout values
- Implement retry logic - Handle transient failures with exponential backoff
- Log errors appropriately - Include context like URLs and request parameters
- Use typed errors - Create custom error types for better error handling
- Check status codes explicitly - Don't rely only on the
Result
type for HTTP errors
By following these patterns, you'll build robust Rust applications that gracefully handle the various ways HTTP requests can fail when using Reqwest.