В Rust HTTP-запросы и парсинг результата выполнять легко — нужны лишь подходящие библиотеки. reqwest
и serde
могут стать идеальным решением.
Репозиторий на GitHub
Весь код приложения доступен в репозитории GitHub.
Вы научитесь:
- выполнять запросы GET и POST;
- отображать HTTP-ответ на предопределенную структуру;
- обрабатывать различные коды состояния HTTP.
Зависимости
Чтобы установить зависимости для следующей сборки, добавим библиотеки reqwest
, tokio
, serde
и serde_json
в файл Cargo.toml
:
[package]
name = "example_make_http_request"
version = "0.1.0"
edition = "2021"
[dependencies]
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1", features = ["full"] } # для асинхронной среды выполнения
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Общая настройка и импортирование
Прежде чем переходить к коду бизнес-логики, рассмотрим код вокруг нее.
В следующем фрагменте импортируются:
- структура
HashMap
дляJSONResponse
; - 2 типажа из
serde
для преобразования HTTP-ответов в структуры*Response
; CONTENT_TYPE
из крейтаreqwest
для установки заголовка запроса content-type (типа содержимого).
А в методе main
инициализируется новый клиент reqwest
— один для всех последующих запросов:
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use reqwest::header::CONTENT_TYPE;
#[derive(Serialize, Deserialize, Debug)]
struct GETAPIResponse {
origin: String,
}
#[derive(Serialize, Deserialize, Debug)]
struct JSONResponse {
json: HashMap<String, String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Создается новый клиент, используемый во всех запросах
let client = reqwest::Client::new();
/// Бизнес-логика помещается сюда
/// ...
/// ...
Ok(())
}
GET-запрос
В следующем фрагменте кода, используя в этом client
метод .get(url)
, создаем GET-запрос для отправки по .send()
. Поскольку возвращаться должен JSON, задаем тип содержимого application/json
.
По завершении запроса (.await?
) ответ в формате JSON десериализуем в структуру GETAPIResponse
, используя метод .json::<GETAPIResponse>()
:
//...
// Выполняется GET-запрос,
// а также парсинг ответа в структуру GETAPIResponse
let resp200 = client.get("https://httpbin.org/ip")
.header(CONTENT_TYPE, "application/json")
.send()
.await?
.json::<GETAPIResponse>()
.await?;
println!("{:#?}", resp200);
// Вывод:
/*
GETAPIResponse {
origin: "182.190.14.159",
}
*/
//...
Примечание. Возвращаемый JSON не обязан точно соответствовать структуре GETAPIResponse
. Обязательно лишь поле origin
, представленное строкой. Другие поля не важны.
POST-запрос
Создается так же, но с двумя отличиями.
.post(url)
вместо.get(url)
.- В теле запроса передается дополнительная полезная нагрузка.
В следующем примере создается изменяемая HashMap
и добавляются 2 пары «ключ — значение». В Rust HashMap
сериализуется методом .json(&T)
, в случае успеха сериализованные данные добавляются в тело запроса.
Здесь парсинг ответа выполняется в структуру JSONResponse
с единственным полем json
. В этом поле содержится HashMap<String, String>
. Поскольку тело запроса возвращается из конечной точки https://httpbin.org/anything
в поле json
тела ответа, оно идеально десериализуется в структуру JSONResponse
:
// Создается карта со строковыми парами «ключ — значение»
// — полезной нагрузкой тела запроса
let mut map = HashMap::new();
map.insert("lang", "rust");
map.insert("body", "json");
// Выполняется POST-запрос,
// а также парсинг ответа в структуру JSONResponse
let resp_json = client.post("https://httpbin.org/anything")
.json(&map)
.send()
.await?
.json::<JSONResponse>()
.await?;
println!("{:#?}", resp_json);
// Вывод:
/*
JSONResponse {
json: {
"body": "json",
"lang": "rust",
},
}
*/
Обработка различных кодов состояния HTTP
Не все запросы возвращаются с 200 OK
и десериализуются в структуры.
В следующем примере делается GET-запрос к https://httpbin.org/status/404. Последней частью (status/404
) URL-адреса указывается, что ответы сервера всегда должны быть с кодом 404
, чтобы проверять сопоставитель.
Логика всех кодов состояния реализуется с помощью match resp404.status()
, для всего остального есть ветвь по умолчанию с символом _
. В примере ниже выполняется сопоставление с ветвью reqwest::StatusCode::NOT_FOUND
:
// Делается GET-запрос
let resp404 = client.get("https://httpbin.org/status/404")
.send()
.await?;
// Сопоставляется код состояния HTTP запроса
match resp404.status() {
// "OK - 200" — все прошло хорошо
reqwest::StatusCode::OK => {
println!("Success!");
// ...
},
// "NOT_FOUND - 404" — ресурс не найден
reqwest::StatusCode::NOT_FOUND => {
println!("Got 404! Haven't found resource!");
// Вывод:
/*
Ошибка 404! Ресурс не найден!
*/
},
// Любой другой код состояния, не совпадающий с приведенными выше
_ => {
panic!("Okay... this shouldn't happen...");
},
};
Запуск приложения
Загляните в репозиторий GitHub.
А теперь компилируем и запускаем приложение на cargo run
. Вы увидите такой вывод, пути и origin будут другими:
Читайте также:
- 5 функций CLI на Rust для оптимизации привычных инструментов
- Создание CLI в Rust для исполнения гитарных аккордов
- Как создавать надежные SSL-сертификаты для локальной разработки
Читайте нас в Telegram, VK и Дзен
Перевод статьи Pascal Zwikirsch: Rust: Making HTTP Requests And Handling Responses by Using reqwest