HTTP клиент на Rust: делаем запросы правильно
Rust — это современный системный язык программирования, который сочетает в себе производительность C++ и безопасность памяти. При разработке веб-приложений, микросервисов или инструментов командной строки часто возникает необходимость взаимодействовать с внешними API. В этой статье мы разберём, как создать HTTP-клиент на Rust, используя популярные крейты reqwest и ureq. Вы узнаете, как отправлять GET и POST запросы, обрабатывать ошибки и работать с JSON.
Почему Rust для HTTP-запросов?
Rust предлагает уникальные преимущества для работы с сетью:
- Безопасность памяти — компилятор гарантирует отсутствие утечек и гонок данных.
- Высокая производительность — скорость сравнима с C/C++.
- Асинхронность — встроенная поддержка async/await для эффективной работы с I/O.
- Экосистема крейтов — тысячи готовых библиотек, включая HTTP-клиенты.
Для работы с HTTP в Rust есть два основных подхода: синхронный (блокирующий) и асинхронный (неблокирующий). Мы рассмотрим оба.
Настройка проекта и выбор крейта
Создадим новый проект Rust:
cargo new rust-http-client
cd rust-http-client
Откройте Cargo.toml и добавьте зависимости:
[dependencies]
# Для асинхронных запросов
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
# Для синхронных запросов (альтернатива)
ureq = "2.9"
Сравнение крейтов:
- reqwest — самый популярный HTTP-клиент. Поддерживает асинхронность, JSON, cookies, прокси.
- ureq — лёгкий синхронный клиент. Идеален для простых скриптов и CLI-инструментов.
1. GET-запрос с reqwest (асинхронный)
Начнём с асинхронного GET-запроса. Создадим файл src/main.rs:
use reqwest;
use std::error::Error;
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Отправляем GET-запрос
let response = reqwest::get("https://api.github.com/repos/rust-lang/rust")
.await?
.text()
.await?;
println!("Ответ от GitHub API:");
println!("{}", &response[..500]); // Выводим первые 500 символов
Ok(())
}
Объяснение:
#[tokio::main]— макрос для запуска асинхронного рантайма Tokio.reqwest::get()— отправляет GET-запрос и возвращаетResponse..await?— ожидает завершения операции и обрабатывает ошибки..text()— извлекает тело ответа как строку.
Запустите проект:
cargo run
Вы увидите JSON-ответ от GitHub API.
2. POST-запрос с JSON (reqwest)
Теперь отправим POST-запрос с телом в формате JSON:
use reqwest;
use serde::{Deserialize, Serialize};
use std::error::Error;
#[derive(Serialize, Deserialize, Debug)]
struct Post {
title: String,
body: String,
user_id: u32,
}
#[derive(Deserialize, Debug)]
struct CreatedPost {
id: u32,
title: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Создаём клиент с настройками
let client = reqwest::Client::new();
// Данные для отправки
let new_post = Post {
title: "HTTP клиент на Rust".to_string(),
body: "Полное руководство по работе с HTTP в Rust".to_string(),
user_id: 1,
};
// Отправляем POST-запрос
let response = client
.post("https://jsonplaceholder.typicode.com/posts")
.json(&new_post)
.send()
.await?;
// Проверяем статус
if response.status().is_success() {
let created_post: CreatedPost = response.json().await?;
println!("Пост создан: {:?}", created_post);
} else {
eprintln!("Ошибка: {}", response.status());
}
Ok(())
}
Ключевые моменты:
reqwest::Client::new()— создаёт пул соединений для повторного использования..json(&data)— автоматически сериализует структуру в JSON..send()— выполняет запрос.response.json::<T>()— десериализует ответ в структуру Rust.
Не забудьте добавить serde в Cargo.toml:
serde = { version = "1", features = ["derive"] }
3. Синхронный HTTP-клиент с ureq
Для простых сценариев, где асинхронность не нужна, используйте ureq. Пример GET-запроса:
use ureq;
fn main() -> Result<(), ureq::Error> {
let response = ureq::get("https://api.github.com/repos/rust-lang/rust")
.set("User-Agent", "rust-http-client")
.call()?;
let body = response.into_string()?;
println!("Ответ: {}", &body[..300]);
Ok(())
}
POST-запрос с JSON:
use ureq;
fn main() -> Result<(), ureq::Error> {
let response = ureq::post("https://jsonplaceholder.typicode.com/posts")
.set("Content-Type", "application/json")
.send_json(ureq::json!({
"title": "HTTP клиент на Rust",
"body": "Синхронный подход с ureq",
"userId": 1
}))?;
let body: serde_json::Value = response.into_json()?;
println!("Созданный пост: {}", body);
Ok(())
}
Преимущества ureq:
- Минимальные зависимости (нет