Rust 同步方式访问 REST API 的完整指南

发布于:2025-08-05 ⋅ 阅读:(13) ⋅ 点赞:(0)

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(&params)
        .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(())
}

⚠️ 同步方法的局限性

  1. 阻塞主线程:每个请求都会阻塞当前线程
  2. 低并发能力:不适合高并发场景
  3. 资源利用低效:线程在等待响应时无法处理其他任务
  4. 扩展性差:大规模请求需要大量线程

💡 何时使用同步方法

  1. 命令行工具:简单的数据获取工具
  2. 脚本任务:一次性数据处理脚本
  3. 低流量服务:内部工具或低流量API
  4. 学习阶段:理解HTTP请求的基础
  5. 简单嵌入式系统:资源受限环境

📚 总结

同步 REST API 访问核心步骤:

  1. 使用 reqwest::blocking 模块
  2. 创建 Client 实例(可复用)
  3. 构建请求(GET/POST/PUT/DELETE)
  4. 发送请求并获取响应
  5. 检查状态码
  6. 解析响应体(文本/JSON/二进制)

最佳实践:

  • 复用 Client:减少连接开销
  • 设置超时:防止无限等待
  • 添加重试:处理临时故障
  • 优雅错误处理:使用自定义错误类型
  • 环境变量管理:安全存储API密钥

对于需要高并发或高性能的场景,建议使用异步方法(如 reqwest 的异步API + tokio 运行时)。但对于许多应用场景,同步方法提供了简单直接的解决方案。


网站公告

今日签到

点亮在社区的每一天
去签到