Table of contents

What is the proper way to handle Reqwest errors?

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

  1. Always handle both network and HTTP errors - Check connection issues and status codes
  2. Use appropriate timeouts - Prevent hanging requests with reasonable timeout values
  3. Implement retry logic - Handle transient failures with exponential backoff
  4. Log errors appropriately - Include context like URLs and request parameters
  5. Use typed errors - Create custom error types for better error handling
  6. 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.

Try WebScraping.AI for Your Web Scraping Needs

Looking for a powerful web scraping solution? WebScraping.AI provides an LLM-powered API that combines Chromium JavaScript rendering with rotating proxies for reliable data extraction.

Key Features:

  • AI-powered extraction: Ask questions about web pages or extract structured data fields
  • JavaScript rendering: Full Chromium browser support for dynamic content
  • Rotating proxies: Datacenter and residential proxies from multiple countries
  • Easy integration: Simple REST API with SDKs for Python, Ruby, PHP, and more
  • Reliable & scalable: Built for developers who need consistent results

Getting Started:

Get page content with AI analysis:

curl "https://api.webscraping.ai/ai/question?url=https://example.com&question=What is the main topic?&api_key=YOUR_API_KEY"

Extract structured data:

curl "https://api.webscraping.ai/ai/fields?url=https://example.com&fields[title]=Page title&fields[price]=Product price&api_key=YOUR_API_KEY"

Try in request builder

Related Questions

Get Started Now

WebScraping.AI provides rotating proxies, Chromium rendering and built-in HTML parser for web scraping
Icon