Introdução
O Reqwest é o cliente HTTP mais popular e ergonômico do ecossistema Rust. Construído sobre o Hyper, ele oferece uma API de alto nível que simplifica drasticamente a realização de requisições HTTP, mantendo toda a flexibilidade necessária para cenários complexos.
Se você já trabalhou com bibliotecas como requests em Python ou axios em JavaScript, vai se sentir em casa com o Reqwest. Ele abstrai a complexidade do protocolo HTTP e oferece funcionalidades prontas para uso como gerenciamento automático de cookies, suporte a TLS, compressão, redirecionamentos e muito mais.
O Reqwest suporta dois modos de operação: assíncrono (padrão, baseado no Tokio) e blocking (síncrono), permitindo que você escolha a abordagem mais adequada para o seu projeto. A grande maioria dos projetos modernos em Rust utiliza o modo assíncrono, mas o modo blocking é útil para scripts simples e prototipagem rápida.
Reqwest Rust em 2026: onde ele encaixa
Quem pesquisa Reqwest Rust geralmente quer resolver um problema prático: consumir uma API externa, enviar um webhook, autenticar com bearer token, integrar um serviço SaaS, baixar um arquivo grande, chamar um microserviço interno ou escrever um worker que fala HTTP sem perder segurança de tipos. Reqwest é a resposta padrão quando você quer ergonomia de cliente HTTP em Rust sem cair diretamente na camada de protocolo.
Na pilha moderna, Reqwest fica acima de Hyper e roda muito bem com Tokio. Hyper entrega a base HTTP; Tokio executa I/O assíncrono; Reqwest oferece a API confortável para JSON, headers, TLS, cookies, streaming e multipart. Em aplicações web com Axum, serviços resilientes com Tower ou bancos via SQLx, Reqwest costuma aparecer no lado de integrações: pagamentos, antifraude, autenticação, webhooks, APIs internas, scraping controlado, automações e chamadas entre serviços.
O erro comum é tratar cliente HTTP como detalhe simples demais. Em produção, um Client precisa ser reutilizado, ter timeouts explícitos, classificar status HTTP, registrar falhas com Tracing, controlar retries e não vazar dados sensíveis em logs. Essa disciplina aparece em guias práticos como Rust para serviços resilientes, deploy Rust em VPS e release engineering em Rust.
Para carreira, Reqwest conecta fundamentos de async Rust com trabalho real de backend, plataforma, dados, fintech, integrações B2B e developer tools. Muitas vagas Rust não pedem “Reqwest” no título, mas descrevem APIs, workers, integrações, observabilidade, retries, webhooks e sistemas distribuídos. Combine esta referência com o projeto de cliente HTTP em Rust, a receita de requisição HTTP em Rust e a trilha de backend Rust para transformar conhecimento de biblioteca em portfólio demonstrável.
Por que usar o Reqwest?
- API ergonômica: métodos encadeáveis e intuitivos
- Async por padrão: integração nativa com Tokio
- TLS embutido: suporte a HTTPS sem configuração adicional
- Gerenciamento de cookies: cookie jar automático
- Compressão: suporte a gzip, brotli e deflate
- Proxy: suporte a proxies HTTP e SOCKS5
- Multipart: upload de arquivos com facilidade
- Connection pooling: reutilização automática de conexões
Instalação
Adicione o Reqwest ao seu Cargo.toml:
[dependencies]
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
As features mais comuns do Reqwest incluem:
[dependencies]
reqwest = { version = "0.12", features = [
"json", # Serialização/deserialização JSON
"cookies", # Gerenciamento automático de cookies
"multipart", # Upload de arquivos multipart
"stream", # Streaming de respostas
"gzip", # Compressão gzip
"brotli", # Compressão brotli
"rustls-tls", # TLS via rustls (alternativa ao OpenSSL)
] }
Para o modo blocking (síncrono), adicione a feature correspondente:
[dependencies]
reqwest = { version = "0.12", features = ["json", "blocking"] }
Uso Básico
Requisição GET simples
O uso mais básico do Reqwest é fazer uma requisição GET e obter o corpo da resposta como texto:
use reqwest;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
// Requisição GET simples
let corpo = reqwest::get("https://httpbin.org/get")
.await?
.text()
.await?;
println!("Resposta: {}", corpo);
Ok(())
}
Requisição GET com deserialização JSON
Na maioria dos casos, você vai querer deserializar a resposta JSON em uma struct:
use reqwest;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct ApiResposta {
origin: String,
url: String,
headers: std::collections::HashMap<String, String>,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let resposta: ApiResposta = reqwest::get("https://httpbin.org/get")
.await?
.json()
.await?;
println!("Origem: {}", resposta.origin);
println!("URL: {}", resposta.url);
Ok(())
}
Usando o Client reutilizável
Para múltiplas requisições, crie um Client que reutiliza conexões (connection pooling):
use reqwest::Client;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Usuario {
id: u32,
name: String,
email: String,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
// Crie o client uma vez e reutilize
let client = Client::new();
// Primeira requisição
let usuarios: Vec<Usuario> = client
.get("https://jsonplaceholder.typicode.com/users")
.send()
.await?
.json()
.await?;
println!("Total de usuários: {}", usuarios.len());
// Segunda requisição (reutiliza a conexão)
let usuario: Usuario = client
.get("https://jsonplaceholder.typicode.com/users/1")
.send()
.await?
.json()
.await?;
println!("Usuário: {} - {}", usuario.name, usuario.email);
Ok(())
}
Requisição POST com JSON
Enviar dados JSON em uma requisição POST:
use reqwest::Client;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize)]
struct NovaTarefa {
title: String,
body: String,
#[serde(rename = "userId")]
user_id: u32,
}
#[derive(Debug, Deserialize)]
struct TarefaCriada {
id: u32,
title: String,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
let nova_tarefa = NovaTarefa {
title: "Aprender Reqwest".to_string(),
body: "Estudar o cliente HTTP do Rust".to_string(),
user_id: 1,
};
let resposta: TarefaCriada = client
.post("https://jsonplaceholder.typicode.com/posts")
.json(&nova_tarefa)
.send()
.await?
.json()
.await?;
println!("Tarefa criada com ID: {}", resposta.id);
Ok(())
}
Requisições PUT e DELETE
use reqwest::Client;
use serde::Serialize;
#[derive(Serialize)]
struct AtualizarTarefa {
title: String,
body: String,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
// PUT - Atualizar recurso
let resposta = client
.put("https://jsonplaceholder.typicode.com/posts/1")
.json(&AtualizarTarefa {
title: "Título atualizado".to_string(),
body: "Corpo atualizado".to_string(),
})
.send()
.await?;
println!("PUT status: {}", resposta.status());
// DELETE - Remover recurso
let resposta = client
.delete("https://jsonplaceholder.typicode.com/posts/1")
.send()
.await?;
println!("DELETE status: {}", resposta.status());
Ok(())
}
Cliente Blocking (Síncrono)
Para scripts simples ou quando você não precisa de async:
use reqwest::blocking::Client;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Post {
id: u32,
title: String,
}
fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
// GET síncrono
let posts: Vec<Post> = client
.get("https://jsonplaceholder.typicode.com/posts")
.send()?
.json()?;
for post in posts.iter().take(5) {
println!("[{}] {}", post.id, post.title);
}
Ok(())
}
Recursos Avançados
Headers personalizados
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
use reqwest::Client;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
// Headers padrão para todas as requisições do client
let mut headers_padrao = HeaderMap::new();
headers_padrao.insert(USER_AGENT, HeaderValue::from_static("minha-app/1.0"));
headers_padrao.insert(
AUTHORIZATION,
HeaderValue::from_static("Bearer meu-token-jwt"),
);
let client = Client::builder()
.default_headers(headers_padrao)
.build()?;
// Headers adicionais por requisição
let resposta = client
.get("https://httpbin.org/headers")
.header(CONTENT_TYPE, "application/json")
.header("X-Custom-Header", "valor-personalizado")
.send()
.await?;
println!("Status: {}", resposta.status());
println!("Corpo: {}", resposta.text().await?);
Ok(())
}
Cookies
use reqwest::{cookie::Jar, Client, Url};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
// Cookie jar para gerenciar cookies manualmente
let jar = Arc::new(Jar::default());
// Adicionar cookie manualmente
let url = "https://httpbin.org".parse::<Url>().unwrap();
jar.add_cookie_str("sessao=abc123; Domain=httpbin.org", &url);
let client = Client::builder()
.cookie_store(true)
.cookie_provider(jar.clone())
.build()?;
let resposta = client
.get("https://httpbin.org/cookies")
.send()
.await?
.text()
.await?;
println!("Cookies: {}", resposta);
Ok(())
}
Timeouts e configuração do Client
use reqwest::Client;
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::builder()
// Timeout total da requisição
.timeout(Duration::from_secs(30))
// Timeout para estabelecer conexão
.connect_timeout(Duration::from_secs(5))
// Número máximo de redirecionamentos
.redirect(reqwest::redirect::Policy::limited(5))
// Habilitar compressão gzip
.gzip(true)
// Configurar proxy
// .proxy(reqwest::Proxy::all("http://proxy:8080")?)
// Usar rustls ao invés de OpenSSL nativo
// .use_rustls_tls()
.build()?;
let resposta = client
.get("https://httpbin.org/delay/2")
.send()
.await?;
println!("Status: {}", resposta.status());
Ok(())
}
Multipart uploads (envio de arquivos)
use reqwest::{multipart, Client};
use tokio::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// Ler arquivo do disco
let conteudo_arquivo = fs::read("foto.jpg").await?;
// Criar formulário multipart
let form = multipart::Form::new()
.text("descricao", "Minha foto de perfil")
.text("usuario", "joao_silva")
.part(
"arquivo",
multipart::Part::bytes(conteudo_arquivo)
.file_name("foto.jpg")
.mime_str("image/jpeg")?,
);
let resposta = client
.post("https://httpbin.org/post")
.multipart(form)
.send()
.await?;
println!("Status: {}", resposta.status());
println!("Resposta: {}", resposta.text().await?);
Ok(())
}
Streaming de respostas (download de arquivos grandes)
use reqwest::Client;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
let mut resposta = client
.get("https://example.com/arquivo-grande.zip")
.send()
.await?;
// Verificar status antes de processar
if !resposta.status().is_success() {
eprintln!("Erro: status {}", resposta.status());
return Ok(());
}
// Tamanho total (se disponível)
let tamanho_total = resposta.content_length();
println!(
"Tamanho total: {} bytes",
tamanho_total
.map(|t| t.to_string())
.unwrap_or_else(|| "desconhecido".to_string())
);
let mut arquivo = File::create("download.zip").await?;
let mut bytes_baixados: u64 = 0;
// Fazer download em chunks
while let Some(chunk) = resposta.chunk().await? {
arquivo.write_all(&chunk).await?;
bytes_baixados += chunk.len() as u64;
if let Some(total) = tamanho_total {
let progresso = (bytes_baixados as f64 / total as f64) * 100.0;
print!("\rProgresso: {:.1}%", progresso);
}
}
println!("\nDownload concluído!");
Ok(())
}
Query parameters
use reqwest::Client;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct ResultadoBusca {
args: std::collections::HashMap<String, String>,
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::new();
// Parâmetros via método query()
let resultado: ResultadoBusca = client
.get("https://httpbin.org/get")
.query(&[
("busca", "rust lang"),
("pagina", "1"),
("limite", "10"),
])
.send()
.await?
.json()
.await?;
println!("Parâmetros enviados: {:?}", resultado.args);
// Parâmetros via struct com Serialize
#[derive(serde::Serialize)]
struct Parametros {
busca: String,
pagina: u32,
limite: u32,
}
let params = Parametros {
busca: "rust reqwest".to_string(),
pagina: 1,
limite: 20,
};
let resultado: ResultadoBusca = client
.get("https://httpbin.org/get")
.query(¶ms)
.send()
.await?
.json()
.await?;
println!("Parâmetros: {:?}", resultado.args);
Ok(())
}
Tratamento robusto de erros
use reqwest::{Client, StatusCode};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct ApiErro {
mensagem: String,
codigo: u32,
}
#[derive(Debug)]
enum AppErro {
Rede(reqwest::Error),
Api(StatusCode, String),
Deserializacao(String),
}
impl From<reqwest::Error> for AppErro {
fn from(err: reqwest::Error) -> Self {
AppErro::Rede(err)
}
}
async fn buscar_dados(client: &Client, url: &str) -> Result<String, AppErro> {
let resposta = client.get(url).send().await?;
match resposta.status() {
StatusCode::OK => {
let texto = resposta.text().await?;
Ok(texto)
}
StatusCode::NOT_FOUND => {
Err(AppErro::Api(StatusCode::NOT_FOUND, "Recurso não encontrado".to_string()))
}
StatusCode::UNAUTHORIZED => {
Err(AppErro::Api(StatusCode::UNAUTHORIZED, "Não autorizado".to_string()))
}
StatusCode::TOO_MANY_REQUESTS => {
Err(AppErro::Api(
StatusCode::TOO_MANY_REQUESTS,
"Rate limit excedido, tente novamente mais tarde".to_string(),
))
}
status => {
let corpo = resposta.text().await.unwrap_or_default();
Err(AppErro::Api(status, corpo))
}
}
}
#[tokio::main]
async fn main() {
let client = Client::new();
match buscar_dados(&client, "https://httpbin.org/status/404").await {
Ok(dados) => println!("Sucesso: {}", dados),
Err(AppErro::Rede(e)) => eprintln!("Erro de rede: {}", e),
Err(AppErro::Api(status, msg)) => eprintln!("Erro da API [{}]: {}", status, msg),
Err(AppErro::Deserializacao(msg)) => eprintln!("Erro de parse: {}", msg),
}
}
Retry com backoff exponencial
use reqwest::Client;
use std::time::Duration;
use tokio::time::sleep;
async fn requisicao_com_retry(
client: &Client,
url: &str,
tentativas_max: u32,
) -> Result<String, reqwest::Error> {
let mut tentativa = 0;
loop {
tentativa += 1;
println!("Tentativa {} de {}...", tentativa, tentativas_max);
match client.get(url).send().await {
Ok(resposta) if resposta.status().is_success() => {
return resposta.text().await;
}
Ok(resposta) if resposta.status().is_server_error() && tentativa < tentativas_max => {
let espera = Duration::from_millis(100 * 2u64.pow(tentativa - 1));
eprintln!(
"Erro do servidor ({}), tentando novamente em {:?}...",
resposta.status(),
espera
);
sleep(espera).await;
}
Ok(resposta) => {
return resposta.text().await;
}
Err(e) if tentativa < tentativas_max => {
let espera = Duration::from_millis(100 * 2u64.pow(tentativa - 1));
eprintln!("Erro de rede: {}, tentando novamente em {:?}...", e, espera);
sleep(espera).await;
}
Err(e) => return Err(e),
}
}
}
#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
let client = Client::builder()
.timeout(Duration::from_secs(10))
.build()?;
let resultado = requisicao_com_retry(&client, "https://httpbin.org/get", 3).await?;
println!("Resultado: {}", resultado);
Ok(())
}
Boas Práticas
1. Reutilize o Client
Sempre crie um único Client e reutilize-o para todas as requisições. O Client gerencia um pool de conexões internamente:
use reqwest::Client;
use std::sync::Arc;
// Em uma aplicação web, compartilhe o client via estado
struct AppState {
http_client: Client,
}
// Ou use Arc para compartilhar entre threads
async fn exemplo() {
let client = Arc::new(Client::new());
let client_clone = client.clone();
let handle = tokio::spawn(async move {
client_clone
.get("https://httpbin.org/get")
.send()
.await
});
let _ = handle.await;
}
2. Sempre verifique o status da resposta
use reqwest::Client;
async fn buscar_seguro(client: &Client, url: &str) -> Result<String, String> {
let resposta = client
.get(url)
.send()
.await
.map_err(|e| format!("Erro de rede: {}", e))?;
// error_for_status() converte 4xx/5xx em erro
let resposta = resposta
.error_for_status()
.map_err(|e| format!("Erro HTTP: {}", e))?;
resposta
.text()
.await
.map_err(|e| format!("Erro ao ler corpo: {}", e))
}
3. Configure timeouts apropriados
use reqwest::Client;
use std::time::Duration;
fn criar_client_producao() -> Client {
Client::builder()
.timeout(Duration::from_secs(30))
.connect_timeout(Duration::from_secs(5))
.pool_idle_timeout(Duration::from_secs(90))
.pool_max_idle_per_host(10)
.build()
.expect("Falha ao criar HTTP client")
}
4. Use tipos fortes para respostas da API
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct PaginaDeResultados<T> {
dados: Vec<T>,
total: u64,
pagina: u32,
por_pagina: u32,
}
#[derive(Debug, Deserialize)]
struct Produto {
id: u64,
nome: String,
preco: f64,
#[serde(default)]
disponivel: bool,
}
// Uso type-safe
async fn listar_produtos(
client: &reqwest::Client,
) -> Result<PaginaDeResultados<Produto>, reqwest::Error> {
client
.get("https://api.exemplo.com/produtos")
.query(&[("pagina", "1"), ("limite", "20")])
.send()
.await?
.json()
.await
}
5. Centralize a configuração do client
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, AUTHORIZATION};
use reqwest::Client;
use std::time::Duration;
struct ApiClient {
client: Client,
base_url: String,
}
impl ApiClient {
fn new(base_url: &str, token: &str) -> Result<Self, reqwest::Error> {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
headers.insert(
AUTHORIZATION,
HeaderValue::from_str(&format!("Bearer {}", token))
.expect("Token inválido"),
);
let client = Client::builder()
.default_headers(headers)
.timeout(Duration::from_secs(30))
.build()?;
Ok(Self {
client,
base_url: base_url.to_string(),
})
}
async fn get<T: serde::de::DeserializeOwned>(
&self,
caminho: &str,
) -> Result<T, reqwest::Error> {
let url = format!("{}{}", self.base_url, caminho);
self.client.get(&url).send().await?.json().await
}
async fn post<B: serde::Serialize, T: serde::de::DeserializeOwned>(
&self,
caminho: &str,
corpo: &B,
) -> Result<T, reqwest::Error> {
let url = format!("{}{}", self.base_url, caminho);
self.client.post(&url).json(corpo).send().await?.json().await
}
}
Exemplos Práticos
Consumindo uma API REST completa
Este exemplo mostra como criar um cliente completo para uma API REST de gerenciamento de tarefas:
use reqwest::{Client, StatusCode};
use serde::{Deserialize, Serialize};
use std::time::Duration;
// === Modelos ===
#[derive(Debug, Serialize)]
struct CriarTarefa {
titulo: String,
descricao: String,
prioridade: Prioridade,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum Prioridade {
Baixa,
Media,
Alta,
}
#[derive(Debug, Deserialize)]
struct Tarefa {
id: u64,
titulo: String,
descricao: String,
prioridade: Prioridade,
concluida: bool,
}
#[derive(Debug, Deserialize)]
struct ListaResposta {
tarefas: Vec<Tarefa>,
total: u64,
}
// === Cliente da API ===
struct TarefasApi {
client: Client,
base_url: String,
}
impl TarefasApi {
fn new(base_url: &str, token: &str) -> Result<Self, reqwest::Error> {
let mut headers = reqwest::header::HeaderMap::new();
headers.insert(
reqwest::header::AUTHORIZATION,
reqwest::header::HeaderValue::from_str(&format!("Bearer {}", token))
.expect("Token inválido"),
);
let client = Client::builder()
.default_headers(headers)
.timeout(Duration::from_secs(30))
.connect_timeout(Duration::from_secs(5))
.build()?;
Ok(Self {
client,
base_url: base_url.to_string(),
})
}
async fn listar(&self, pagina: u32) -> Result<ListaResposta, reqwest::Error> {
self.client
.get(format!("{}/tarefas", self.base_url))
.query(&[("pagina", pagina.to_string()), ("limite", "20".to_string())])
.send()
.await?
.error_for_status()?
.json()
.await
}
async fn obter(&self, id: u64) -> Result<Option<Tarefa>, reqwest::Error> {
let resposta = self.client
.get(format!("{}/tarefas/{}", self.base_url, id))
.send()
.await?;
match resposta.status() {
StatusCode::OK => Ok(Some(resposta.json().await?)),
StatusCode::NOT_FOUND => Ok(None),
_ => {
resposta.error_for_status()?;
unreachable!()
}
}
}
async fn criar(&self, tarefa: &CriarTarefa) -> Result<Tarefa, reqwest::Error> {
self.client
.post(format!("{}/tarefas", self.base_url))
.json(tarefa)
.send()
.await?
.error_for_status()?
.json()
.await
}
async fn concluir(&self, id: u64) -> Result<Tarefa, reqwest::Error> {
self.client
.patch(format!("{}/tarefas/{}", self.base_url, id))
.json(&serde_json::json!({"concluida": true}))
.send()
.await?
.error_for_status()?
.json()
.await
}
async fn excluir(&self, id: u64) -> Result<bool, reqwest::Error> {
let resposta = self.client
.delete(format!("{}/tarefas/{}", self.base_url, id))
.send()
.await?;
Ok(resposta.status() == StatusCode::NO_CONTENT)
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = TarefasApi::new("https://api.exemplo.com", "meu-token")?;
// Criar uma nova tarefa
let nova = api.criar(&CriarTarefa {
titulo: "Aprender Reqwest".to_string(),
descricao: "Estudar o cliente HTTP do Rust".to_string(),
prioridade: Prioridade::Alta,
}).await?;
println!("Tarefa criada: {:?}", nova);
// Listar tarefas
let lista = api.listar(1).await?;
println!("Total de tarefas: {}", lista.total);
for tarefa in &lista.tarefas {
println!(
" [{}] {} (concluída: {})",
tarefa.id, tarefa.titulo, tarefa.concluida
);
}
// Marcar como concluída
let concluida = api.concluir(nova.id).await?;
println!("Tarefa concluída: {:?}", concluida);
// Excluir
let excluida = api.excluir(nova.id).await?;
println!("Tarefa excluída: {}", excluida);
Ok(())
}
Requisições paralelas com Tokio
use reqwest::Client;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct Post {
id: u32,
title: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();
// Fazer múltiplas requisições em paralelo
let urls = vec![
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
"https://jsonplaceholder.typicode.com/posts/4",
"https://jsonplaceholder.typicode.com/posts/5",
];
let futures: Vec<_> = urls
.into_iter()
.map(|url| {
let client = client.clone();
tokio::spawn(async move {
client.get(url).send().await?.json::<Post>().await
})
})
.collect();
for future in futures {
match future.await? {
Ok(post) => println!("[{}] {}", post.id, post.title),
Err(e) => eprintln!("Erro: {}", e),
}
}
Ok(())
}
Comparação com Alternativas
| Característica | Reqwest | Hyper | ureq | surf |
|---|---|---|---|---|
| Nível | Alto nível | Baixo nível | Alto nível | Alto nível |
| Async | Sim (padrão) | Sim | Não (blocking) | Sim |
| Blocking | Opcional | Não | Sim (padrão) | Não |
| TLS | Embutido | Manual | Embutido | Embutido |
| Cookies | Automático | Manual | Manual | Manual |
| JSON | Via feature | Manual | Via feature | Via feature |
| Multipart | Via feature | Manual | Manual | Via feature |
| Connection pool | Automático | Manual | Automático | Automático |
| Dependências | Média | Mínima | Mínima | Média |
- Reqwest vs Hyper: Use Reqwest para consumir APIs e Hyper quando precisar de controle total sobre o protocolo HTTP ou estiver construindo frameworks.
- Reqwest vs ureq: Use ureq se não precisa de async e quer o mínimo de dependências. Reqwest é mais completo e versátil.
- Reqwest vs surf: Surf é baseado em async-std ao invés de Tokio. Se seu projeto já usa Tokio, escolha Reqwest.
Checklist de produção com Reqwest
Antes de colocar uma integração HTTP em produção, trate Reqwest como parte da arquitetura do serviço, não como uma chamada solta dentro de uma função. Uma boa base inclui:
- Reutilizar um
reqwest::Clientpor aplicação, worker ou dependência lógica, em vez de criar um client novo por requisição. - Configurar
timeout,connect_timeout,user_agente política de redirect de forma explícita. - Chamar
error_for_status()ou classificarStatusCodemanualmente para separar 2xx, 4xx e 5xx. - Registrar latência, rota lógica, status, tentativa e classe de erro com Tracing, evitando tokens, CPF, email e payload sensível.
- Definir retry apenas para erros transitórios, com backoff e limite. Nem todo POST pode ser repetido sem idempotência.
- Usar
rustls-tlsquando quiser evitar dependência operacional de OpenSSL, especialmente em containers pequenos. - Testar integrações com mocks ou servidores locais antes de depender de uma API externa real em CI.
Esse checklist aproxima Reqwest de temas que aparecem em backend profissional: Tower para camadas e resiliência, Hyper para entender a base do protocolo, Tokio para concorrência assíncrona e empresas que usam Rust para enxergar onde integrações HTTP sustentam produtos reais.
Conclusão
O Reqwest é a escolha padrão para fazer requisições HTTP em Rust e por boas razões. Sua API ergonômica, suporte completo a async, gerenciamento automático de conexões e funcionalidades prontas como TLS, cookies e compressão tornam-no indispensável para qualquer projeto que precisa se comunicar via HTTP.
Para a maioria dos casos de uso – consumir APIs REST, fazer downloads, enviar webhooks, integrar com serviços externos – o Reqwest resolve com poucas linhas de código e alta confiabilidade.
Próximos passos
- Explore Hyper para entender a camada HTTP de baixo nível.
- Combine Reqwest com Tower para desenhar retry, timeout e rate limiting com mais disciplina.
- Use Tokio para orquestrar múltiplas requisições em paralelo sem bloquear threads.
- Aprenda Tracing para observar chamadas HTTP em produção.
- Conecte esta página com vagas Rust, empresas que usam Rust e backend Rust para orientar estudo por demanda real.