探索 Rust 中的 Reqwest

更新于 2026-01-18

Chetan Reddy Kodidela 2025-04-14

Reqwest 是 Rust 语言中一个功能强大的 HTTP 客户端,支持异步(asynchronous)和阻塞(blocking)两种调用方式。根据你的应用程序需求——无论是发起单个 HTTP 请求,还是处理多个并发请求——你都可以在这两种模式之间进行选择。

在本文中,我们将涵盖以下内容:

  • Reqwest 简介
  • 同步(阻塞)API 的使用
  • 结合 Tokio 和 Serde 的异步 API 使用
  • 发起 GET 和 POST 请求
  • 处理 JSON 的序列化与反序列化
  • Cargo.toml 中的依赖项与配置

1. Reqwest 简介

Reqwest 基于 hyper 构建,提供了用于处理 HTTP 请求和响应的高级 API。它支持以下特性:

  • 连接池:通过单一客户端复用多个请求的连接。
  • 自动重定向处理
  • 通过与 Serde 集成实现 JSON 的(反)序列化
  • 提供阻塞和异步两种风格,以适应不同的使用场景。

2. 同步(阻塞)API 的使用

阻塞 API 非常适合简单的脚本,或者当你只需要执行一次 API 调用、且不希望引入异步编程复杂性时。你可以通过启用 "blocking" 特性来使用它。

示例:使用阻塞客户端发起基本的 GET 请求

use reqwest::blocking::{Client, ClientBuilder};
use reqwest::redirect::Policy;

fn main() {
    // 创建一个基本的 HTTP 客户端实例。
    let http_client = Client::new();

    // 发起一个简单的 GET 请求。
    let http_result = http_client
        .get("https://jsonplaceholder.typicode.com/posts/1")
        .send();

    // 检查请求是否成功,然后打印响应体。
    if let Ok(response) = http_result {
        // 在生产代码中,建议对 .text() 的潜在错误进行处理。
        match response.text() {
            Ok(text) => println!("Body: {:#?}", text),
            Err(e) => eprintln!("Failed to read response text: {:?}", e),
        }
    } else if let Err(e) = http_result {
        eprintln!("Error: {:?}", e);
    }

    // 演示使用限制策略处理重定向。
    let redir_policy = Policy::limited(5);
    let http_client = ClientBuilder::new()
        .redirect(redir_policy)
        .build()
        .expect("ClientBuilder failed to build a client");

    // 此调用最多跟随 5 次重定向。
    let http_result2 = http_client
        .get("http://httpbin.org/redirect-to?url=https://example.com")
        .send()
        .expect("Failed to send redirected request");

    println!("Response with redirection: {:#?}", http_result2);
}

关键点:

  • 阻塞调用:代码会等待请求完成后再执行下一行。
  • 错误处理:示例中包含了基本的错误处理,说明如何检查错误。
  • 重定向策略:你可以使用 Policy::limited 自定义重定向行为。

3. 异步 API 的使用

对于多个或长时间运行的 HTTP 调用,异步 API(由 Tokio 提供支持)允许你的应用程序在等待响应的同时执行其他任务。这在服务器或需要并发处理大量 HTTP 请求的应用程序中特别有用。

示例 1:带 JSON 反序列化的异步 GET 请求

简单的 GET 示例

// 将 Tokio 运行时引入作用域。
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let url = "https://www.rust-lang.org";

    // 异步 GET 请求。
    let response = reqwest::get(url).await?;

    // 提取并打印响应体。
    let response_body = response.text().await?;
    println!("Response Body: {:?}", response_body);

    Ok(())
}

将 JSON 响应反序列化为通用值(Generic Value)

use serde_json::Value;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let posts_url = "https://jsonplaceholder.typicode.com/posts";

    // 异步 GET 请求。
    let resp = reqwest::get(posts_url).await?;

    // 将 JSON 响应转换为 serde_json::Value。
    let posts: Value = resp.json().await?;
    dbg!(posts);

    Ok(())
}

示例 2:将 JSON 反序列化为 Rust 结构体

为了获得更强的类型安全性和代码清晰度,通常会定义一个与 JSON 数据对应的结构体。

use serde::Deserialize;
use reqwest;
use tokio;

#[derive(Debug, Deserialize)]
struct Post {
    userId: i32,
    id: i32,
    title: String,
    body: String,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let posts_url = "https://jsonplaceholder.typicode.com/posts";

    // 异步发起 GET 请求。
    let response = reqwest::get(posts_url).await?;

    // 将 JSON 响应反序列化为 Post 结构体的向量。
    let posts: Vec<Post> = response.json().await?;

    // 遍历并打印每篇帖子的 ID 和标题。
    for post in posts {
        println!("ID: {}, Title: {}", post.id, post.title);
    }

    Ok(())
}

示例 3:将数据获取到 HashMap 中

有时响应数据无法很好地映射到预定义的结构体中——此时使用 HashMap 是一种灵活的选择。

use std::collections::HashMap;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let resp = reqwest::get("https://httpbin.org/ip")
        .await?
        .json::<HashMap<String, String>>()
        .await?;

    println!("{:#?}", resp);
    Ok(())
}

关键点:

  • 异步执行#[tokio::main] 属性会创建一个运行时来执行异步代码。
  • 错误处理:使用 ? 操作符传播错误。在生产代码中,可能需要更细粒度的错误处理。
  • 反序列化:使用 Serde 将 JSON 响应转换为 Rust 类型,提高类型安全性和代码可读性。

4. 发起 POST 请求

除了 GET 请求,Reqwest 也轻松支持 POST 请求。以下是一个发送纯文本请求体的示例。

use reqwest::Error;

#[tokio::main]
async fn main() -> Result<(), Error> {
    let client = reqwest::Client::new();

    // 发送带有请求体的 POST 请求。
    let res = client
        .post("http://httpbin.org/post")
        .body("the exact body that is sent")
        .send()
        .await?;

    // 打印响应体。
    let body = res.text().await?;
    println!("Response: {}", body);

    Ok(())
}

POST 请求的关键注意事项:

  • 请求体格式:Reqwest 支持多种请求体格式。根据你的 API,你可能需要发送 JSON、表单数据或其他内容类型。
  • 客户端复用:为了执行多个请求,建议复用 Client 实例,以便利用连接池提升性能。

5. 配置依赖项

要在项目中使用 Reqwest,请在 Cargo.toml 中添加相应的依赖项。以下是异步和阻塞示例的配置。

用于异步代码

[dependencies]
reqwest = { version = "0.10", features = ["json"] }
tokio = { version = "0.2", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }  # 可选,当使用结构体进行 JSON 反序列化时需要

用于阻塞代码

[dependencies]
reqwest = { version = "0.10", features = ["blocking", "json"] }
serde = { version = "1.0", features = ["derive"] }  # 可选,当使用结构体进行 JSON 反序列化时需要

小技巧:你可以通过命令行快速添加 reqwest:

cargo add reqwest

此命令会确保你使用的是 reqwest 0.10.10 版本,并启用适当的特性。


6. 阻塞 vs 异步:何时使用哪种?

阻塞(同步)模式:

适用于简单、单一或不频繁的请求场景,此时应用程序不需要处理多个并发网络操作。这种模式编写起来更简单,如果你的用例很直接,也更容易理解。

异步(非阻塞)模式:

当你的应用程序需要并发执行多个 HTTP 请求时(例如网络爬虫、处理多个客户端请求的服务器),异步编程可以让你最高效地利用系统资源。它避免了在每个请求完成前等待,从而实现更高的吞吐量和响应能力。


结论

Reqwest 是一个用途广泛的 Rust HTTP 客户端,既适用于简单的脚本,也能胜任复杂的异步应用程序。你选择阻塞还是异步操作,取决于具体的使用场景。本文提供的示例展示了如何:

  • 发起 GET 和 POST 请求,
  • 处理重定向,
  • 使用 Serde 将 JSON 响应反序列化为 Rust 类型。

欢迎你根据这些示例和最佳实践,构建能够高效与 HTTP API 通信的健壮应用程序。