Rust 用 reqwest 封装健壮 HTTP 客户端:退避重试、超时与中间件
test12026-10-2719 次阅读
为什么不直接用裸请求
业务里直接 reqwest::get 没有超时、没有重试、出错信息也散。把客户端抽成带默认超时、指数退避重试、统一错误类型的结构体,调用方只需关心业务参数。
定义错误与客户端
use std::time::Duration;
use reqwest::Client;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum HttpError {
#[error("request failed: {0}")]
Req(#[from] reqwest::Error),
#[error("max retries exceeded")]
RetryExhausted,
}
pub struct ApiClient {
inner: Client,
retries: u32,
}
impl ApiClient {
pub fn new(retries: u32) -> Self {
let inner = Client::builder()
.timeout(Duration::from_secs(5))
.build()
.expect("client build");
Self { inner, retries }
}
pub async fn get_json<T: serde::de::DeserializeOwned>(
&self,
url: &str,
) -> Result<T, HttpError> {
let mut attempt: u32 = 0;
loop {
match self.inner.get(url).send().await {
Ok(resp) => return Ok(resp.json::<T>().await?),
Err(e) if attempt < self.retries => {
attempt += 1;
let backoff = Duration::from_millis(100 * 2u64.pow(attempt));
tokio::time::sleep(backoff).await;
}
Err(_) => return Err(HttpError::RetryExhausted),
}
}
}
}
用法
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = ApiClient::new(3);
let post: serde_json::Value = client
.get_json("https://api.example.com/post/1")
.await?;
println!("{:?}", post);
Ok(())
}
工程注意点
- 超时一定要设,否则一个慢响应能卡死整个任务。
- 重试只针对幂等请求(GET、部分 PUT),写操作重试要先确认幂等。
- 退避用指数增长并加随机抖动,避免所有失败请求同时重试。
- 错误类型用 thiserror 收敛,调用方用 ? 一路上抛,不用到处 match。
T
test1
文章作者
为什么不直接用裸请求 业务里直接 reqwest::get 没有超时、没有重试、出错信息也散。把客户端抽成带默认超时、...
- 分类
- 技术
- 发布时间
- 2026-10-27
- 字数
- 约 1699 字
- 阅读
- 19 次
