Rust 同步方式访问 REST API 的完整指南
在 Rust 中不使用异步机制访问 REST API 是完全可行的,特别适合简单应用、脚本或不需要高并发的场景。以下是完整的同步实现方案:
📦 依赖选择
推荐库:
[dependencies]
reqwest = { version = "0.11", features = ["blocking", "json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
🔧 基础 GET 请求
1. 简单文本响应
use reqwest::blocking::get;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://jsonplaceholder.typicode.com/posts/1";
let response = get(url)?;
if response.status().is_success() {
let body = response.text()?;
println!("Response body: {}", body);
} else {
println!("Request failed with status: {}", response.status());
}
Ok(())
}
2. 解析 JSON 响应
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Post {
userId: u32,
id: u32,
title: String,
body: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = "https://jsonplaceholder.typicode.com/posts/1";
let response = get(url)?;
if response.status().is_success() {
let post: Post = response.json()?;
println!("Post title: {}", post.title);
println!("Full post: {:#?}", post);
} else {
println!("Request failed with status: {}", response.status());
}
Ok(())
}
📤 POST 请求示例
1. 发送 JSON 数据
use reqwest::blocking::Client;
use serde::Serialize;
#[derive(Debug, Serialize)]
struct NewPost {
title: String,
body: String,
userId: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let new_post = NewPost {
title: "My New Post".to_string(),
body: "This is the body of my new post".to_string(),
userId: 1,
};
let response = client
.post("https://jsonplaceholder.typicode.com/posts")
.json(&new_post)
.send()?;
println!("Status: {}", response.status());
println!("Response: {}", response.text()?);
Ok(())
}
2. 发送表单数据
use reqwest::blocking::Client;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let params = [("username", "john_doe"), ("password", "secret123")];
let response = client
.post("https://example.com/login")
.form(¶ms)
.send()?;
println!("Login response: {}", response.text()?);
Ok(())
}
🔐 高级功能
1. 添加请求头
use reqwest::blocking::Client;
use reqwest::header;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let mut headers = header::HeaderMap::new();
headers.insert(
header::AUTHORIZATION,
"Bearer token123".parse()?
);
headers.insert(
header::USER_AGENT,
"MySyncRustClient/1.0".parse()?
);
let response = client
.get("https://api.example.com/protected")
.headers(headers)
.send()?;
println!("Protected resource: {}", response.text()?);
Ok(())
}
2. 超时设置
use reqwest::blocking::Client;
use std::time::Duration;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::builder()
.timeout(Duration::from_secs(5))
.build()?;
let response = client
.get("https://api.example.com/slow-endpoint")
.send()?;
println!("Response: {}", response.text()?);
Ok(())
}
3. 处理分页
use reqwest::blocking::Client;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Page {
items: Vec<Item>,
next_page: Option<u32>,
}
#[derive(Debug, Deserialize)]
struct Item {
id: u32,
name: String,
}
fn fetch_all_items() -> Result<Vec<Item>, Box<dyn std::error::Error>> {
let client = Client::new();
let mut all_items = Vec::new();
let mut page_num = 1;
loop {
let url = format!("https://api.example.com/items?page={}", page_num);
let response = client.get(&url).send()?;
if !response.status().is_success() {
return Err(format!("Request failed: {}", response.status()).into());
}
let page: Page = response.json()?;
all_items.extend(page.items);
match page.next_page {
Some(next) => page_num = next,
None => break,
}
}
Ok(all_items)
}
🛡️ 错误处理最佳实践
1. 自定义错误类型
use thiserror::Error;
#[derive(Error, Debug)]
enum ApiError {
#[error("HTTP request failed: {0}")]
HttpError(#[from] reqwest::Error),
#[error("API returned error: {0}")]
ApiError(String),
#[error("Invalid response format")]
ParseError,
}
fn fetch_data() -> Result<String, ApiError> {
let response = reqwest::blocking::get("https://api.example.com/data")?;
if response.status().is_success() {
response.text().map_err(|_| ApiError::ParseError)
} else {
let status = response.status();
let body = response.text().unwrap_or_default();
Err(ApiError::ApiError(format!("{}: {}", status, body)))
}
}
2. 重试机制
use reqwest::blocking::Client;
use std::thread;
use std::time::Duration;
fn fetch_with_retry(url: &str, max_retries: u32) -> Result<String, Box<dyn std::error::Error>> {
let client = Client::new();
let mut retries = 0;
loop {
match client.get(url).send() {
Ok(response) if response.status().is_success() => {
return response.text().map_err(|e| e.into());
}
Ok(response) => {
eprintln!("Request failed: {}", response.status());
}
Err(e) => {
eprintln!("Request error: {}", e);
}
}
retries += 1;
if retries >= max_retries {
return Err("Max retries exceeded".into());
}
// 指数退避
let delay = 2u64.pow(retries);
eprintln!("Retrying in {} seconds...", delay);
thread::sleep(Duration::from_secs(delay));
}
}
📊 性能优化
1. 复用 HTTP 客户端
use reqwest::blocking::Client;
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建一次,多次复用
let client = Client::new();
let response1 = client.get("https://api.example.com/resource1").send()?;
// 处理响应1...
let response2 = client.get("https://api.example.com/resource2").send()?;
// 处理响应2...
Ok(())
}
2. 多线程处理(有限并发)
use reqwest::blocking::Client;
use std::thread;
fn fetch_urls(urls: &[&str]) -> Vec<String> {
let client = Client::new();
let handles: Vec<_> = urls.iter().map(|url| {
let url = (*url).to_string();
thread::spawn(move || {
match client.get(&url).send() {
Ok(resp) => resp.text().unwrap_or_else(|_| "Error reading response".into()),
Err(_) => "Request failed".into(),
}
})
}).collect();
handles.into_iter().map(|h| h.join().unwrap()).collect()
}
fn main() {
let urls = [
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
];
let results = fetch_urls(&urls);
for (i, result) in results.iter().enumerate() {
println!("Response {}: {}", i + 1, result);
}
}
📝 完整示例:天气查询工具
use reqwest::blocking::Client;
use serde::Deserialize;
use std::env;
#[derive(Debug, Deserialize)]
struct WeatherData {
name: String,
main: Main,
weather: Vec<Weather>,
}
#[derive(Debug, Deserialize)]
struct Main {
temp: f32,
feels_like: f32,
humidity: u32,
}
#[derive(Debug, Deserialize)]
struct Weather {
description: String,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("OPENWEATHER_API_KEY")
.expect("请设置 OPENWEATHER_API_KEY 环境变量");
let city = env::args().nth(1)
.unwrap_or_else(|| "London".to_string());
let url = format!(
"https://api.openweathermap.org/data/2.5/weather?q={}&appid={}&units=metric",
city, api_key
);
let client = Client::new();
let response = client.get(&url).send()?;
if !response.status().is_success() {
return Err(format!("API请求失败: {}", response.status()).into());
}
let weather: WeatherData = response.json()?;
println!("\n{} 的天气:", weather.name);
println!("温度: {:.1}°C", weather.main.temp);
println!("体感温度: {:.1}°C", weather.main.feels_like);
println!("湿度: {}%", weather.main.humidity);
println!("天气状况: {}", weather.weather[0].description);
Ok(())
}
⚠️ 同步方法的局限性
- 阻塞主线程:每个请求都会阻塞当前线程
- 低并发能力:不适合高并发场景
- 资源利用低效:线程在等待响应时无法处理其他任务
- 扩展性差:大规模请求需要大量线程
💡 何时使用同步方法
- 命令行工具:简单的数据获取工具
- 脚本任务:一次性数据处理脚本
- 低流量服务:内部工具或低流量API
- 学习阶段:理解HTTP请求的基础
- 简单嵌入式系统:资源受限环境
📚 总结
同步 REST API 访问核心步骤:
- 使用
reqwest::blocking
模块 - 创建
Client
实例(可复用) - 构建请求(GET/POST/PUT/DELETE)
- 发送请求并获取响应
- 检查状态码
- 解析响应体(文本/JSON/二进制)
最佳实践:
- 复用 Client:减少连接开销
- 设置超时:防止无限等待
- 添加重试:处理临时故障
- 优雅错误处理:使用自定义错误类型
- 环境变量管理:安全存储API密钥
对于需要高并发或高性能的场景,建议使用异步方法(如 reqwest
的异步API + tokio
运行时)。但对于许多应用场景,同步方法提供了简单直接的解决方案。