Loading page…
Rust walkthroughs
Loading page…
{"tool_call":"file.create","args":{"content":"# What are the differences between reqwest::redirect::Policy::none and reqwest::redirect::Policy::limited for HTTP redirect handling?
Policy::none disables all automatic redirect following, causing the client to return the redirect response directly without following the Location header. Policy::limited(n) allows following redirects up to a maximum count of n, protecting against infinite redirect loops. The key distinction is that none gives you complete control over redirect handling while limited provides automatic following with a safety limit. Use none when you need to inspect redirect responses or implement custom redirect logic, and limited for standard HTTP clients that should follow redirects automatically but need protection from redirect loops.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create client with no redirect following
let client = Client::builder()
.redirect(Policy::none())
.build()?;
// Make request to URL that redirects
let response = client.get(\"https://httpbin.org/redirect/1\")
.send()
.await?;
// Response is the redirect itself, not the final destination
println!(\"Status: {}\", response.status()); // 302 Found
println!(\"Location: {:?}\", response.headers().get(\"location\"));
// Must manually follow if desired
if let Some(location) = response.headers().get(\"location\") {
let final_url = location.to_str()?;
println!(\"Redirect to: {}\", final_url);
// Manual follow would require another request
}
Ok(())
}Policy::none() returns redirect responses directly without following.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Create client with limited redirect following
let client = Client::builder()
.redirect(Policy::limited(5))
.build()?;
// Make request to URL that redirects
let response = client.get(\"https://httpbin.org/redirect/3\")
.send()
.await?;
// Response is the final destination after following redirects
println!(\"Status: {}\", response.status()); // 200 OK
println!(\"Final URL: {}\", response.url()); // Shows final URL
// Up to 5 redirects followed automatically
Ok(())
}Policy::limited(n) follows redirects automatically up to n times.
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Default client uses Policy::limited(10)
let client = Client::new();
// Equivalent to:
let client = Client::builder()
.redirect(reqwest::redirect::Policy::limited(10))
.build()?;
// Both follow up to 10 redirects automatically
let response = client.get(\"https://httpbin.org/redirect/5\")
.send()
.await?;
// Response is after following redirects
println!(\"Status: {}\", response.status());
Ok(())
}The default client uses Policy::limited(10).
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// limited protects against infinite loops
let client = Client::builder()
.redirect(Policy::limited(3))
.build()?;
// URL that redirects infinitely (a -> b -> a -> b -> ...)
let result = client.get(\"https://httpbin.org/redirect/infinite\")
.send()
.await;
// Returns error after 3 redirects
match result {
Err(e) => {
if e.is_redirect() {
println!(\"Too many redirects: {}\", e);
}
}
Ok(_) => println!(\"Success\"),
}
// With none, you'd get each redirect response
let client = Client::builder()
.redirect(Policy::none())
.build()?;
let response = client.get(\"https://httpbin.org/redirect/infinite\")
.send()
.await?;
// Returns first redirect, doesn't follow
println!(\"Status: {}\", response.status()); // 302
Ok(())
}limited protects against loops; none returns each redirect response.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Use none to inspect redirect details
let client = Client::builder()
.redirect(Policy::none())
.build()?;
let response = client.get(\"https://httpbin.org/redirect-to?url=/get\")
.send()
.await?;
if response.status().is_redirection() {
println!(\"Redirect status: {}\", response.status());
// Inspect Location header
if let Some(location) = response.headers().get(\"location\") {
println!(\"Redirects to: {}\", location.to_str()?);
}
// Inspect other headers
println!(\"Content-Length: {:?}\", response.headers().get(\"content-length\"));
// Decide whether to follow
let should_follow = true; // Your logic here
if should_follow {
let location = response.headers().get(\"location\")
.and_then(|h| h.to_str().ok())
.unwrap_or(\"/\");
let final_response = client.get(location)
.send()
.await?;
println!(\"Final status: {}\", final_response.status());
}
}
Ok(())
}none allows inspecting redirect responses before deciding to follow.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.redirect(Policy::none())
.build()?;
// Implement custom redirect handling
fn should_follow_redirect(original_url: &str, redirect_url: &str) -> bool {
// Only follow redirects to same host
let original_host = url::Url::parse(original_url)
.ok()
.and_then(|u| u.host_str().map(|h| h.to_string()));
let redirect_host = url::Url::parse(redirect_url)
.ok()
.and_then(|u| u.host_str().map(|h| h.to_string()));
original_host.is_some() && original_host == redirect_host
}
let url = \"https://example.com/some/path\";
let mut current_url = url.to_string();
let max_redirects = 5;
for _ in 0..max_redirects {
let response = client.get(¤t_url).send().await?;
if !response.status().is_redirection() {
println!(\"Final response: {}\", response.status());
break;
}
let location = response.headers()
.get(\"location\")
.and_then(|h| h.to_str().ok())
.unwrap_or(\"/\");
if !should_follow_redirect(¤t_url, location) {
println!(\"Rejecting redirect to different host: {}\", location);
break;
}
println!(\"Following redirect: {} -> {}\", current_url, location);
current_url = location.to_string();
}
Ok(())
}none enables custom redirect policies with full control.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Different limits for different use cases
// Strict limit for API calls
let api_client = Client::builder()
.redirect(Policy::limited(3))
.build()?;
// Generous limit for web crawling
let crawl_client = Client::builder()
.redirect(Policy::limited(20))
.build()?;
// No redirects for redirect detection
let detection_client = Client::builder()
.redirect(Policy::none())
.build()?;
// Use appropriate client for use case
Ok(())
}Choose limited value based on expected redirect chains.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// limited: returns error on too many redirects
let client = Client::builder()
.redirect(Policy::limited(2))
.build()?;
// URL that requires 5 redirects
let result = client.get(\"https://httpbin.org/redirect/5\")
.send()
.await;
match result {
Err(e) => {
// Error type: reqwest::Error
if e.is_redirect() {
println!(\"Too many redirects\");
}
// Contains message about redirect limit
}
Ok(_) => println!(\"Unexpected success\"),
}
// none: returns redirect response, no error
let client = Client::builder()
.redirect(Policy::none())
.build()?;
let response = client.get(\"https://httpbin.org/redirect/5\")
.send()
.await?;
// Success - we get the redirect response
println!(\"Status: {}\", response.status()); // 302
Ok(())
}limited errors on too many redirects; none returns redirect response.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// With limited, cross-origin redirects are followed
let client = Client::builder()
.redirect(Policy::limited(5))
.build()?;
// Follows redirect even to different domain
let response = client.get(\"https://example.com/redirect-to-other-domain\")
.send()
.await?;
// Final URL might be on different domain
// With none, you can inspect and validate cross-origin
let client = Client::builder()
.redirect(Policy::none())
.build()?;
let response = client.get(\"https://example.com/redirect-to-other-domain\")
.send()
.await?;
if response.status().is_redirection() {
let location = response.headers()
.get(\"location\")
.and_then(|h| h.to_str().ok());
if let Some(loc) = location {
let redirect_url = url::Url::parse(loc)?;
let original_url = url::Url::parse(\"https://example.com\")?;
if redirect_url.host() != original_url.host() {
println!(\"Warning: cross-origin redirect to {}\", loc);
// Apply security policy
}
}
}
Ok(())
}none allows validating cross-origin redirects before following.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// reqwest follows HTTP standards for POST redirects:
// 301/302: POST -> GET (loses body)
// 307/308: POST -> POST (preserves method and body)
let client = Client::builder()
.redirect(Policy::limited(5))
.build()?;
// POST with redirect
let response = client
.post(\"https://example.com/redirect-endpoint\")
.body(\"request data\")
.send()
.await?;
// Behavior depends on redirect status code
// With none, you control POST redirect behavior
let client = Client::builder()
.redirect(Policy::none())
.build()?;
let response = client
.post(\"https://example.com/redirect-endpoint\")
.body(\"request data\")
.send()
.await?;
if response.status().is_redirection() {
// Decide how to handle POST redirect
let status = response.status();
if status == 307 || status == 308 {
// Preserve POST method
println!(\"Preserving POST for redirect\");
} else {
// Convert to GET
println!(\"Converting to GET for redirect\");
}
}
Ok(())
}none gives control over method preservation during POST redirects.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// limited handles both relative and absolute redirects
let client = Client::builder()
.redirect(Policy::limited(5))
.build()?;
// Both relative and absolute Location headers work
// Relative: Location: /new-path
// Absolute: Location: https://other.com/new-path
let response = client.get(\"https://example.com/redirect\")
.send()
.await?;
// reqwest resolves relative URLs correctly
// With none, you must resolve relative URLs manually
let client = Client::builder()
.redirect(Policy::none())
.build()?;
let response = client.get(\"https://example.com/old\")
.send()
.await?;
if let Some(location) = response.headers().get(\"location\") {
let location_str = location.to_str()?;
let base_url = url::Url::parse(\"https://example.com/old\")?;
let resolved_url = base_url.join(location_str)?;
println!(\"Resolved redirect: {}\", resolved_url);
}
Ok(())
}limited resolves relative URLs; with none you must resolve manually.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// With none, you can track redirect history
let client = Client::builder()
.redirect(Policy::none())
.build()?;
let mut redirect_chain: Vec<String> = Vec::new();
let mut current_url = \"https://example.com/start\".to_string();
let max_redirects = 10;
redirect_chain.push(current_url.clone());
for _ in 0..max_redirects {
let response = client.get(¤t_url).send().await?;
if !response.status().is_redirection() {
println!(\"Final URL: {}\", current_url);
println!(\"Redirect chain: {:?}\", redirect_chain);
break;
}
let location = response.headers()
.get(\"location\")
.and_then(|h| h.to_str().ok())
.unwrap_or(\"/\")
.to_string();
redirect_chain.push(location.clone());
current_url = location;
}
// With limited, redirect history is not directly available
// You only get the final response
Ok(())
}none allows building redirect history for auditing or logging.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Security risks with unlimited redirects:
// - Redirect loops consuming resources
// - SSRF via redirect to internal networks
// - Credential leakage via redirects
// Safer: use limited with reasonable count
let safe_client = Client::builder()
.redirect(Policy::limited(5))
.build()?;
// Or: custom policy with validation
let secure_client = Client::builder()
.redirect(Policy::none())
.build()?;
// Validate all redirects before following
fn validate_redirect(original: &url::Url, redirect: &url::Url) -> bool {
// Only HTTPS
if redirect.scheme() != \"https\" {
return false;
}
// No internal network redirects
if let Some(host) = redirect.host_str() {
if host.starts_with(\"10.\")
|| host.starts_with(\"192.168.\")
|| host.starts_with(\"172.16.\")
|| host == \"localhost\"
|| host.starts_with(\"127.\")
{
return false;
}
}
true
}
Ok(())
}none enables security-conscious redirect handling.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// For custom logic beyond none/limited, use Policy::custom
let client = Client::builder()
.redirect(Policy::custom(|attempt| {
// attempt contains:
// - url: the redirect target
// - previous: how many redirects so far
// Example: limit but also validate
if attempt.previous() > 5 {
attempt.stop()
} else {
let url = attempt.url();
if url.scheme() == \"https\" {
attempt.follow()
} else {
// Don't follow non-HTTPS redirects
attempt.stop()
}
}
}))
.build()?;
// This combines limited's safety with custom validation
Ok(())
}Policy::custom provides fine-grained control combining aspects of both approaches.
use reqwest::redirect::Policy;
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// limited: response.url() shows final URL
let client = Client::builder()
.redirect(Policy::limited(5))
.build()?;
let response = client.get(\"https://httpbin.org/redirect/3\")
.send()
.await?;
println!(\"Request URL: https://httpbin.org/redirect/3\");
println!(\"Response URL: {}\", response.url());
// Response URL is the final destination after redirects
// none: response.url() shows requested URL
let client = Client::builder()
.redirect(Policy::none())
.build()?;
let response = client.get(\"https://httpbin.org/redirect/3\")
.send()
.await?;
println!(\"Response URL: {}\", response.url());
// Response URL is still the original URL
Ok(())
}limited updates response.url() to final destination; none preserves original.
use reqwest::redirect::Policy;
// Use Policy::none when:
// - Detecting redirects for URL shorteners
// - Implementing custom redirect logic
// - Security audits requiring redirect inspection
// - Following specific redirect paths manually
// - Avoiding automatic following of malicious redirects
// - Building redirect chains for logging
// - Handling POST redirects with custom method preservation
// Use Policy::limited when:
// - Standard HTTP client behavior
// - Following normal web redirects
// - API calls where redirects should be transparent
// - Simulating browser redirect behavior
// - Simple redirect following with safety limitChoose based on whether you need redirect inspection or automatic following.
Policy::none characteristics:
Location headerPolicy::limited characteristics:
n timesresponse.url() to final destinationUse Policy::none when:
Use Policy::limited when:
Redirect loop handling:
none: Returns redirect response, never loopslimited: Returns error after n redirectsCross-origin handling:
none: Can inspect and validate before followinglimited: Follows automatically without validationMethod preservation:
none: Full control over method in redirectslimited: Follows HTTP standards (307/308 preserve method)Key insight: Policy::none and Policy::limited represent two fundamentally different approaches to redirect handling. none gives you the redirect response and lets you decide what to do—it's useful when redirects are part of your application logic, when you need to validate destinations, or when implementing custom redirect behavior. limited handles redirects automatically up to a safety limit—it's appropriate when redirects should be transparent to your application, like standard HTTP client behavior. The default client uses limited(10) because most HTTP clients should follow redirects by default, but a limit prevents infinite loops. Use none for redirect-aware applications (URL shortener resolvers, redirect auditors, security scanners) and limited for redirect-transparent applications (API clients, web scrapers with standard behavior). For cases requiring both automatic following and validation, Policy::custom provides fine-grained control.","path":"/articles/275_reqwest_redirect_policy_none_vs_limited.md"}}