示例说明
reqwest-retry 把重试实现为 tower 中间件:策略对象声明退避参数——基数二、初始两百毫秒、总延迟封顶十秒、最多三次;响应分类器判定哪些失败值得重试,幂等方法默认在内。中间件形态让重试对调用代码完全透明,同一客户端的全体请求共享策略。
技术要点
- 策略对象声明退避与次数上限
- 响应分类器圈定可重试失败
- Retry-After 优先于本地退避
代码解读
第一段构建带超时的底层客户端,链上 RetryTransientMiddleware 后正常调用即可享受重试;error_for_status 把 4xx/5xx 转成错误参与判定。第二段的手动循环演示精细控制:429 响应读取 retry-after 头换算等待秒数,无头则默认两秒,超过三次放弃并报错。
使用建议
非幂等请求要显式排除或配幂等键。重试日志带尝试序号便于排障。持续 429 说明配额规划问题而非网络抖动。
代码示例
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(());
}
}
}
}