Введение: Почему Rust — отличный выбор для HTTP-запросов
Rust — это язык системного программирования, который завоевал популярность благодаря своей безопасности, производительности и отсутствию сборщика мусора. Если вы разрабатываете сетевое приложение, веб-скрапер или микросервис, умение правильно выполнять HTTP-запросы на Rust — обязательный навык. В этой статье мы разберём, как создать полноценный HTTP клиент на Rust с использованием библиотеки reqwest, которая является стандартом де-факто для работы с HTTP в экосистеме Rust.
Мы пройдём путь от установки зависимостей до обработки ошибок и работы с JSON. К концу статьи вы сможете писать надёжные и быстрые HTTP-клиенты на Rust, которые не подведут в production.
Установка и настройка проекта
Первый шаг — создание нового проекта и добавление зависимости reqwest с поддержкой асинхронности и JSON.
cargo new rust_http_client
cd rust_http_client
Откройте Cargo.toml и добавьте:
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
Объясним каждую зависимость:
- reqwest — основной HTTP клиент. Флаг
jsonвключает поддержку сериализации/десериализации. - tokio — асинхронный рантайм Rust. HTTP-запросы в
reqwestпо умолчанию асинхронные. - serde / serde_json — для работы с JSON (структуры, сериализация).
Теперь всё готово для написания кода.
Базовый GET-запрос: получаем данные с сервера
Начнём с простого GET-запроса. Создадим функцию, которая получает данные с публичного API и выводит их в консоль.
use reqwest::Error;
#[tokio::main]
async fn main() -> Result<(), Error> {
let url = "https://jsonplaceholder.typicode.com/posts/1";
let response = reqwest::get(url).await?;
println!("Статус: {}", response.status());
let body = response.text().await?;
println!("Тело ответа:\n{}", body);
Ok(())
}
Что здесь происходит:
- Мы используем макрос
#[tokio::main]для запуска асинхронной функции. reqwest::get(url).await?выполняет GET-запрос. Знак?пробрасывает ошибку, если запрос не удался.response.status()возвращает HTTP-статус (например, 200 OK).response.text().await?извлекает тело ответа как строку.
Запустите код командой cargo run. Вы увидите JSON-объект с данными поста.
Добавляем заголовки и параметры запроса
В реальных приложениях часто нужно передавать заголовки (например, API-ключ) или query-параметры. Вот как это сделать:
use reqwest::Client;
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
let mut params = HashMap::new();
params.insert("userId", "1");
let response = client
.get("https://jsonplaceholder.typicode.com/posts")
.header("Accept", "application/json")
.query(¶ms)
.send()
.await?;
let posts: serde_json::Value = response.json().await?;
println!("Количество постов: {}", posts.as_array().unwrap().len());
Ok(())
}
Ключевые моменты:
- Мы создаём
Client— это переиспользуемый HTTP-клиент с пулом соединений. Всегда используйте Client вместо прямых вызововreqwest::getдля production-кода. - Метод
.header()добавляет заголовок. .query()принимает словарь параметров и автоматически кодирует URL..json()сразу десериализует ответ в нужный тип (здесь —serde_json::Value).
POST-запрос: отправляем данные на сервер
POST-запросы используются для создания ресурсов. Рассмотрим отправку JSON-данных.
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug)]
struct Post {
title: String,
body: String,
userId: u32,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = reqwest::Client::new();
let new_post = Post {
title: "Rust HTTP клиент".to_string(),
body: "Изучаем отправку POST-запросов".to_string(),
userId: 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(())
}
Обратите внимание:
- Мы определяем структуру
Postс атрибутами#[derive(Serialize, Deserialize)]. Это позволяет serde автоматически конвертировать её в JSON и обратно. - Метод
.json()автоматически сериализует структуру и устанавливает заголовокContent-Type: application/json. - Ответ от сервера мы снова десериализуем в ту же структуру.
Отправка формы (application/x-www-form-urlencoded)
Иногда нужно отправить данные в формате формы. Для этого используйте .form():
let mut form_data = std::collections::HashMap::new();
form_data.insert("username", "rust_user");
form_data.insert("password", "secret123");
let response = client
.post("https://example.com/login")
.form(&form_data)
.send()
.await?;
Обработка ошибок и статус-кодов
Надёжный HTTP-клиент должен корректно обрабатывать ошибки сети, таймауты и некорректные статус-коды.
use reqwest::StatusCode;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>&