How do I handle redirects when using Reqwest?

Reqwest, Rust's popular HTTP client library, provides flexible redirect handling capabilities. By default, it automatically follows redirects, but you can customize this behavior for different use cases including manual handling, limiting redirects, or implementing custom redirect logic.

Default Redirect Behavior

Reqwest automatically follows up to 10 redirects by default. This works seamlessly for most applications:

use reqwest::Error;

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Automatically follows redirects (up to 10)
    let response = reqwest::get("https://httpbin.org/redirect/3")
        .await?;

    println!("Final URL: {}", response.url());
    println!("Status: {}", response.status());

    let text = response.text().await?;
    println!("Response: {}", text);
    Ok(())
}

Configuring Redirect Policies

Limited Redirects

Control the maximum number of redirects to follow:

use reqwest::{Client, Error, redirect::Policy};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let client = Client::builder()
        .redirect(Policy::limited(3)) // Follow up to 3 redirects
        .build()?;

    match client.get("https://httpbin.org/redirect/5").send().await {
        Ok(response) => {
            println!("Success! Final URL: {}", response.url());
        }
        Err(e) => {
            println!("Error (likely too many redirects): {}", e);
        }
    }

    Ok(())
}

Disable Redirects

Completely disable automatic redirect following:

use reqwest::{Client, Error, redirect::Policy};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let client = Client::builder()
        .redirect(Policy::none()) // Don't follow any redirects
        .build()?;

    let response = client.get("https://httpbin.org/redirect/1")
        .send()
        .await?;

    println!("Status: {}", response.status()); // Will be 302/301
    println!("Location: {:?}", response.headers().get("location"));

    Ok(())
}

Manual Redirect Handling

Basic Manual Handling

Handle redirects manually to gain full control over the process:

use reqwest::{Client, Error, StatusCode, redirect::Policy, Url};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let client = Client::builder()
        .redirect(Policy::none())
        .build()?;

    let mut url = "https://httpbin.org/redirect/3";
    let mut redirect_count = 0;
    let max_redirects = 5;

    loop {
        let response = client.get(url).send().await?;

        if response.status().is_redirection() {
            if redirect_count >= max_redirects {
                eprintln!("Too many redirects!");
                break;
            }

            if let Some(location) = response.headers().get("location") {
                let location_str = location.to_str()
                    .map_err(|_| reqwest::Error::from(std::io::Error::new(
                        std::io::ErrorKind::InvalidData, 
                        "Invalid Location header"
                    )))?;

                // Handle relative URLs
                let next_url = if location_str.starts_with("http") {
                    location_str.to_string()
                } else {
                    // Convert relative URL to absolute
                    let base = Url::parse(url)?;
                    base.join(location_str)?.to_string()
                };

                println!("Redirecting to: {}", next_url);
                url = Box::leak(next_url.into_boxed_str());
                redirect_count += 1;
            } else {
                eprintln!("Redirect response without Location header");
                break;
            }
        } else {
            println!("Final response status: {}", response.status());
            let text = response.text().await?;
            println!("Response: {}", text);
            break;
        }
    }

    Ok(())
}

Advanced Manual Handling with Custom Logic

Implement custom redirect logic for specific requirements:

use reqwest::{Client, Error, Method, redirect::Policy, Url};
use std::collections::HashSet;

struct RedirectHandler {
    client: Client,
    max_redirects: usize,
    visited_urls: HashSet<String>,
}

impl RedirectHandler {
    fn new(max_redirects: usize) -> Self {
        let client = Client::builder()
            .redirect(Policy::none())
            .build()
            .expect("Failed to build client");

        Self {
            client,
            max_redirects,
            visited_urls: HashSet::new(),
        }
    }

    async fn fetch_with_redirects(&mut self, url: &str) -> Result<reqwest::Response, Error> {
        let mut current_url = url.to_string();
        let mut redirect_count = 0;

        loop {
            // Prevent redirect loops
            if self.visited_urls.contains(&current_url) {
                return Err(reqwest::Error::from(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    "Redirect loop detected"
                )));
            }

            self.visited_urls.insert(current_url.clone());

            let response = self.client.get(&current_url).send().await?;

            if !response.status().is_redirection() {
                return Ok(response);
            }

            if redirect_count >= self.max_redirects {
                return Err(reqwest::Error::from(std::io::Error::new(
                    std::io::ErrorKind::Other,
                    "Too many redirects"
                )));
            }

            let location = response.headers()
                .get("location")
                .and_then(|v| v.to_str().ok())
                .ok_or_else(|| reqwest::Error::from(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "Missing or invalid Location header"
                )))?;

            // Handle relative URLs
            current_url = if location.starts_with("http") {
                location.to_string()
            } else {
                let base = Url::parse(&current_url)?;
                base.join(location)?.to_string()
            };

            println!("Following redirect {}: {}", redirect_count + 1, current_url);
            redirect_count += 1;
        }
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    let mut handler = RedirectHandler::new(10);

    match handler.fetch_with_redirects("https://httpbin.org/redirect/3").await {
        Ok(response) => {
            println!("Success! Status: {}", response.status());
            let text = response.text().await?;
            println!("Response: {}", text);
        }
        Err(e) => {
            eprintln!("Error: {}", e);
        }
    }

    Ok(())
}

Checking Redirect Information

Get information about redirects that occurred during a request:

use reqwest::{Client, Error};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let client = Client::new();

    let response = client.get("https://httpbin.org/redirect/2")
        .send()
        .await?;

    // The final URL after all redirects
    println!("Final URL: {}", response.url());

    // Check if the URL changed (indicating redirects occurred)
    let original_url = "https://httpbin.org/redirect/2";
    if response.url().as_str() != original_url {
        println!("Redirects occurred!");
        println!("Original: {}", original_url);
        println!("Final: {}", response.url());
    }

    Ok(())
}

Best Practices

1. Handle Different Redirect Types

Different HTTP status codes have different meanings:

use reqwest::{Client, StatusCode, redirect::Policy};

async fn handle_redirect_types(client: &Client, url: &str) -> Result<(), reqwest::Error> {
    let response = client.get(url).send().await?;

    match response.status() {
        StatusCode::MOVED_PERMANENTLY => {
            println!("301: Permanent redirect - update bookmarks");
        }
        StatusCode::FOUND => {
            println!("302: Temporary redirect - original URL still valid");
        }
        StatusCode::SEE_OTHER => {
            println!("303: See other - should use GET for next request");
        }
        StatusCode::TEMPORARY_REDIRECT => {
            println!("307: Temporary redirect - preserve original method");
        }
        StatusCode::PERMANENT_REDIRECT => {
            println!("308: Permanent redirect - preserve original method");
        }
        _ => {}
    }

    Ok(())
}

2. Error Handling

Always implement proper error handling for redirect scenarios:

use reqwest::{Client, Error, redirect::Policy};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::builder()
        .redirect(Policy::limited(5))
        .build()?;

    match client.get("https://example.com/redirect-chain").send().await {
        Ok(response) => {
            println!("Success: {}", response.status());
        }
        Err(e) => {
            if e.is_redirect() {
                println!("Redirect error: {}", e);
            } else if e.is_timeout() {
                println!("Timeout during redirect");
            } else {
                println!("Other error: {}", e);
            }
        }
    }

    Ok(())
}

3. Security Considerations

Be cautious with redirects to prevent security issues:

use reqwest::{Client, Url, redirect::Policy};

fn is_safe_redirect(original: &Url, redirect: &Url) -> bool {
    // Only allow redirects to the same domain
    original.host_str() == redirect.host_str() &&
    original.scheme() == redirect.scheme()
}

async fn safe_redirect_client() -> Result<(), reqwest::Error> {
    let client = Client::builder()
        .redirect(Policy::custom(|attempt| {
            let original = attempt.previous().last()
                .unwrap_or_else(|| attempt.url());

            if is_safe_redirect(original, attempt.url()) {
                attempt.follow()
            } else {
                attempt.stop()
            }
        }))
        .build()?;

    // Use the client with custom redirect policy
    Ok(())
}

Common Issues and Solutions

  • Infinite redirect loops: Use manual handling with visited URL tracking
  • Too many redirects: Set appropriate limits with Policy::limited()
  • Relative URLs: Always resolve relative redirect URLs against the current base URL
  • Protocol changes: Be aware of HTTP to HTTPS redirects and security implications
  • Method preservation: Some redirect types change the HTTP method (e.g., POST to GET)

By understanding these redirect handling patterns, you can build robust HTTP clients that handle various redirect scenarios gracefully while maintaining security and performance.

Related Questions

Get Started Now

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