HTTP клиент на Rust: делаем запросы правильно от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: зачем писать HTTP клиент на Rust?

Современная разработка редко обходится без взаимодействия с внешними сервисами. Будь то REST API погоды, база данных или микросервисная архитектура — умение отправлять HTTP-запросы является базовым навыком. Rust, с его акцентом на производительность и безопасность, предлагает несколько элегантных решений для работы с HTTP. В этой статье мы разберем, как правильно создавать HTTP клиенты на Rust: от выбора библиотеки до обработки сложных сценариев.

Мы рассмотрим две самые популярные библиотеки: reqwest (асинхронный стандарт) и ureq (простой синхронный подход). Вы узнаете, как отправлять GET и POST запросы, обрабатывать JSON, управлять заголовками и избегать типичных ошибок.

1. Выбор библиотеки: reqwest vs ureq

Прежде чем писать код, нужно определиться с инструментом. В экосистеме Rust есть два основных кандидата.

1.1. Reqwest — мощный и асинхронный

reqwest — это де-факто стандарт для HTTP-запросов в Rust. Он построен на базе hyper и tokio, поддерживает асинхронность, автоматическую сериализацию/десериализацию JSON и работу с cookies. Идеален для высоконагруженных приложений и веб-серверов.

Для начала работы добавьте в Cargo.toml:

[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

1.2. Ureq — простота без асинхронности

Если вам не нужны асинхронные вызовы и вы пишете простой скрипт или CLI-утилиту, ureq станет отличным выбором. Он синхронный, минималистичный и не требует сложной настройки рантайма.

[dependencies]
ureq = "2.9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

Когда что выбирать? Для веб-серверов и сложных приложений — reqwest. Для простых задач, тестов и скриптов — ureq.

2. Базовые запросы: GET и POST

Рассмотрим практические примеры для обеих библиотек.

2.1. GET запрос с reqwest

Самый простой пример — получение данных с публичного API. Мы используем асинхронную функцию main с макросом #[tokio::main].

use reqwest;
use std::error::Error;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let url = "https://jsonplaceholder.typicode.com/posts/1";
    let response = reqwest::get(url).await?;

    // Проверяем статус ответа
    if response.status().is_success() {
        let body = response.text().await?;
        println!("Ответ: {}", body);
    } else {
        eprintln!("Ошибка HTTP: {}", response.status());
    }

    Ok(())
}

2.2. POST запрос с JSON телом (reqwest)

Отправка данных часто требует сериализации структуры. Для этого используем крейт serde.

use reqwest;
use serde::{Deserialize, Serialize};
use std::error::Error;

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let client = reqwest::Client::new();
    let new_post = Post {
        title: "Hello Rust".to_string(),
        body: "Это тело запроса".to_string(),
        user_id: 1,
    };

    let response = client
        .post("https://jsonplaceholder.typicode.com/posts")
        .json(&new_post)
        .send()
        .await?;

    let created_post: Post = response.json().await?;
    println!("Созданный пост: {:?}", created_post);

    Ok(())
}

2.3. Простой GET с ureq

С ureq код становится короче, так как не требует асинхронности:

use ureq;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let response = ureq::get("https://api.github.com/repos/rust-lang/rust")
        .call()?;

    let body: String = response.into_string()?;
    println!("Ответ: {}", body);

    Ok(())
}

3. Продвинутые техники: заголовки, таймауты и обработка ошибок

В реальных проектах простых запросов недостаточно. Рассмотрим, как настроить HTTP клиент правильно.

3.1. Настройка заголовков и User-Agent

Многие API требуют указания User-Agent или авторизационного токена. В reqwest это делается через билдер клиента:

use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, USER_AGENT};

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let mut headers = HeaderMap::new();
    headers.insert(USER_AGENT, HeaderValue::from_static("MyRustApp/1.0"));
    headers.insert(
        AUTHORIZATION,
        HeaderValue::from_static("Bearer your_token_here"),
    );

    let client = reqwest::Client::builder()
        .default_headers(headers)
        .timeout(std::time::Duration::from_secs(10))
        .build()?;

    let response = client
        .get("https://api.example.com/data")
        .send()
        .await?;

    println!("Статус: {}", response.status());
    Ok(())
}

3.2. Обработка ошибок и повторные попытки

Сеть нестабильна, поэтому важно корректно обрабатывать ошибки. Используйте паттерн retry с помощью крейта backoff или простого цикла:

use reqwest;
use std::time::Duration;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::new();
    let mut attempts = 0;
    let max_retries = 3;

    loop {
        attempts += 1;
        let result = client
            .get("https://httpbin.org/status/500") // Симулируем ошибку                

Блоги

Рекомендаций по книгам