Introdução: por que escrever um cliente HTTP em Rust?
O desenvolvimento moderno raramente acontece sem interação com serviços externos. Seja uma API REST de clima, um banco de dados ou uma arquitetura de microsserviços — saber enviar requisições HTTP é uma habilidade básica. Rust, com seu foco em desempenho e segurança, oferece várias soluções elegantes para trabalhar com HTTP. Neste artigo, vamos analisar como criar clientes HTTP em Rust corretamente: desde a escolha da biblioteca até o tratamento de cenários complexos.
Vamos abordar as duas bibliotecas mais populares: reqwest (padrão assíncrono) e ureq (abordagem síncrona simples). Você aprenderá como enviar requisições GET e POST, processar JSON, gerenciar cabeçalhos e evitar erros comuns.
1. Escolha da biblioteca: reqwest vs ureq
Antes de escrever código, é preciso decidir qual ferramenta usar. No ecossistema Rust, existem dois principais candidatos.
1.1. Reqwest — poderoso e assíncrono
reqwest é o padrão de facto para requisições HTTP em Rust. Ele é construído sobre hyper e tokio, suporta assincronia, serialização/desserialização automática de JSON e trabalho com cookies. Ideal para aplicações de alta carga e servidores web.
Para começar, adicione ao Cargo.toml:
[dependencies]reqwest = { version = "0.12", features = ["json"] }tokio = { version = "1", features = ["full"] }serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"1.2. Ureq — simplicidade sem assincronia
Se você não precisa de chamadas assíncronas e está escrevendo um script simples ou utilitário de linha de comando, ureq é uma excelente escolha. Ele é síncrono, minimalista e não requer configuração complexa de runtime.
[dependencies]ureq = "2.9"serde = { version = "1.0", features = ["derive"] }serde_json = "1.0"Quando escolher o quê? Para servidores web e aplicações complexas — reqwest. Para tarefas simples, testes e scripts — ureq.
2. Requisições básicas: GET e POST
Vamos ver exemplos práticos para ambas as bibliotecas.
2.1. Requisição GET com reqwest
O exemplo mais simples — obter dados de uma API pública. Usamos uma função main assíncrona com a macro #[tokio::main].
use reqwest;use std::error::Error;
#[tokio::main]async fn main() -> Result<(), Box<dyn Error>> { let url = "https://jsonplaceholder.typicode.com/posts/1"; let response = reqwest::get(url).await?;
// Verificamos o status da resposta if response.status().is_success() { let body = response.text().await?; println!("Resposta: {}", body); } else { eprintln!("Erro HTTP: {}", response.status()); }
Ok(())}2.2. Requisição POST com corpo JSON (reqwest)
O envio de dados frequentemente requer serialização de uma estrutura. Para isso, usamos a crate serde.
use reqwest;use serde::{Deserialize, Serialize};use std::error::Error;
#[derive(Serialize, Deserialize, Debug)]struct Post { title: String, body: String, user_id: i32,}
#[tokio::main]async fn main() -> Result<(), Box<dyn Error>> { let client = reqwest::Client::new(); let new_post = Post { title: "Hello Rust".to_string(), body: "Este é o corpo da requisição".to_string(), user_id: 1, };
let response = client .post("https://jsonplaceholder.typicode.com/posts") .json(&new_post) .send() .await?;
let created_post: Post = response.json().await?; println!("Post criado: {:?}", created_post);
Ok(())}2.3. GET simples com ureq
Com ureq, o código fica mais curto, pois não requer assincronia:
use ureq;use std::error::Error;
fn