Avatar ·

Как выполнить HTTP-запрос на Rust? Нет встроенной функции?

Привет! Я только начал изучать Rust после Python и JavaScript, и столкнулся с проблемой. В Python я привык писать `requests.get(url)`, и всё работает. В Rust я ожидал найти что-то подобное в стандартной библиотеке, но, видимо, её там нет. Я попытался написать простой GET-запрос к публичному API, используя только стандартную библиотеку, но ничего не вышло. Вот мой код, который, очевидно, не компилируется: ```rust use std::net::TcpStream; use std::io::Read; fn main() { let mut response = String::new(); let mut stream = TcpStream::connect("http://api.example.com/data").unwrap(); stream.read_to_string(&mut response).unwrap(); println!("{}", response); } ``` Ошибка говорит что-то про протокол и отсутствие поддержки HTTP. Я понимаю, что Rust — системный язык, но неужели нет простого способа сделать запрос без подключения тонны зависимостей? Мне нужно получить JSON с сервера и распарсить его. Я слышал про крейты `reqwest` и `ureq`, но не уверен, какой выбрать. Также меня интересует асинхронность — в современном Rust это модно, но я пока не разобрался с `async`/`await`. Подскажите, как правильно сделать HTTP-запрос с учётом современных практик? Желательно с примером кода, который можно скопировать и запустить. <<>>

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

Вы правы — в стандартной библиотеке Rust нет встроенного HTTP-клиента. Это осознанное решение разработчиков языка: стандартная библиотека должна быть минимальной и стабильной, а HTTP-клиенты быстро развиваются. Лучший способ — использовать крейт reqwest. Он поддерживает и синхронные, и асинхронные запросы, автоматически обрабатывает перенаправления, сжатие и TLS.

Установка

Добавьте в Cargo.toml:

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

Простой синхронный GET-запрос

Если вам не нужна асинхронность, используйте блокирующий клиент:

use reqwest::blocking::Client;

fn main() -> Result<(), reqwest::Error> {
    let client = Client::new();
    let response = client
        .get("https://api.github.com/repos/rust-lang/rust")
        .header("User-Agent", "my-rust-app")
        .send()?;
    
    println!("Статус: {}", response.status());
    println!("Заголовки: {:?}", response.headers());
    
    let body = response.text()?;
    println!("Тело ответа: {}", body);
    Ok(())
}

Асинхронный GET-запрос (рекомендуемый способ)

Современный Rust активно использует асинхронность. Вот пример с async/await:

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

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let client = Client::new();
    let response = client
        .get("https://jsonplaceholder.typicode.com/posts/1")
        .send()
        .await?;
    
    let json: serde_json::Value = response.json().await?;
    println!("ID: {}", json["id"]);
    println!("Заголовок: {}", json["title"]);
    Ok(())
}

POST-запрос с JSON

use reqwest::Client;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = Client::new();
    let payload = json!({
        "title": "foo",
        "body": "bar",
        "userId": 1
    });
    
    let response = client
        .post("https://jsonplaceholder.typicode.com/posts")
        .json(&payload)
        .send()
        .await?;
    
    println!("Создан: {:?}", response.text().await?);
    Ok(())
}

Обработка ошибок

Всегда проверяйте статус ответа:

let response = client.get(url).send().await?;
if response.status().is_success() {
    let data = response.text().await?;
    // обрабатываем данные
} else {
    eprintln!("Ошибка: {}", response.status());
}

Почему не TcpStream напрямую?

  • HTTP — сложный протокол: требует парсинга заголовков, обработки chunked encoding, поддержки HTTPS (TLS).
  • reqwest делает это за вас, включая автоматическое управление keep-alive соединениями.
  • Безопасность: reqwest использует rustls или native-tls для HTTPS.

Альтернативы

  • ureq — минималистичный синхронный клиент без зависимостей от Tokio. Хорош для простых скриптов.
  • hyper — низкоуровневая библиотека, на которой построен reqwest. Используйте, если нужен полный контроль.

Начните с reqwest — это стандарт де-факто для HTTP-запросов в Rust. Он активно поддерживается и имеет отличную документацию.

<<>>

Альтернативный подход: синхронный клиент ureq

Если вы не хотите связываться с асинхронностью и Tokio, отличный выбор — крейт ureq. Он полностью синхронный, не требует настройки рантайма и идеально подходит для простых скриптов, утилит или когда асинхронность избыточна.

Установка

[dependencies]
ureq = { version = "2.10", features = ["json"] }

Пример GET-запроса

use ureq;

fn main() -> Result<(), ureq::Error> {
    let response = ureq::get("https://api.github.com/repos/rust-lang/rust")
        .set("User-Agent", "my-app")
        .call()?;
    
    let body: String = response.into_string()?;
    println!("{}", body);
    Ok(())
}

POST с JSON

use ureq::json;

fn main() -> Result<(), ureq::Error> {
    let payload = json!({
        "title": "Пример",
        "body": "Тело запроса"
    });
    
    let response = ureq::post("https://httpbin.org/post")
        .send_json(payload)?;
    
    println!("Ответ: {}", response.into_string()?);
    Ok(())
}

Когда использовать ureq вместо reqwest?

  • Вам нужен простой скрипт без асинхронности.
  • Вы не хотите подключать Tokio (экономия времени компиляции).
  • Вы пишете библиотеку, которая не должна диктовать пользователю выбор рантайма.

ureq поддерживает HTTPS, куки, перенаправления и JSON. Для большинства задач его функциональности достаточно. Если позже понадобится асинхронность, легко перейти на reqwest — API очень похожи.