HTTP client com reqwest
1. Introdução ao reqwest
O reqwest é a biblioteca HTTP client mais popular do ecossistema Rust, oferecendo uma API ergonômica, assíncrona e type-safe para realizar requisições HTTP. Construída sobre o hyper e tokio, ela fornece suporte nativo a TLS (via rustls ou native-tls), cookies, redirecionamentos e compressão.
Principais características:
- Totalmente assíncrono (baseado em async/await)
- Tipos seguros para headers, status codes e corpos
- Suporte integrado a JSON, formulários e multipart
- Pool de conexões reutilizável
Instalação: Adicione ao Cargo.toml:
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
2. Requisições Básicas (GET e POST)
GET simples
use reqwest::Error;
#[tokio::main]
async fn main() -> Result<(), Error> {
let response = reqwest::get("https://api.github.com/repos/rust-lang/rust")
.await?;
println!("Status: {}", response.status());
println!("Headers: {:?}", response.headers());
let body = response.text().await?;
println!("Body: {}", body);
Ok(())
}
POST com JSON
use serde::Serialize;
#[derive(Serialize)]
struct NovoUsuario {
nome: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let usuario = NovoUsuario {
nome: "João".into(),
email: "joao@exemplo.com".into(),
};
let client = reqwest::Client::new();
let response = client
.post("https://api.exemplo.com/usuarios")
.json(&usuario)
.send()
.await?;
println!("Criado: {}", response.status().is_success());
Ok(())
}
Formulários e Multipart
use reqwest::multipart;
// Formulário URL-encoded
let form = [("campo1", "valor1"), ("campo2", "valor2")];
let response = client
.post("https://httpbin.org/post")
.form(&form)
.send()
.await?;
// Multipart para upload de arquivos
let form = multipart::Form::new()
.text("nome", "foto")
.file("arquivo", "caminho/para/imagem.jpg")?;
let response = client
.post("https://api.exemplo.com/upload")
.multipart(form)
.send()
.await?;
3. Configuração Avançada do Cliente
Para performance e controle fino, crie um Client reutilizável:
use std::time::Duration;
use reqwest::header::{HeaderMap, HeaderValue, USER_AGENT};
fn criar_cliente() -> reqwest::Client {
let mut headers = HeaderMap::new();
headers.insert(USER_AGENT, HeaderValue::from_static("meu-app/1.0"));
headers.insert("X-Custom-Header", HeaderValue::from_static("valor"));
reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.connect_timeout(Duration::from_secs(10))
.default_headers(headers)
.pool_max_idle_per_host(10) // pool de conexões
.tcp_keepalive(Duration::from_secs(60))
.build()
.expect("Falha ao criar cliente HTTP")
}
4. Serialização e Desserialização com Serde
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
struct Repo {
name: String,
description: Option<String>,
stargazers_count: u32,
}
#[derive(Serialize)]
struct SearchQuery {
q: String,
per_page: u8,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = reqwest::Client::new();
// Desserialização automática
let repos: Vec<Repo> = client
.get("https://api.github.com/search/repositories")
.query(&SearchQuery { q: "rust".into(), per_page: 5 })
.send()
.await?
.json()
.await?;
for repo in &repos {
println!("{} - ⭐ {}", repo.name, repo.stargazers_count);
}
Ok(())
}
5. Autenticação e Headers Customizados
Bearer Token
use reqwest::header::{AUTHORIZATION, HeaderValue};
async fn api_autenticada(token: &str) -> Result<(), reqwest::Error> {
let client = reqwest::Client::new();
let response = client
.get("https://api.github.com/user")
.header(AUTHORIZATION, format!("Bearer {}", token))
.send()
.await?;
println!("Autenticado: {}", response.status().is_success());
Ok(())
}
Autenticação Básica
async fn basica() -> Result<(), reqwest::Error> {
let client = reqwest::Client::new();
let response = client
.get("https://api.exemplo.com/protegido")
.basic_auth("usuario", Some("senha"))
.send()
.await?;
Ok(())
}
6. Upload e Download de Arquivos
Download com Streaming
use tokio::io::AsyncWriteExt;
use tokio::fs::File;
async fn download_arquivo(url: &str, caminho: &str) -> Result<(), reqwest::Error> {
let response = reqwest::get(url).await?;
let mut file = File::create(caminho).await.unwrap();
let mut stream = response.bytes_stream();
use futures_util::StreamExt;
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
file.write_all(&chunk).await.unwrap();
}
println!("Download concluído!");
Ok(())
}
Upload com Monitoramento
use reqwest::multipart::Form;
use indicatif::{ProgressBar, ProgressStyle};
async fn upload_com_progresso() -> Result<(), reqwest::Error> {
let pb = ProgressBar::new(1024 * 1024); // 1MB
pb.set_style(ProgressStyle::default_bar()
.template("{msg} {bar:40} {bytes}/{total_bytes}")
.unwrap());
let form = Form::new()
.part("arquivo", reqwest::Body::from(
tokio::fs::read("dados.bin").await.unwrap()
));
let client = reqwest::Client::new();
let response = client
.post("https://api.exemplo.com/upload")
.multipart(form)
.send()
.await?;
pb.finish_with_message("Upload concluído!");
println!("Resposta: {}", response.status());
Ok(())
}
7. Tratamento de Erros e Retry
Categorias de Erro
async fn tratar_erros() -> Result<(), reqwest::Error> {
let result = reqwest::get("https://api.exemplo.com").await;
match result {
Ok(response) => {
if response.status().is_success() {
println!("Sucesso!");
} else if response.status().is_server_error() {
eprintln!("Erro 5xx: {}", response.status());
} else if response.status().is_client_error() {
eprintln!("Erro 4xx: {}", response.status());
}
}
Err(e) => {
if e.is_timeout() {
eprintln!("Timeout na requisição");
} else if e.is_connect() {
eprintln!("Falha de conexão");
} else {
eprintln!("Erro desconhecido: {}", e);
}
}
}
Ok(())
}
Retry com Backoff Exponencial
use backoff::ExponentialBackoff;
use backoff::future::retry;
async fn requisicao_com_retry() -> Result<(), reqwest::Error> {
let client = reqwest::Client::new();
let op = || async {
let response = client
.get("https://api.exemplo.com")
.send()
.await
.map_err(|e| backoff::Error::Transient {
err: e,
retry_after: None,
})?;
if response.status().is_server_error() {
return Err(backoff::Error::Transient {
err: reqwest::Error::from(response.error_for_status().unwrap_err()),
retry_after: None,
});
}
Ok(response)
};
let backoff = ExponentialBackoff::default();
let _response = retry(backoff, op).await.map_err(|e| match e {
backoff::Error::Permanent(e) | backoff::Error::Transient { err: e, .. } => e,
})?;
Ok(())
}
8. Boas Práticas e Performance
Cliente Global Reutilizável
use once_cell::sync::OnceCell;
use reqwest::Client;
static HTTP_CLIENT: OnceCell<Client> = OnceCell::new();
fn get_client() -> &'static Client {
HTTP_CLIENT.get_or_init(|| {
Client::builder()
.gzip(true)
.brotli(true)
.redirect(reqwest::redirect::Policy::limited(10))
.build()
.expect("Falha ao criar cliente HTTP")
})
}
// Em handlers Axum/Actix
async fn handler() -> impl axum::response::IntoResponse {
let response = get_client()
.get("https://api.exemplo.com")
.send()
.await;
// ...
}
Testes com Mock Servers
use wiremock::{MockServer, Mock, ResponseTemplate};
use wiremock::matchers::{method, path};
#[tokio::test]
async fn test_api_externa() {
let mock_server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/usuarios"))
.respond_with(ResponseTemplate::new(200)
.set_body_json(serde_json::json!([{"id": 1, "nome": "Teste"}])))
.mount(&mock_server)
.await;
let client = reqwest::Client::new();
let response = client
.get(format!("{}/usuarios", &mock_server.uri()))
.send()
.await
.unwrap();
assert_eq!(response.status(), 200);
let usuarios: Vec<serde_json::Value> = response.json().await.unwrap();
assert_eq!(usuarios.len(), 1);
}
Resumo
O reqwest oferece uma experiência completa para consumo de APIs HTTP em Rust, combinando segurança de tipos com performance assíncrona. Desde requisições simples até streaming de arquivos e retry automático, a biblioteca se adapta desde prototipação rápida até sistemas de produção.
Pontos-chave:
- Sempre reutilize o Client para melhor performance
- Utilize serde para serialização/desserialização automática
- Configure timeouts e pool de conexões para evitar vazamentos
- Implemente retry com backoff para resiliência
- Teste com wiremock para simular APIs externas
Referências
- Documentação oficial do reqwest — API completa com exemplos de todos os recursos da biblioteca
- Tutorial reqwest no Learn Rust — Receitas práticas para clientes HTTP no Rust Cookbook
- Guia de reqwest com async/await — Artigo detalhado do LogRocket sobre requisições assíncronas
- Documentação do Serde — Referência oficial para serialização/deserialização com derive macros
- wiremock: Mock Servers para Testes — Biblioteca para simular servidores HTTP em testes
- HTTP Client Performance em Rust — Comparativo de performance entre reqwest, ureq e outros clients
- Tokio e reqwest: Async HTTP — Guia oficial do Tokio sobre programação assíncrona com reqwest