在 Rust 生态中,HTTP 客户端开发是高频场景------无论是调用第三方 API、爬取网页内容,还是构建服务间通信链路,都需要可靠的 HTTP 工具支持。reqwest 作为 Rust 最主流的 HTTP 客户端库,凭借"同步/异步双支持""API 简洁易用""功能全面""性能优异"等特性,成为绝大多数 Rust 项目的首选。它不仅封装了底层的 HTTP 协议细节,还提供了 JSON 序列化、表单提交、文件上传、超时控制等开箱即用的能力。本文将从基础实践到进阶技巧,结合详细示例,带你吃透 reqwest 库的全场景使用。
一、环境准备:引入 reqwest 与依赖配置
reqwest 支持同步和异步两种模式,异步模式依赖 Tokio 运行时;同时可根据需求开启 JSON 解析、TLS 加密等特性。首先在 Cargo.toml 中配置依赖。
1. 基础依赖(同步+JSON 支持)
仅需同步请求和 JSON 解析功能,引入核心依赖即可:
toml
[dependencies]
reqwest = { version = "0.11", features = ["json"] } # json特性用于自动序列化/反序列化
serde = { version = "1.0", features = ["derive"] } # 与reqwest配合实现JSON处理
2. 异步依赖(推荐,高并发场景)
异步模式是 reqwest 的推荐用法,适合高并发场景,需搭配 Tokio 运行时:
toml
[dependencies]
reqwest = { version = "0.11", features = ["json", "async-tls"] } # async-tls开启异步TLS支持
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] } # 异步运行时,支持async/await
3. 拓展特性(按需开启)
根据业务需求开启额外特性:
-
form:支持表单提交(application/x-www-form-urlencoded)。 -
multipart:支持文件上传(multipart/form-data)。 -
cookies:支持 Cookie 持久化。 -
gzip/brotli:支持响应压缩解压。
示例:开启表单、文件上传、Cookie 特性:
toml
reqwest = { version = "0.11", features = ["json", "async-tls", "form", "multipart", "cookies"] }
二、核心基础:同步 HTTP 请求
同步请求用法简单,适合低并发场景(如脚本工具、简单接口调用),无需依赖异步运行时。reqwest 通过 reqwest::blocking 模块提供同步 API,核心流程为"构建请求→发送请求→处理响应"。
1. GET 请求(基础场景)
GET 请求用于获取资源,支持查询参数、请求头设置,下面以调用公开 API 为例:
rust
use reqwest::blocking::Client;
use serde::Deserialize;
// 定义API响应对应的结构体(自动反序列化JSON)
#[derive(Debug, Deserialize)]
struct IpInfo {
ip: String,
city: String,
region: String,
country: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 1. 创建同步客户端(可复用,推荐单例模式)
let client = Client::new();
// 2. 发送GET请求:带查询参数(?format=json),设置请求头
let response = client.get("https://ipapi.co/json/")
.query(&[("format", "json")]) // 设置查询参数
.header("User-Agent", "reqwest-demo/1.0") // 设置User-Agent
.send()?; // 发送请求,?传播错误
// 3. 处理响应:检查状态码,反序列化为结构体
println!("响应状态码:{}", response.status());
if response.status().is_success() {
let ip_info: IpInfo = response.json()?; // 自动解析JSON为结构体
println!("IP信息:{:?}", ip_info);
} else {
eprintln!("请求失败:{}", response.text()?);
}
Ok(())
}
关键说明:Client::new() 创建的客户端可复用,内部维护连接池,比每次创建新客户端更高效;json() 方法依赖 serde,需确保响应格式与结构体字段匹配。
2. POST 请求(表单与 JSON 提交)
POST 请求用于提交数据,reqwest 支持表单(form)和 JSON 两种主流提交方式,需开启对应特性。
示例1:POST 表单提交(application/x-www-form-urlencoded)
rust
use reqwest::blocking::Client;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct LoginResponse {
code: u16,
msg: String,
token: Option<String>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// 构建表单数据(键值对)
let form_data = [
("username", "rust_dev"),
("password", "123456"),
];
// 发送POST表单请求
let response = client.post("https://api.example.com/login")
.form(&form_data) // 自动设置Content-Type为application/x-www-form-urlencoded
.send()?;
let login_res: LoginResponse = response.json()?;
println!("登录结果:{:?}", login_res);
Ok(())
}
示例2:POST JSON 提交(application/json)
rust
use reqwest::blocking::Client;
use serde::{Serialize, Deserialize};
// 定义请求体结构体(自动序列化为JSON)
#[derive(Debug, Serialize)]
struct LoginRequest {
username: String,
password: String,
remember_me: bool,
}
#[derive(Debug, Deserialize)]
struct LoginResponse {
code: u16,
msg: String,
token: Option<String>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// 构建请求体(自动序列化为JSON)
let login_req = LoginRequest {
username: "rust_dev".to_string(),
password: "123456".to_string(),
remember_me: true,
};
// 发送POST JSON请求
let response = client.post("https://api.example.com/login")
.json(&login_req) // 自动设置Content-Type为application/json,序列化请求体
.send()?;
let login_res: LoginResponse = response.json()?;
println!("登录结果:{:?}", login_res);
Ok(())
}
三、进阶核心:异步 HTTP 请求
异步请求是 reqwest 的推荐用法,基于 Tokio 运行时,支持高并发场景(如服务间通信、批量接口调用),不会因单个请求阻塞线程。异步 API 与同步 API 风格一致,仅需配合 async/await 语法。
1. 基础异步 GET/POST 请求
rust
use reqwest::Client;
use serde::{Serialize, Deserialize};
use tokio;
// 异步运行时入口(必须添加#[tokio::main]宏)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建异步客户端(与同步客户端API一致,无阻塞)
let client = Client::new();
// 1. 异步GET请求
let ip_info = client.get("https://ipapi.co/json/")
.query(&[("format", "json")])
.send()
.await? // 等待请求完成(异步阻塞,不占用线程)
.json::<IpInfo>()
.await?; // 等待JSON解析完成
println!("IP信息:{:?}", ip_info);
// 2. 异步POST JSON请求
let login_req = LoginRequest {
username: "rust_dev".to_string(),
password: "123456".to_string(),
remember_me: true,
};
let login_res = client.post("https://api.example.com/login")
.json(&login_req)
.send()
.await?
.json::<LoginResponse>()
.await?;
println!("登录结果:{:?}", login_res);
Ok(())
}
// 复用前面定义的结构体
#[derive(Debug, Deserialize)]
struct IpInfo {
ip: String,
city: String,
region: String,
country: String,
}
#[derive(Debug, Serialize)]
struct LoginRequest {
username: String,
password: String,
remember_me: bool,
}
#[derive(Debug, Deserialize)]
struct LoginResponse {
code: u16,
msg: String,
token: Option<String>,
}
关键说明:异步请求的每个阻塞操作(send()、json())都需加 await 等待完成;#[tokio::main] 宏用于启动 Tokio 异步运行时,是异步函数的入口。
2. 并发批量请求(异步优势场景)
异步模式的核心优势的是高并发,通过 tokio::join! 或 futures::future::join_all 可同时发起多个请求,提升效率。
rust
use reqwest::Client;
use serde::Deserialize;
use tokio;
use futures::future::join_all; // 需在Cargo.toml添加futures = "0.3"
#[derive(Debug, Deserialize)]
struct IpInfo {
ip: String,
country: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let urls = vec![
"https://ipapi.co/8.8.8.8/json/", // 谷歌DNS
"https://ipapi.co/1.1.1.1/json/", // .cloudflare DNS
"https://ipapi.co/223.5.5.5/json/", // 阿里DNS
];
// 构建多个异步请求Future(未执行)
let futures: Vec<_> = urls.into_iter()
.map(|url| async move {
client.get(url)
.send()
.await?
.json::<IpInfo>()
.await
})
.collect();
// 并发执行所有请求,等待全部完成
let results = join_all(futures).await;
// 处理结果
for (idx, result) in results.into_iter().enumerate() {
match result {
Ok(info) => println!("IP {} 信息:{:?}", idx+1, info),
Err(e) => eprintln!("IP {} 请求失败:{}", idx+1, e),
}
}
Ok(())
}
四、高级特性:文件上传、Cookie 与超时控制
reqwest 提供了丰富的高级特性,覆盖文件上传、Cookie 持久化、超时设置、代理配置等实战场景,下面逐一演示。
1. 文件上传(multipart/form-data)
开启 multipart 特性后,可实现单文件/多文件上传,适用于图片、文档上传场景:
rust
use reqwest::{Client, multipart};
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// 1. 构建单文件上传表单
let file = tokio::fs::read("test.png").await?; // 读取文件内容
let part = multipart::Part::bytes(file)
.file_name("test.png") // 文件名
.mime_str("image/png")?; // 文件MIME类型
// 构建multipart表单
let form = multipart::Form::new()
.part("file", part) // 文件字段名
.text("username", "rust_dev") // 额外文本字段
// 2. 发送文件上传请求
let response = client.post("https://api.example.com/upload")
.multipart(form) // 自动设置Content-Type为multipart/form-data
.send()
.await?;
println!("上传结果:{}", response.text().await?);
Ok(())
}
2. Cookie 持久化
开启 cookies特性后,通过 CookieStore 可实现 Cookie 持久化,适用于需要登录状态保持的场景:
rust
use reqwest::{Client, cookie::Jar, Url};
use serde::Deserialize;
use tokio;
#[derive(Debug, Deserialize)]
struct UserInfo {
username: String,
balance: u64,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建Cookie容器(持久化Cookie)
let cookie_jar = Jar::default();
let base_url = Url::parse("https://api.example.com/")?;
cookie_jar.add_cookie_str("session_id=xxx", &base_url); // 手动添加Cookie
// 创建带Cookie的客户端
let client = Client::builder()
.cookie_provider(std::sync::Arc::new(cookie_jar)) // 关联Cookie容器
.build()?;
// 1. 登录(Cookie会自动存入容器)
client.post("https://api.example.com/login")
.json(&serde_json::json!({
"username": "rust_dev",
"password": "123456"
}))
.send()
.await?;
// 2. 访问需要登录状态的接口(自动携带Cookie)
let user_info = client.get("https://api.example.com/user/info")
.send()
.await?
.json::<UserInfo>()
.await?;
println!("用户信息:{:?}", user_info);
Ok(())
}
3. 超时控制(避免永久阻塞)
通过 ClientBuilder 可设置连接超时、请求超时、读取超时,避免因网络异常导致请求永久阻塞:
rust
use reqwest::Client;
use std::time::Duration;
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 构建带超时配置的客户端
let client = Client::builder()
.connect_timeout(Duration::from_secs(5)) // 连接超时:5秒
.timeout(Duration::from_secs(10)) // 总请求超时:10秒
.build()?;
let response = client.get("https://api.example.com/slow-endpoint")
.send()
.await;
match response {
Ok(res) => println!("请求成功:{}", res.status()),
Err(e) => {
if e.is_timeout() {
eprintln!("请求超时");
} else {
eprintln!("请求失败:{}", e);
}
}
}
Ok(())
}
4. 代理配置(突破网络限制)
通过 ClientBuilder::proxy 可设置 HTTP/HTTPS/SOCKS 代理,适用于跨网络访问场景:
rust
use reqwest::{Client, Proxy};
use tokio;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 配置HTTP代理
let proxy = Proxy::http("http://127.0.0.1:7890")?;
// 配置SOCKS5代理(需开启socks特性)
// let proxy = Proxy::socks5("127.0.0.1:7890")?;
let client = Client::builder()
.proxy(proxy) // 关联代理
.build()?;
let ip_info = client.get("https://ipapi.co/json/")
.send()
.await?
.json::<serde_json::Value>()
.await?;
println!("代理IP信息:{}", serde_json::to_string_pretty(&ip_info)?);
Ok(())
}
五、拓展场景:错误处理与实战联动
1. 精细化错误处理
reqwest 的错误类型 reqwest::Error 提供了丰富的分类方法,可针对性处理不同错误场景:
rust
use reqwest::Client;
use tokio;
#[tokio::main]
async fn main() {
let client = Client::new();
let response = client.get("https://api.example.com/invalid-endpoint")
.send()
.await;
match response {
Ok(res) => {
if res.status().is_success() {
// 处理成功响应
let data = res.json::<serde_json::Value>().await;
println!("数据:{:?}", data);
} else {
// 处理HTTP错误状态码(4xx/5xx)
eprintln!("HTTP错误:{}", res.status());
let err_body = res.text().await.unwrap_or_default();
eprintln!("错误详情:{}", err_body);
}
}
Err(e) => {
// 分类处理网络错误
if e.is_timeout() {
eprintln!("错误类型:请求超时");
} else if e.is_connect() {
eprintln!("错误类型:连接失败");
} else if e.is_body() {
eprintln!("错误类型:响应体解析失败");
} else {
eprintln!("错误类型:其他错误,{}", e);
}
}
}
}
2. 与 Serde、Chrono 联动(实战场景)
reqwest 常与 Serde(JSON 解析)、Chrono(时间处理)联动,构建完整业务流程,示例如下:
rust
use reqwest::Client;
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};
use tokio;
#[derive(Debug, Serialize)]
struct EventRequest {
name: String,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
participants: Vec<String>,
}
#[derive(Debug, Deserialize)]
struct EventResponse {
id: String,
name: String,
start_time: DateTime<Utc>,
end_time: DateTime<Utc>,
create_time: DateTime<Utc>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// 构建带时间字段的请求体(自动序列化为ISO 8601格式)
let event_req = EventRequest {
name: "Rust技术分享会".to_string(),
start_time: Utc::now() + chrono::Duration::hours(2),
end_time: Utc::now() + chrono::Duration::hours(4),
participants: vec!["user1".to_string(), "user2".to_string()],
};
// 创建事件并解析响应
let event_res = client.post("https://api.example.com/events")
.json(&event_req)
.send()
.await?
.json::<EventResponse>()
.await?;
println!("创建事件成功:{:?}", event_res);
Ok(())
}
六、最佳实践与常见问题
1. 最佳实践
-
复用 Client 实例:Client 内部维护连接池,复用可减少连接建立开销,避免频繁创建销毁。
-
优先使用异步模式:高并发场景必用异步,低并发脚本可选用同步,平衡开发效率与性能。
-
强制设置超时:所有生产环境请求必须配置超时,避免网络异常导致服务阻塞。
-
合理处理错误:区分 HTTP 状态码错误与网络错误,针对性给出提示,提升可维护性。
-
避免裸用 unwrap() :生产环境用
?传播错误或match处理,防止 panic。
2. 常见问题
-
HTTPS 证书问题 :默认情况下,reqwest 会验证 TLS 证书,本地测试可通过
danger_accept_invalid_certs(true)跳过验证(生产环境禁用)。 -
异步请求不执行 :忘记加
await或#[tokio::main]宏,导致 Future 未执行。 -
JSON 解析失败 :响应格式与结构体字段不匹配(如字段名、类型不一致),可先打印
response.text().await?排查格式。 -
连接池耗尽 :高并发场景下连接池不够用,可通过
ClientBuilder::pool_max_idle_per_host调整最大空闲连接数。
七、总结
reqwest 作为 Rust 生态最成熟的 HTTP 客户端库,以其简洁的 API、完善的功能、优异的性能,覆盖了从简单脚本到高并发服务的全场景需求。无论是同步请求的快速开发,还是异步请求的高并发处理,reqwest 都能通过灵活的配置满足业务需求。
使用 reqwest 的核心是"复用客户端、按需配置、精细化错误处理"------基础场景通过简单 API 快速实现,复杂场景借助 ClientBuilder 配置超时、代理、Cookie 等特性,高并发场景依托异步模式提升效率。掌握本文的实践内容后,你可以在 Rust 项目中轻松应对各类 HTTP 交互需求,构建可靠、高效的网络通信链路。