Reqwest 库详细使用指南

概述

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(&params)
    .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(&params)
    .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?;
复制代码
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)

  • ❌ 嵌入式设备资源受限 (考虑更轻量的库)

相关推荐
橘子真甜~7 小时前
C/C++ Linux网络编程11 - 数据加密与https协议
linux·服务器·网络·http·https·密码学·加密解密
mzhan0179 小时前
Rust 资料
开发语言·后端·rust
Hello.Reader11 小时前
Rocket 0.5 快速上手从克隆仓库到跑起第一个示例
rust·rocket
HelloReader11 小时前
一款“既好玩又靠谱”的 Rust Web 框架
后端·rust
HelloReader11 小时前
从 Rocket 0.4 升级到 0.5一份实战迁移指南
后端·rust
jinxinyuuuus12 小时前
抖音在线去水印:HTTP/2流量分析、反爬虫的对称与非对称加密
爬虫·网络协议·http
Source.Liu12 小时前
【学写LibreCAD】Win11下在MSYS2 UCRT64环境中搭建Qt+Rust混合开发环境(VSCode)完整笔记
c++·qt·rust
Fr2ed0m12 小时前
HTTP vs HTTPS vs SSL/TLS:https协议全面解析(附HTTPS部署指南)
http·https·ssl
从负无穷开始的三次元代码生活12 小时前
《图解HTTP》——浓缩讲解,快速了解HTTP
网络·网络协议·http