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.
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.
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 o Hyper para entender a camada HTTP de baixo nível
- Combine Reqwest com Tower para adicionar middleware como retry e rate limiting
- Use Tokio para orquestrar múltiplas requisições em paralelo
- Aprenda sobre Tracing para observar suas requisições HTTP em produção