Reqwest Retry Middleware Backoff
Add retry to reqwest with retry-policies and tower middleware, restricting to idempotent methods and honoring Retry-After.
Code
use std::time::Duration;
use reqwest::Client;
use reqwest_middleware::ClientBuilder;
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware, classify_response_for_retry};
use retry_policies::RetryPolicy;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let retry_policy = ExponentialBackoff::builder()
.base(2)
.initial(Duration::from_millis(200))
.max_total_delay(Duration::from_secs(10))
.max_retries(3)
.build();
let client = ClientBuilder::new(Client::builder().timeout(Duration::from_secs(8)).build()?)
.with(RetryTransientMiddleware::new_with_policy(retry_policy))
.build();
let body: serde_json::Value = client
.get("https://api.example.com/catalog")
.query(&[("page", "2")])
.header("x-client", "sync/1.0")
.send()
.await?
.error_for_status()?
.json()
.await?;
println!("items: {}", body.get("items").and_then(|v| v.as_array()).map_or(0, |a| a.len()));
// 手动重试演示:仅幂等方法、遵循 Retry-After
let raw = Client::new();
let mut attempt = 0;
loop {
attempt += 1;
let resp = raw.get("https://api.example.com/rates").send().await?;
match classify_response_for_retry(&resp) {
Some(_) if resp.status().as_u16() == 429 => {
let wait = resp
.headers()
.get("retry-after")
.and_then(|v| v.to_str().ok())
.and_then(|s| s.parse::<u64>().ok())
.unwrap_or(2);
if attempt > 3 {
return Err("rate limited after retries".into());
}
tokio::time::sleep(Duration::from_secs(wait)).await;
}
_ => {
let text = resp.text().await?;
println!("attempt {attempt} ok, bytes: {}", text.len());
return Ok(());
}
}
}
}Report a problem with this exampleOpens a pre-filled issue on GitHub.