概述
reqwest 是 Rust 生态系统中最流行的 HTTP 客户端库,提供了简单、高效、功能丰富的 HTTP/HTTPS 请求解决方案。它基于 hyper 库构建,支持异步和同步两种使用方式。
核心特性
-
✅ 异步和阻塞式 API - 支持 Tokio 异步运行时和同步阻塞调用
-
✅ 多种数据格式 - 支持纯文本、JSON、表单、multipart 等
-
✅ 自动重定向 - 可自定义重定向策略
-
✅ 代理支持 - HTTP/HTTPS/SOCKS5 代理
-
✅ TLS/SSL - 默认使用系统原生 TLS,可选 rustls
-
✅ Cookie 管理 - 自动存储和发送 Cookie
-
✅ 连接池 - 自动管理 keep-alive 连接复用
-
✅ 超时控制 - 连接超时、读取超时、总超时
-
✅ WASM 支持 - 可在浏览器环境运行
版本信息
-
最新版本: 0.12.25 (2025-12-08)
-
最低 Rust 版本: 1.70+
-
许可证: Apache-2.0 / MIT 双许可
安装和配置
基本依赖
[dependencies]
reqwest = "0.12"
tokio = { version = "1", features = ["full"] }
启用可选特性
[dependencies]
reqwest = { version = "0.12", features = [
"json", # JSON 序列化/反序列化
"multipart", # multipart/form-data 支持
"stream", # 流式请求/响应
"cookies", # Cookie 管理
"gzip", # gzip 压缩
"brotli", # brotli 压缩
"deflate", # deflate 压缩
"blocking", # 同步阻塞 API
"rustls-tls", # 使用 rustls 替代原生 TLS
"socks", # SOCKS5 代理支持
] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
特性标志说明
TLS 相关
-
default-tls (默认启用) - 使用系统原生 TLS
-
native-tls - 显式使用 native-tls
-
native-tls-vendored - 静态链接 OpenSSL
-
rustls-tls - 使用 rustls (纯 Rust TLS 实现)
-
rustls-tls-webpki-roots - rustls + webpki 根证书
-
rustls-tls-native-roots - rustls + 系统根证书
-
rustls-tls-manual-roots - rustls + 手动指定根证书
功能相关
-
http2 (默认启用) - HTTP/2 支持
-
charset (默认启用) - 字符集检测和转换
-
json - JSON 序列化(需要 serde)
-
multipart - multipart/form-data
-
stream - futures::Stream 支持
-
cookies - Cookie jar 支持
-
blocking - 同步阻塞 API
压缩相关
-
gzip - gzip 压缩/解压
-
brotli - brotli 压缩/解压
-
zstd - zstd 压缩/解压
-
deflate - deflate 压缩/解压
其他
-
socks - SOCKS5 代理支持
-
hickory-dns - 使用 hickory-dns 异步 DNS 解析器
-
system-proxy (默认启用) - 自动使用系统代理设置
快速开始
1. 简单 GET 请求
use reqwest;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
// 最简单的 GET 请求
let body = reqwest::get("https://www.rust-lang.org")
.await?
.text()
.await?;
println!("Body: {}", body);
Ok(())
}
2. 使用 Client 复用连接
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建可复用的 Client (推荐方式)
let client = Client::new();
// 发送多个请求,复用连接池
let resp1 = client.get("https://httpbin.org/get")
.send()
.await?
.text()
.await?;
let resp2 = client.get("https://httpbin.org/ip")
.send()
.await?
.text()
.await?;
println!("Response 1: {}", resp1);
println!("Response 2: {}", resp2);
Ok(())
}
3. JSON 请求和响应
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Serialize, Deserialize)]
struct User {
name: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// GET 请求并解析 JSON
let ip_data = client.get("https://httpbin.org/ip")
.send()
.await?
.json::<HashMap<String, String>>()
.await?;
println!("IP: {:?}", ip_data);
// POST JSON 数据
let user = User {
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
};
let response = client.post("https://httpbin.org/post")
.json(&user)
.send()
.await?;
println!("Status: {}", response.status());
Ok(())
}
Client 和 ClientBuilder
创建 Client
use reqwest::{Client, ClientBuilder};
use std::time::Duration;
// 方式 1: 使用默认配置
let client = Client::new();
// 方式 2: 使用 Builder 自定义配置
let client = ClientBuilder::new()
.timeout(Duration::from_secs(30))
.build()?;
ClientBuilder 配置选项
use reqwest::{ClientBuilder, redirect::Policy};
use std::time::Duration;
let client = ClientBuilder::new()
// 超时设置
.timeout(Duration::from_secs(30)) // 总超时
.connect_timeout(Duration::from_secs(10)) // 连接超时
.read_timeout(Duration::from_secs(20)) // 读取超时
// 连接池设置
.pool_idle_timeout(Duration::from_secs(90)) // 空闲连接保持时间
.pool_max_idle_per_host(10) // 每个主机最大空闲连接数
// 重定向策略
.redirect(Policy::limited(10)) // 最多重定向10次
// .redirect(Policy::none()) // 禁用重定向
// .redirect(Policy::custom(|attempt| { ... })) // 自定义策略
// 默认请求头
.default_headers({
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::USER_AGENT,
reqwest::header::HeaderValue::from_static("MyApp/1.0")
);
headers.insert(
reqwest::header::ACCEPT,
reqwest::header::HeaderValue::from_static("application/json")
);
headers
})
// Cookie 存储
.cookie_store(true) // 启用 cookie jar
// 代理设置
.proxy(reqwest::Proxy::http("http://proxy.example.com:8080")?)
.proxy(reqwest::Proxy::https("https://proxy.example.com:8080")?)
// .no_proxy() // 禁用系统代理
// 压缩
.gzip(true) // 启用 gzip
.brotli(true) // 启用 brotli
.deflate(true) // 启用 deflate
// HTTP 版本
.http1_only() // 仅 HTTP/1.x
// .http2_prior_knowledge() // 仅 HTTP/2
// TLS 配置
.danger_accept_invalid_certs(false) // 不接受无效证书
.danger_accept_invalid_hostnames(false) // 不接受无效主机名
.use_rustls_tls() // 使用 rustls
// 其他
.user_agent("MyApp/1.0") // 快捷设置 User-Agent
.referer(false) // 不自动设置 Referer
.tcp_nodelay(true) // 禁用 Nagle 算法
.tcp_keepalive(Duration::from_secs(60)) // TCP keepalive
.build()?;
HTTP 方法
GET 请求
use reqwest::Client;
let client = Client::new();
// 基本 GET
let response = client.get("https://httpbin.org/get")
.send()
.await?;
// 带查询参数
let response = client.get("https://httpbin.org/get")
.query(&[("key1", "value1"), ("key2", "value2")])
.send()
.await?;
// 使用结构体作为查询参数
use serde::Serialize;
#[derive(Serialize)]
struct Params {
page: u32,
limit: u32,
}
let params = Params { page: 1, limit: 20 };
let response = client.get("https://api.example.com/users")
.query(¶ms)
.send()
.await?;
POST 请求
use reqwest::Client;
use serde::Serialize;
let client = Client::new();
// 1. 纯文本 Body
let response = client.post("https://httpbin.org/post")
.body("plain text data")
.send()
.await?;
// 2. JSON Body
#[derive(Serialize)]
struct User {
name: String,
email: String,
}
let user = User {
name: "John".to_string(),
email: "john@example.com".to_string(),
};
let response = client.post("https://httpbin.org/post")
.json(&user)
.send()
.await?;
// 3. 表单数据 (application/x-www-form-urlencoded)
let params = [("username", "john"), ("password", "secret")];
let response = client.post("https://httpbin.org/post")
.form(¶ms)
.send()
.await?;
// 4. 二进制数据
let bytes = vec![1, 2, 3, 4, 5];
let response = client.post("https://httpbin.org/post")
.body(bytes)
.send()
.await?;
PUT / PATCH / DELETE
use reqwest::Client;
let client = Client::new();
// PUT - 完整替换资源
let response = client.put("https://api.example.com/users/123")
.json(&updated_user)
.send()
.await?;
// PATCH - 部分更新资源
let response = client.patch("https://api.example.com/users/123")
.json(&partial_update)
.send()
.await?;
// DELETE - 删除资源
let response = client.delete("https://api.example.com/users/123")
.send()
.await?;
HEAD / OPTIONS
// HEAD - 获取响应头(不获取 body)
let response = client.head("https://httpbin.org/get")
.send()
.await?;
let content_length = response.headers()
.get("content-length")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.parse::<u64>().ok());
// OPTIONS - 获取服务器支持的方法
let response = client.request(reqwest::Method::OPTIONS, "https://api.example.com")
.send()
.await?;
RequestBuilder
设置请求头
use reqwest::{Client, header};
let client = Client::new();
let response = client.get("https://api.example.com/data")
// 方式 1: 单个设置
.header("X-Custom-Header", "value")
.header(header::AUTHORIZATION, "Bearer token123")
.header(header::CONTENT_TYPE, "application/json")
// 方式 2: 批量设置
.headers({
let mut headers = header::HeaderMap::new();
headers.insert(header::ACCEPT, header::HeaderValue::from_static("application/json"));
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("MyApp/1.0"));
headers
})
.send()
.await?;
认证
use reqwest::Client;
let client = Client::new();
// 1. Basic 认证
let response = client.get("https://api.example.com/data")
.basic_auth("username", Some("password"))
.send()
.await?;
// 2. Bearer Token
let response = client.get("https://api.example.com/data")
.bearer_auth("your_token_here")
.send()
.await?;
// 3. 自定义 Authorization 头
let response = client.get("https://api.example.com/data")
.header("Authorization", "Custom token123")
.send()
.await?;
超时设置
use std::time::Duration;
// 单个请求的超时
let response = client.get("https://api.example.com/slow")
.timeout(Duration::from_secs(5)) // 覆盖 Client 的默认超时
.send()
.await?;
Body 设置
use reqwest::Body;
// 1. 字符串
let response = client.post("https://httpbin.org/post")
.body("text data")
.send()
.await?;
// 2. 字节数组
let bytes = vec![0u8; 1024];
let response = client.post("https://httpbin.org/post")
.body(bytes)
.send()
.await?;
// 3. 流式 Body
use tokio::fs::File;
use tokio_util::codec::{BytesCodec, FramedRead};
let file = File::open("large_file.bin").await?;
let stream = FramedRead::new(file, BytesCodec::new());
let body = Body::wrap_stream(stream);
let response = client.post("https://httpbin.org/post")
.body(body)
.send()
.await?;
处理响应 (Response)
获取响应数据
use reqwest::Client;
let client = Client::new();
let response = client.get("https://httpbin.org/get").send().await?;
// 1. 获取文本
let text = response.text().await?;
println!("Text: {}", text);
// 2. 获取字节数组
let bytes = response.bytes().await?;
println!("Bytes length: {}", bytes.len());
// 3. 解析 JSON
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct ApiResponse {
origin: String,
}
let json_data = response.json::<ApiResponse>().await?;
println!("Origin: {}", json_data.origin);
// 4. 流式读取
use futures::StreamExt;
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
println!("Chunk size: {}", chunk.len());
}
检查响应状态
use reqwest::StatusCode;
let response = client.get("https://httpbin.org/get").send().await?;
// 获取状态码
let status = response.status();
println!("Status: {}", status);
println!("Status code: {}", status.as_u16());
// 判断状态
if status.is_success() {
println!("Success!");
} else if status.is_client_error() {
println!("Client error: {}", status);
} else if status.is_server_error() {
println!("Server error: {}", status);
}
// 匹配特定状态码
match status {
StatusCode::OK => println!("200 OK"),
StatusCode::NOT_FOUND => println!("404 Not Found"),
StatusCode::INTERNAL_SERVER_ERROR => println!("500 Internal Server Error"),
_ => println!("Other status: {}", status),
}
// 确保成功,否则返回错误
let response = response.error_for_status()?;
读取响应头
use reqwest::header;
let response = client.get("https://httpbin.org/get").send().await?;
// 获取特定头
if let Some(content_type) = response.headers().get(header::CONTENT_TYPE) {
println!("Content-Type: {:?}", content_type);
}
// 遍历所有头
for (name, value) in response.headers() {
println!("{}: {:?}", name, value);
}
// 常用头字段
let content_length = response.content_length();
println!("Content-Length: {:?}", content_length);
获取 URL 和版本信息
let response = client.get("https://httpbin.org/get").send().await?;
// 最终 URL (可能与请求 URL 不同,如果发生了重定向)
let url = response.url();
println!("Final URL: {}", url);
// HTTP 版本
let version = response.version();
println!("Version: {:?}", version); // HTTP/1.1 或 HTTP/2
// 远程地址
if let Some(remote_addr) = response.remote_addr() {
println!("Remote address: {}", remote_addr);
}
高级功能
Multipart 表单 (文件上传)
use reqwest::{Client, multipart};
let client = Client::new();
// 创建 multipart form
let form = multipart::Form::new()
// 添加文本字段
.text("username", "john_doe")
.text("email", "john@example.com")
// 添加文件 (从路径)
.file("avatar", "/path/to/avatar.jpg").await?
// 添加文件 (从内存)
.part(
"document",
multipart::Part::bytes(vec![1, 2, 3, 4])
.file_name("document.pdf")
.mime_str("application/pdf")?
)
// 添加流式文件
.part(
"video",
multipart::Part::stream(file_stream)
.file_name("video.mp4")
.mime_str("video/mp4")?
);
let response = client.post("https://httpbin.org/post")
.multipart(form)
.send()
.await?;
Cookie 管理
use reqwest::{Client, ClientBuilder, cookie::Jar};
use std::sync::Arc;
// 方式 1: 自动 Cookie 管理
let client = ClientBuilder::new()
.cookie_store(true)
.build()?;
// 发送请求,自动保存 cookie
client.get("https://httpbin.org/cookies/set?name=value").send().await?;
// 后续请求自动发送 cookie
let response = client.get("https://httpbin.org/cookies").send().await?;
// 方式 2: 手动管理 Cookie Jar
let jar = Arc::new(Jar::default());
let client = ClientBuilder::new()
.cookie_provider(jar.clone())
.build()?;
// 手动添加 cookie
let url = "https://example.com".parse::<reqwest::Url>()?;
jar.add_cookie_str("session_id=abc123", &url);
// 查看 cookies
for cookie in jar.cookies(&url).iter() {
println!("Cookie: {:?}", cookie);
}
代理设置
use reqwest::{Client, ClientBuilder, Proxy};
// 1. HTTP 代理
let client = ClientBuilder::new()
.proxy(Proxy::http("http://proxy.example.com:8080")?)
.build()?;
// 2. HTTPS 代理
let client = ClientBuilder::new()
.proxy(Proxy::https("https://proxy.example.com:8080")?)
.build()?;
// 3. 全局代理 (HTTP + HTTPS)
let client = ClientBuilder::new()
.proxy(Proxy::all("http://proxy.example.com:8080")?)
.build()?;
// 4. 带认证的代理
let proxy = Proxy::all("http://proxy.example.com:8080")?
.basic_auth("proxy_user", "proxy_pass");
let client = ClientBuilder::new()
.proxy(proxy)
.build()?;
// 5. SOCKS5 代理 (需要 socks 特性)
#[cfg(feature = "socks")]
let client = ClientBuilder::new()
.proxy(Proxy::all("socks5://127.0.0.1:1080")?)
.build()?;
// 6. 禁用系统代理
let client = ClientBuilder::new()
.no_proxy()
.build()?;
// 7. 代理白名单 (某些域名不走代理)
let client = ClientBuilder::new()
.proxy(Proxy::all("http://proxy.example.com:8080")?)
.no_proxy(reqwest::NoProxy::from_string("localhost,127.0.0.1,.internal.com"))
.build()?;
重定向控制
use reqwest::{Client, ClientBuilder, redirect};
// 1. 限制重定向次数
let client = ClientBuilder::new()
.redirect(redirect::Policy::limited(5)) // 最多5次
.build()?;
// 2. 禁用重定向
let client = ClientBuilder::new()
.redirect(redirect::Policy::none())
.build()?;
// 手动处理重定向
let response = client.get("https://httpbin.org/redirect/3")
.send()
.await?;
if response.status().is_redirection() {
if let Some(location) = response.headers().get("location") {
println!("Redirect to: {:?}", location);
}
}
// 3. 自定义重定向策略
let client = ClientBuilder::new()
.redirect(redirect::Policy::custom(|attempt| {
if attempt.previous().len() > 5 {
attempt.error("too many redirects")
} else if attempt.url().host_str() == Some("malicious.com") {
attempt.stop()
} else {
attempt.follow()
}
}))
.build()?;
流式上传和下载
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use futures::StreamExt;
// 流式下载大文件
let response = client.get("https://example.com/large_file.zip")
.send()
.await?;
let mut file = File::create("downloaded_file.zip").await?;
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
file.write_all(&chunk).await?;
}
// 带进度的下载
let total_size = response.content_length().unwrap_or(0);
let mut downloaded: u64 = 0;
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
file.write_all(&chunk).await?;
downloaded += chunk.len() as u64;
let progress = (downloaded as f64 / total_size as f64) * 100.0;
println!("Downloaded: {:.2}%", progress);
}
TLS 配置
use reqwest::{ClientBuilder, Certificate, Identity};
use std::fs;
// 1. 添加自定义根证书
let cert = fs::read("custom_ca.crt")?;
let cert = Certificate::from_pem(&cert)?;
let client = ClientBuilder::new()
.add_root_certificate(cert)
.build()?;
// 2. 客户端证书 (mTLS)
let identity_bytes = fs::read("client_cert.p12")?;
let identity = Identity::from_pkcs12_der(&identity_bytes, "password")?;
let client = ClientBuilder::new()
.identity(identity)
.build()?;
// 3. 危险选项 (仅用于开发/测试)
let client = ClientBuilder::new()
.danger_accept_invalid_certs(true) // 接受无效证书
.danger_accept_invalid_hostnames(true) // 接受无效主机名
.build()?;
// 4. 使用 rustls 替代 native-tls
let client = ClientBuilder::new()
.use_rustls_tls()
.build()?;
连接池配置
use std::time::Duration;
let client = ClientBuilder::new()
// 每个主机的最大空闲连接数
.pool_max_idle_per_host(10)
// 空闲连接保持时间
.pool_idle_timeout(Duration::from_secs(90))
// TCP keepalive
.tcp_keepalive(Duration::from_secs(60))
// 禁用 Nagle 算法
.tcp_nodelay(true)
.build()?;
错误处理
Error 类型
use reqwest::Error;
match client.get("https://example.com").send().await {
Ok(response) => {
// 处理响应
}
Err(e) => {
// 检查错误类型
if e.is_timeout() {
eprintln!("Request timeout");
} else if e.is_connect() {
eprintln!("Connection error");
} else if e.is_body() {
eprintln!("Body error");
} else if e.is_decode() {
eprintln!("Decode error");
} else if e.is_redirect() {
eprintln!("Redirect error");
} else if e.is_status() {
eprintln!("Status error: {:?}", e.status());
} else if e.is_request() {
eprintln!("Request error");
} else {
eprintln!("Other error: {}", e);
}
// 获取详细信息
if let Some(url) = e.url() {
eprintln!("URL: {}", url);
}
if let Some(status) = e.status() {
eprintln!("Status code: {}", status);
}
}
}
自动错误处理
// error_for_status() 方法
let response = client.get("https://httpbin.org/status/404")
.send()
.await?
.error_for_status()?; // 如果状态码是 4xx 或 5xx,返回错误
// 等价于:
let response = client.get("https://httpbin.org/status/404")
.send()
.await?;
if !response.status().is_success() {
return Err(response.error_for_status().unwrap_err());
}
自定义错误处理
use reqwest::{Client, StatusCode};
use thiserror::Error;
#[derive(Error, Debug)]
enum ApiError {
#[error("HTTP error: {0}")]
Http(#[from] reqwest::Error),
#[error("API error: {status}, message: {message}")]
Api {
status: StatusCode,
message: String,
},
#[error("Parse error: {0}")]
Parse(String),
}
async fn api_request(url: &str) -> Result<String, ApiError> {
let response = Client::new()
.get(url)
.send()
.await?;
let status = response.status();
if !status.is_success() {
let error_text = response.text().await?;
return Err(ApiError::Api {
status,
message: error_text,
});
}
let text = response.text().await?;
Ok(text)
}
同步阻塞 API
启用 blocking 特性
[dependencies]
reqwest = { version = "0.12", features = ["blocking", "json"] }
基本用法
use reqwest::blocking::Client;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建阻塞 Client
let client = Client::new();
// 发送请求 (阻塞直到完成)
let response = client.get("https://httpbin.org/get").send()?;
let text = response.text()?;
println!("Response: {}", text);
Ok(())
}
阻塞 API 完整示例
use reqwest::blocking::{Client, ClientBuilder};
use std::time::Duration;
use serde::Deserialize;
#[derive(Deserialize, Debug)]
struct IpInfo {
origin: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建配置的 Client
let client = ClientBuilder::new()
.timeout(Duration::from_secs(10))
.build()?;
// GET 请求
let ip_info = client.get("https://httpbin.org/ip")
.send()?
.json::<IpInfo>()?;
println!("Your IP: {}", ip_info.origin);
// POST JSON
let response = client.post("https://httpbin.org/post")
.json(&serde_json::json!({
"name": "John",
"email": "john@example.com"
}))
.send()?;
println!("Status: {}", response.status());
Ok(())
}
实战示例
1. RESTful API 客户端
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::error::Error;
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: Option<u32>,
name: String,
email: String,
}
struct ApiClient {
client: Client,
base_url: String,
token: String,
}
impl ApiClient {
fn new(base_url: String, token: String) -> Self {
let client = Client::new();
Self { client, base_url, token }
}
// GET /users
async fn list_users(&self) -> Result<Vec<User>, Box<dyn Error>> {
let url = format!("{}/users", self.base_url);
let users = self.client.get(&url)
.bearer_auth(&self.token)
.send()
.await?
.error_for_status()?
.json::<Vec<User>>()
.await?;
Ok(users)
}
// GET /users/:id
async fn get_user(&self, id: u32) -> Result<User, Box<dyn Error>> {
let url = format!("{}/users/{}", self.base_url, id);
let user = self.client.get(&url)
.bearer_auth(&self.token)
.send()
.await?
.error_for_status()?
.json::<User>()
.await?;
Ok(user)
}
// POST /users
async fn create_user(&self, user: &User) -> Result<User, Box<dyn Error>> {
let url = format!("{}/users", self.base_url);
let created_user = self.client.post(&url)
.bearer_auth(&self.token)
.json(user)
.send()
.await?
.error_for_status()?
.json::<User>()
.await?;
Ok(created_user)
}
// PUT /users/:id
async fn update_user(&self, id: u32, user: &User) -> Result<User, Box<dyn Error>> {
let url = format!("{}/users/{}", self.base_url, id);
let updated_user = self.client.put(&url)
.bearer_auth(&self.token)
.json(user)
.send()
.await?
.error_for_status()?
.json::<User>()
.await?;
Ok(updated_user)
}
// DELETE /users/:id
async fn delete_user(&self, id: u32) -> Result<(), Box<dyn Error>> {
let url = format!("{}/users/{}", self.base_url, id);
let response = self.client.delete(&url)
.bearer_auth(&self.token)
.send()
.await?
.error_for_status()?;
if response.status() == StatusCode::NO_CONTENT {
Ok(())
} else {
Err("Unexpected response".into())
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let api = ApiClient::new(
"https://api.example.com".to_string(),
"your_token_here".to_string()
);
// 获取所有用户
let users = api.list_users().await?;
println!("Users: {:?}", users);
// 创建新用户
let new_user = User {
id: None,
name: "John Doe".to_string(),
email: "john@example.com".to_string(),
};
let created = api.create_user(&new_user).await?;
println!("Created user: {:?}", created);
Ok(())
}
2. 带重试的请求
use reqwest::{Client, StatusCode};
use std::time::Duration;
use tokio::time::sleep;
async fn retry_request<F, Fut, T>(
max_retries: u32,
delay: Duration,
f: F,
) -> Result<T, reqwest::Error>
where
F: Fn() -> Fut,
Fut: std::future::Future<Output = Result<T, reqwest::Error>>,
{
let mut attempts = 0;
loop {
match f().await {
Ok(result) => return Ok(result),
Err(e) if attempts < max_retries => {
attempts += 1;
eprintln!("Request failed (attempt {}), retrying... Error: {}", attempts, e);
sleep(delay).await;
}
Err(e) => return Err(e),
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let url = "https://httpbin.org/status/500";
let response = retry_request(3, Duration::from_secs(2), || {
let client = client.clone();
let url = url.to_string();
async move {
client.get(&url).send().await
}
}).await?;
println!("Final status: {}", response.status());
Ok(())
}
3. 并发请求
use reqwest::Client;
use futures::future::join_all;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let urls = vec![
"https://httpbin.org/delay/1",
"https://httpbin.org/delay/2",
"https://httpbin.org/delay/3",
];
// 并发发送所有请求
let futures = urls.into_iter().map(|url| {
let client = client.clone();
async move {
let response = client.get(url).send().await?;
let text = response.text().await?;
Ok::<_, reqwest::Error>(text)
}
});
let results = join_all(futures).await;
for (i, result) in results.into_iter().enumerate() {
match result {
Ok(text) => println!("Request {} succeeded, length: {}", i, text.len()),
Err(e) => eprintln!("Request {} failed: {}", i, e),
}
}
Ok(())
}
4. 下载进度条
use reqwest::Client;
use futures::StreamExt;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use indicatif::{ProgressBar, ProgressStyle};
async fn download_with_progress(
url: &str,
file_path: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let response = client.get(url).send().await?;
let total_size = response.content_length().unwrap_or(0);
// 创建进度条
let pb = ProgressBar::new(total_size);
pb.set_style(
ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({eta})")
.unwrap()
.progress_chars("#>-")
);
let mut file = File::create(file_path).await?;
let mut downloaded: u64 = 0;
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
file.write_all(&chunk).await?;
downloaded += chunk.len() as u64;
pb.set_position(downloaded);
}
pb.finish_with_message("Download complete");
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
download_with_progress(
"https://example.com/large_file.zip",
"downloaded_file.zip"
).await?;
Ok(())
}
性能优化建议
1. 复用 Client
// ❌ 不推荐 - 每次请求创建新 Client
async fn bad_example() {
for _ in 0..100 {
let client = Client::new();
client.get("https://api.example.com").send().await;
}
}
// ✅ 推荐 - 复用 Client
async fn good_example() {
let client = Client::new();
for _ in 0..100 {
client.get("https://api.example.com").send().await;
}
}
2. 启用 HTTP/2
// HTTP/2 默认启用,支持多路复用
let client = ClientBuilder::new()
.http2_prior_knowledge() // 强制使用 HTTP/2
.build()?;
3. 启用压缩
let client = ClientBuilder::new()
.gzip(true)
.brotli(true)
.build()?;
4. 调整连接池
let client = ClientBuilder::new()
.pool_max_idle_per_host(20) // 增加每个主机的连接池大小
.pool_idle_timeout(Duration::from_secs(120))
.build()?;
5. 使用流式处理大文件
// ❌ 不推荐 - 将整个响应加载到内存
let bytes = response.bytes().await?;
// ✅ 推荐 - 流式处理
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
process_chunk(chunk?);
}
常见问题
1. 如何处理 JSON 解析错误?
match response.json::<MyStruct>().await {
Ok(data) => println!("Data: {:?}", data),
Err(e) => {
if e.is_decode() {
eprintln!("JSON decode error: {}", e);
}
}
}
2. 如何设置自定义 DNS 解析器?
// 需要 hickory-dns 特性
let client = ClientBuilder::new()
.dns_resolver(Arc::new(custom_resolver))
.build()?;
3. 如何处理大文件上传?
use tokio::fs::File;
use tokio_util::codec::{BytesCodec, FramedRead};
let file = File::open("large_file.bin").await?;
let stream = FramedRead::new(file, BytesCodec::new());
let body = reqwest::Body::wrap_stream(stream);
client.post("https://example.com/upload")
.body(body)
.send()
.await?;
4. 如何跳过 SSL 证书验证? (仅用于测试)
let client = ClientBuilder::new()
.danger_accept_invalid_certs(true)
.build()?;
5. 如何设置自定义 User-Agent?
let client = ClientBuilder::new()
.user_agent("MyApp/1.0")
.build()?;
与其他库的比较
reqwest vs hyper
| 特性 | reqwest | hyper |
|---|---|---|
| 易用性 | 高 (高级 API) | 低 (底层 API) |
| 功能完整性 | 丰富 | 基础 |
| 性能 | 高 | 非常高 |
| 适用场景 | 应用开发 | 库开发/高性能场景 |
reqwest vs ureq
| 特性 | reqwest | ureq |
|---|---|---|
| 异步支持 | 是 | 否 (仅同步) |
| 依赖大小 | 较大 | 小 |
| 功能 | 丰富 | 简单 |
| 适用场景 | 现代异步应用 | 简单同步场景/CLI 工具 |
相关资源
官方文档
相关库
学习资源
总结
reqwest 是 Rust 生态中最成熟、功能最完整的 HTTP 客户端库。它提供了:
✅ 简单易用 - 高级 API 设计,几行代码即可完成请求 ✅ 功能丰富 - 支持各种 HTTP 特性和数据格式 ✅ 性能优秀 - 基于 hyper,支持 HTTP/2,连接复用 ✅ 异步优先 - 与 Tokio 深度集成,适合现代异步应用 ✅ 可配置性强 - 提供大量配置选项,满足各种需求 ✅ 生产就绪 - 被广泛使用,经过充分测试
适用场景
-
✅ RESTful API 客户端
-
✅ 微服务间通信
-
✅ Web 爬虫
-
✅ 文件下载/上传
-
✅ WebHook 触发
-
✅ 第三方 API 集成
不适用场景
-
❌ 需要极致性能且愿意使用底层 API (考虑 hyper)
-
❌ 简单 CLI 工具且不需要异步 (考虑 ureq)
-
❌ 嵌入式设备资源受限 (考虑更轻量的库)