Rust: выполнение HTTP-запросов и обработка ответов с помощью reqwest
![](/file/f6ab26d677f510d40bc25.jpg)
В 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 будут другими:
![](https://cdn-images-1.medium.com/max/1000/0*-YCcF1FFHvAyXKFR.png)