Loading page…
Rust walkthroughs
Loading page…
reqwest::redirect::Policy::limited and none for redirect handling control?reqwest::redirect::Policy::limited(n) allows up to n redirects before returning an error, while Policy::none disables all redirect following entirely—the former provides controlled automatic redirect handling with a safety limit to prevent infinite redirect loops, and the latter gives complete control to the caller by making redirect responses (3xx status codes) return as regular responses that the application must handle manually. The choice between them depends on whether you want reqwest to handle redirects automatically (limited) or whether you need to inspect redirect responses, implement custom redirect logic, or prevent certain types of redirects for security reasons (none). Both policies protect against different concerns: limited prevents infinite loops while still providing convenience, while none provides maximum control at the cost of manual redirect handling.
use reqwest::Client;
async fn default_behavior() {
// By default, Client follows up to 10 redirects
let client = Client::new();
let response = client.get("https://example.com/redirect")
.send()
.await
.unwrap();
// If the server returns 302 Found with Location header,
// reqwest automatically follows to the new URL
// Up to 10 times before returning an error
}The default client follows redirects automatically with a limit of 10.
use reqwest::{Client, redirect::Policy};
async fn limited_redirects() {
// Allow up to 5 redirects
let client = Client::builder()
.redirect(Policy::limited(5))
.build()
.unwrap();
let response = client.get("https://example.com/redirect-chain")
.send()
.await
.unwrap();
// If the redirect chain has <= 5 redirects, response is final
// If > 5 redirects, reqwest returns an error
}Policy::limited(n) allows automatic redirect following with a maximum count.
use reqwest::{Client, redirect::Policy};
async fn no_redirects() {
// Disable all redirect following
let client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
let response = client.get("https://example.com/redirect")
.send()
.await
.unwrap();
// If server returns 302, response is the redirect response itself
// NOT the final destination
// You must manually handle the redirect
}Policy::none() disables automatic redirect following entirely.
use reqwest::{Client, redirect::Policy, StatusCode};
async fn compare_policies() {
let url = "https://example.com/redirects-to-final";
// limited(5): Follow redirects automatically
let limited_client = Client::builder()
.redirect(Policy::limited(5))
.build()
.unwrap();
let limited_response = limited_client.get(url).send().await.unwrap();
// limited_response is the final response after following redirects
// Status is 200 OK (final destination)
// none(): Don't follow redirects
let none_client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
let none_response = none_client.get(url).send().await.unwrap();
// none_response is the redirect response itself
// Status is 302 Found (or whatever redirect status)
// You must extract Location header and follow manually
}limited returns the final response; none returns the redirect response.
use reqwest::{Client, redirect::Policy, StatusCode};
async fn manual_redirect_handling() {
let client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
let response = client.get("https://example.com/redirect")
.send()
.await
.unwrap();
if response.status().is_redirection() {
// Extract Location header
let location = response.headers()
.get("Location")
.and_then(|v| v.to_str().ok())
.unwrap();
// Decide whether to follow
if should_follow_redirect(location) {
let final_response = client.get(location)
.send()
.await
.unwrap();
}
}
}
fn should_follow_redirect(location: &str) -> bool {
// Custom logic: don't follow cross-domain redirects
// Or don't follow HTTP->HTTPS upgrades
// Or validate redirect targets
true
}With none, you have full control over redirect behavior.
use reqwest::{Client, redirect::Policy};
async fn redirect_loop() {
// limited(n) prevents infinite loops
let client = Client::builder()
.redirect(Policy::limited(10))
.build()
.unwrap();
// If server redirects A -> B -> A -> B -> ... infinitely
// limited(10) will return error after 10 redirects
let result = client.get("https://example.com/loop")
.send()
.await;
match result {
Ok(response) => {
// Got final response within 10 redirects
}
Err(e) => {
// Too many redirects
println!("Redirect loop or too many redirects: {}", e);
}
}
}limited provides automatic protection against redirect loops.
use reqwest::{Client, redirect::Policy, Url};
async fn security_considerations() {
// Security concerns with automatic redirects:
// 1. Cross-domain redirects may leak credentials
// 2. Redirect chains can hide malicious destinations
// 3. Open redirect vulnerabilities
// Policy::none() allows security validation:
let client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
let response = client.get("https://trusted.com/api")
.send()
.await
.unwrap();
if response.status().is_redirection() {
let location = response.headers()
.get("Location")
.and_then(|v| v.to_str().ok())
.unwrap();
let dest_url = Url::parse(location).unwrap();
// Validate redirect destination
if dest_url.domain() != Some("trusted.com") {
// Reject cross-domain redirect
panic!("Cross-domain redirect blocked");
}
// Safe to follow
let final_response = client.get(dest_url).send().await.unwrap();
}
}none allows security validation of redirect targets.
use reqwest::{Client, redirect::Policy};
use url::Url;
async fn custom_policy() {
// Policy also supports custom redirect logic
let client = Client::builder()
.redirect(Policy::custom(|attempt| {
let url = attempt.url();
// Don't follow cross-domain redirects
if url.domain() != Some("original-domain.com") {
return attempt.stop();
}
// Don't follow more than 3 redirects
if attempt.previous().len() > 3 {
return attempt.too_many_redirects();
}
// Follow the redirect
attempt.follow()
}))
.build()
.unwrap();
}Policy::custom provides fine-grained redirect control.
use reqwest::{Client, redirect::Policy, StatusCode};
async fn redirect_status_codes() {
// Status codes that trigger redirect following:
// - 301 Moved Permanently
// - 302 Found
// - 303 See Other
// - 307 Temporary Redirect
// - 308 Permanent Redirect
// With Policy::limited(), all are followed
// With Policy::none(), all return as responses
let client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
let response = client.get("https://example.com/moved")
.send()
.await
.unwrap();
match response.status() {
StatusCode::MOVED_PERMANENTLY => {
// 301: Permanent redirect
// Should cache and use new URL
}
StatusCode::FOUND => {
// 302: Temporary redirect
// May change method to GET (historical)
}
StatusCode::SEE_OTHER => {
// 303: Redirect to different resource (GET)
// Always uses GET for follow-up
}
StatusCode::TEMPORARY_REDIRECT => {
// 307: Temporary redirect, preserve method
}
StatusCode::PERMANENT_REDIRECT => {
// 308: Permanent redirect, preserve method
}
_ => {}
}
}Different 3xx status codes have different semantics for redirect handling.
use reqwest::{Client, redirect::Policy, Method};
async fn method_handling() {
let client = Client::builder()
.redirect(Policy::limited(10))
.build()
.unwrap();
// POST + 302: Historical behavior changes method to GET
// POST + 307/308: Preserves POST method
// With Policy::limited():
// - 301/302/303: May change POST to GET (depends on status)
// - 307/308: Preserves original method
// With Policy::none():
// - You control method for follow-up request
// - Can choose to preserve or change as needed
let none_client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
let response = none_client
.post("https://example.com/submit")
.send()
.await
.unwrap();
if response.status() == StatusCode::TEMPORARY_REDIRECT {
let location = response.headers()
.get("Location")
.and_then(|v| v.to_str().ok())
.unwrap();
// 307 requires preserving method
let final_response = none_client
.post(location) // Preserve POST
.send()
.await
.unwrap();
}
}Method preservation depends on redirect status code and policy.
use reqwest::{Client, redirect::Policy, header::{HeaderMap, HeaderValue, AUTHORIZATION}};
async fn header_handling() {
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, HeaderValue::from_static("Bearer secret"));
let client = Client::builder()
.redirect(Policy::limited(10))
.build()
.unwrap();
// With Policy::limited():
// Authorization headers may be stripped on cross-origin redirects
// Sensitive headers like Authorization are protected
let response = client
.get("https://example.com/auth-required")
.headers(headers)
.send()
.await
.unwrap();
// If redirecting to different host, Authorization may not be sent
// With Policy::none():
// Full control over which headers to send on redirect
let none_client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
let response = none_client
.get("https://example.com/auth-required")
.header(AUTHORIZATION, "Bearer secret")
.send()
.await
.unwrap();
if response.status().is_redirection() {
let location = /* ... */;
// Explicitly decide to send Authorization or not
let final_response = none_client
.get(location)
.header(AUTHORIZATION, "Bearer secret") // Or not
.send()
.await
.unwrap();
}
}none gives explicit control over header handling during redirects.
use reqwest::{Client, redirect::Policy};
async fn when_to_use_limited() {
// Use Policy::limited(n) when:
// 1. You want automatic redirect following
// 2. You trust the redirect destinations
// 3. Simple API consumption
// 4. Standard web scraping
let client = Client::builder()
.redirect(Policy::limited(10))
.build()
.unwrap();
// Simple API call - let reqwest handle redirects
let response = client.get("https://api.example.com/v1/data")
.send()
.await
.unwrap();
// Common patterns:
// - Following HTTP -> HTTPS upgrades
// - Following canonical URLs
// - API endpoints that redirect to current version
}Use limited when you want convenience and trust redirect destinations.
use reqwest::{Client, redirect::Policy};
async fn when_to_use_none() {
// Use Policy::none() when:
// 1. You need to validate redirect targets
// 2. You want to track redirect chains
// 3. Security-sensitive applications
// 4. You need custom redirect logic
// 5. You need to preserve request bodies
let client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
// Security: validate all redirects
let response = client.get("https://api.example.com/auth")
.send()
.await
.unwrap();
// Tracking: log all redirect chains
if response.status().is_redirection() {
let location = response.headers()
.get("Location")
.and_then(|v| v.to_str().ok())
.unwrap();
println!("Redirected to: {}", location);
// Custom validation before following
if is_safe_redirect(location) {
// Follow manually
} else {
// Block suspicious redirect
}
}
}Use none when you need control, validation, or custom logic.
use reqwest::{Client, redirect::Policy};
async fn error_handling() {
// Policy::limited(n) errors:
// - "too many redirects" after n redirects
let limited_client = Client::builder()
.redirect(Policy::limited(3))
.build()
.unwrap();
match limited_client.get("https://example.com/many-redirects").send().await {
Ok(response) => {
// Successfully reached final destination
}
Err(e) => {
if e.to_string().contains("too many redirects") {
// Redirect limit exceeded
}
}
}
// Policy::none() never returns "too many redirects"
// Returns redirect response directly
let none_client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
let response = none_client.get("https://example.com/many-redirects")
.send()
.await
.unwrap(); // Returns 302 response, not error
}limited can error on too many redirects; none never does.
Policy::limited(n):
// - Follows redirects automatically up to n times
// - Returns final response after all redirects
// - Errors on redirect loops (n redirects exceeded)
// - Convenient for standard HTTP clients
// - Protects against infinite loops
// - Less control over redirect behavior
let client = Client::builder()
.redirect(Policy::limited(10))
.build()
.unwrap();Policy::none():
// - Never follows redirects automatically
// - Returns redirect response (3xx) directly
// - Requires manual redirect handling
// - Full control over redirect behavior
// - Can validate redirect targets for security
// - More code, more control
let client = Client::builder()
.redirect(Policy::none())
.build()
.unwrap();
// Manual redirect handling:
if response.status().is_redirection() {
let location = response.headers().get("Location")...;
// Validate, log, modify request as needed
// Follow redirect manually if desired
}Key differences:
// limited(n):
// - Convenience: automatic redirect following
// - Safety: prevents infinite loops
// - Returns: final response
// - Use when: you trust redirects and want simplicity
// none():
// - Control: manual redirect handling
// - Security: can validate redirect targets
// - Returns: redirect response itself
// - Use when: you need validation, logging, or custom logicKey insight: reqwest::redirect::Policy::limited(n) and Policy::none() represent two ends of a spectrum—limited provides automatic convenience with safety limits, while none provides complete control with manual handling. limited(n) is appropriate for trusted APIs, standard web requests, and scenarios where redirect chains are expected and safe. none() is essential for security-sensitive applications that need to validate redirect targets, prevent credential leakage on cross-domain redirects, implement custom redirect policies, or log redirect chains for auditing. The default behavior (limited(10)) is appropriate for most web clients, but security-conscious applications should consider none() or Policy::custom() to enforce redirect policies explicitly.