Вызов Веб API
Запрос к API GitHub
Пример посылает запрос к GitHub stargazers API v3 используя метод reqwest::get
, чтобы получить список всех пользователей, кто отметил какой-то проект на GitHub звёздочкой. reqwest::Response
десериализуется с помощью Response::json
в объекты типа User
, которые реализуют типаж serde::Deserialize
.
[tokio::main] используется, чтобы установить асинхронный движок и обрабатывать ожидания выполнения для [reqwet::get
] до обработки запросов и преобразования в значения типа User.
use serde::Deserialize; use reqwest::Error; #[derive(Deserialize, Debug)] struct User { login: String, id: u32, } #[tokio::main] async fn main() -> Result<(), Error> { let request_url = format!("https://api.github.com/repos/{owner}/{repo}/stargazers", owner = "rust-lang-nursery", repo = "rust-cookbook"); println!("{}", request_url); let response = reqwest::get(&request_url).await?; let users: Vec<User> = response.json().await?; println!("{:?}", users); Ok(()) }
Проверка существования ресурса в API
Данный рецепт запрашивает точку Users широко известного сервиса GitHub с использованием метода HEAD (Client::head
) и затем смотрит код ответа для определения того, был ли вызов успешен или нет. Такой способ подходит для того, чтобы быстро "ткнуть" в REST ресурс без необходимости загружать тело ответа. reqwest::Client
сконфигурированный с ClientBuilder::timeout
, который гарантирует то, что запрос не будет длиться дольше данного временного интервала.
Из-за возвращения методами ClientBuilder::build
и [ReqwestBuilder::send
] типов ошибки reqwest::Error
, используется псевдоним reqwest::Result
для возвращаемого значения функции main.
use reqwest::Result; use std::time::Duration; use reqwest::ClientBuilder; #[tokio::main] async fn main() -> Result<()> { let user = "ferris-the-crab"; let request_url = format!("https://api.github.com/users/{}", user); println!("{}", request_url); let timeout = Duration::new(5, 0); let client = ClientBuilder::new().timeout(timeout).build()?; let response = client.head(&request_url).send().await?; if response.status().is_success() { println!("{} is a user!", user); } else { println!("{} is not a user!", user); } Ok(()) }
Создать и удалить Gist через GitHub API
Пример создаёт фрагмент кода (в терминах GitHub называется gist) с помощью POST-запроса gists API v3 используя Client::post
и удаляет только что созданный фрагмент с помощью DELETE-запроса используя Client::delete
.
Объект reqwest::Client
отвечает за детали для обоих типов запросов выше, детали включают URL адрес, тело запроса и аутентификацию. Тело POST-запроса как JSON можно подготовить с помощью макроса serde_json::json!
. Вызов RequestBuilder::json
определяет тело запроса. RequestBuilder::basic_auth
управляет аутентификацией. А вызов RequestBuilder::send
синхронно выполняет созданные запросы.
use error_chain::error_chain; use serde::Deserialize; use serde_json::json; use std::env; use reqwest::Client; error_chain! { foreign_links { EnvVar(env::VarError); HttpRequest(reqwest::Error); } } #[derive(Deserialize, Debug)] struct Gist { id: String, html_url: String, } #[tokio::main] async fn main() -> Result<()> { let gh_user = env::var("GH_USER")?; let gh_pass = env::var("GH_PASS")?; let gist_body = json!({ "description": "the description for this gist", "public": true, "files": { "main.rs": { "content": r#"fn main() { println!("hello world!");}"# } }}); let request_url = "https://api.github.com/gists"; let response = Client::new() .post(request_url) .basic_auth(gh_user.clone(), Some(gh_pass.clone())) .json(&gist_body) .send().await?; let gist: Gist = response.json().await?; println!("Created {:?}", gist); let request_url = format!("{}/{}",request_url, gist.id); let response = Client::new() .delete(&request_url) .basic_auth(gh_user, Some(gh_pass)) .send().await?; println!("Gist {} deleted! Status code: {}",gist.id, response.status()); Ok(()) }
Данный пример использует HTTP Basic Auth чтобы получить доступ к GitHub API. Но в промышленном коде типичный пример использования может задействовать гораздо более сложный OAuth процесс авторизации.
Обращение к RESTful API с постраничной обработкой
Пример оборачивает метода веб интерфейса в удобный итератор на Rust. Этот итератор лениво запрашивает следующую страницу результатов от удалённого сервера по достижении конца каждой страницы.
use reqwest::Result; use serde::Deserialize; #[derive(Deserialize)] struct ApiResponse { dependencies: Vec<Dependency>, meta: Meta, } #[derive(Deserialize)] struct Dependency { crate_id: String, } #[derive(Deserialize)] struct Meta { total: u32, } struct ReverseDependencies { crate_id: String, dependencies: <Vec<Dependency> as IntoIterator>::IntoIter, client: reqwest::blocking::Client, page: u32, per_page: u32, total: u32, } impl ReverseDependencies { fn of(crate_id: &str) -> Result<Self> { Ok(ReverseDependencies { crate_id: crate_id.to_owned(), dependencies: vec![].into_iter(), client: reqwest::blocking::Client::new(), page: 0, per_page: 100, total: 0, }) } fn try_next(&mut self) -> Result<Option<Dependency>> { if let Some(dep) = self.dependencies.next() { return Ok(Some(dep)); } if self.page > 0 && self.page * self.per_page >= self.total { return Ok(None); } self.page += 1; let url = format!("https://crates.io/api/v1/crates/{}/reverse_dependencies?page={}&per_page={}", self.crate_id, self.page, self.per_page); let response = self.client.get(&url).send()?.json::<ApiResponse>()?; self.dependencies = response.dependencies.into_iter(); self.total = response.meta.total; Ok(self.dependencies.next()) } } impl Iterator for ReverseDependencies { type Item = Result<Dependency>; fn next(&mut self) -> Option<Self::Item> { match self.try_next() { Ok(Some(dep)) => Some(Ok(dep)), Ok(None) => None, Err(err) => Some(Err(err)), } } } fn main() -> Result<()> { for dep in ReverseDependencies::of("serde")? { println!("reverse dependency: {}", dep?.crate_id); } Ok(()) }