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

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

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

Начать курс

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

Rust — это современный язык системного программирования, который завоевал популярность благодаря своей производительности и безопасности памяти. Однако многие разработчики, привыкшие к Python или JavaScript, задаются вопросом: как в Rust выполнять HTTP-запросы? В этой статье мы разберём создание HTTP-клиента на Rust с использованием популярной библиотеки reqwest. Вы узнаете, как делать GET и POST запросы, обрабатывать JSON, управлять заголовками и работать с асинхронностью. После прочтения вы сможете уверенно интегрировать HTTP-клиент в свои Rust-проекты.

1. Установка и настройка окружения

Для начала создадим новый проект Rust и добавим необходимые зависимости. Откройте терминал и выполните:

cargo new http_client_rust
cd http_client_rust

Теперь отредактируйте файл Cargo.toml, добавив следующие зависимости:

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

Разберём каждую библиотеку:

  • reqwest — основной HTTP-клиент. Флаг json добавляет поддержку сериализации/десериализации JSON.
  • tokio — асинхронный рантайм, необходимый для работы reqwest в асинхронном режиме.
  • serde и serde_json — для работы с JSON-структурами.

После добавления зависимостей выполните cargo build, чтобы скачать и скомпилировать все библиотеки.

2. Базовый GET-запрос

Начнём с простого GET-запроса к публичному API. Создадим структуру для ответа и выполним запрос к сервису JSONPlaceholder:

use reqwest;
use serde::Deserialize;

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

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let url = "https://jsonplaceholder.typicode.com/posts/1";
    let response = reqwest::get(url).await?;
    
    let post: Post = response.json().await?;
    println!("{:#?}", post);
    
    Ok(())
}

Разберём код:

  • Атрибут #[derive(Debug, Deserialize)] автоматически генерирует трейты для вывода и десериализации.
  • #[tokio::main] превращает функцию в асинхронную точку входа.
  • reqwest::get() выполняет GET-запрос, а .await ожидает его завершения.
  • response.json() преобразует JSON-ответ в нашу структуру.

Запустите код командой cargo run. Вы увидите данные поста в консоли.

3. POST-запрос с отправкой данных

Теперь разберём, как отправлять данные на сервер. Для POST-запроса нам потребуется создать структуру для отправляемых данных и использовать метод post():

use reqwest::Client;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize)]
struct NewPost {
    title: String,
    body: String,
    userId: u32,
}

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

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = Client::new();
    
    let new_post = NewPost {
        title: "Мой первый пост на Rust".to_string(),
        body: "Это тело поста, отправленного с помощью reqwest".to_string(),
        userId: 1,
    };
    
    let response = client
        .post("https://jsonplaceholder.typicode.com/posts")
        .json(&new_post)
        .send()
        .await?;
    
    let created_post: CreatedPost = response.json().await?;
    println!("Создан пост с ID: {}", created_post.id);
    println!("{:#?}", created_post);
    
    Ok(())
}

Ключевые моменты:

  • Мы создаём экземпляр Client, который можно переиспользовать для нескольких запросов.
  • Метод .json() автоматически сериализует нашу структуру в JSON и устанавливает заголовок Content-Type: application/json.
  • Метод .send() выполняет запрос, а затем мы десериализуем ответ.

4. Работа с заголовками и обработка ошибок

В реальных проектах часто требуется добавлять кастомные заголовки (например, для авторизации) и корректно обрабатывать ошибки. Рассмотрим пример с GitHub API:

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

#[derive(Debug, Deserialize)]
struct GitHubUser {
    login: String,
    id: u32,
    name: Option<String>,
    public_repos: u32,
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new();
    
    let mut headers = HeaderMap::new();
    headers.insert(USER_AGENT, HeaderValue::from_static("my-rust-app"));
    headers.insert(
        AUTHORIZATION,
        HeaderValue::from_str("Bearer YOUR_GITHUB_TOKEN")?
    );
    
    let response = client
        .get("https://api.github.com/users/rust-lang")
        .headers(headers)
        .send()
        .await?;
    
    // Проверка статуса ответа
    if response.status().is_success() {
        let user: GitHubUser = response.json().await?;
        println!("Пользователь: {}", user.login);
        println!("Публичных репозиториев: {}", user.public_repos);
    } else {
        let status = response.status();
        let error_text = response.text().await?;
        eprintln!("Ошибка {}: {}", status, error_text);
    }
    
    Ok(())
}

Важные аспекты:

  • HeaderMap позволяет собирать заголовки в коллекцию.
  • Всегда устанавливайте заголовок USER_AGENT при работе с GitHub API — это требование их политики.
  • Проверка response.status().is_success() — правильный способ обработки HTTP-статусов.
  • Тип возврата Result<(), Box

Блоги

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