Introdução: por que um cliente HTTP avançado em Rust é importante
Rust conquistou a reputação de ser uma linguagem para sistemas onde desempenho e confiabilidade são importantes. No entanto, ao escrever um cliente HTTP em Rust, muitos desenvolvedores param nos exemplos básicos da documentação. Em projetos reais — microsserviços, parsers, ferramentas CI/CD — uma simples chamada reqwest::get() não resiste a críticas. Quedas devido a erros de rede, travamentos em respostas longas, vazamentos de conexão — tudo isso são consequências de uma abordagem superficial.
Neste artigo, vamos abordar o nível avançado de trabalho com um cliente HTTP em Rust. Você aprenderá como lidar corretamente com erros, configurar timeouts, implementar um mecanismo de tentativas repetidas (retries) e gerenciar TLS. Todos os exemplos são escritos em Rust usando as crates reqwest e tokio — a pilha padrão para requisições HTTP assíncronas.
1. Tratamento de erros no cliente HTTP em Rust
Qualquer requisição de rede pode falhar: interrupção de conexão, erro de DNS, status de resposta inválido. Em Rust, para isso, é usado o poderoso sistema de tipos Result e Error. A abordagem avançada não é apenas verificar o status, mas registrar e classificar os erros.
Tratamento estruturado de erros
Vamos criar um tipo de erro personalizado para o cliente HTTP:
use std::fmt;use reqwest::StatusCode;
#[derive(Debug)]pub enum HttpClientError { NetworkError(reqwest::Error), BadStatus(StatusCode), Timeout, InvalidUrl(String),}
impl fmt::Display for HttpClientError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { HttpClientError::NetworkError(e) => write!(f, "Erro de rede: {}", e), HttpClientError::BadStatus(s) => write!(f, "Status inválido: {}", s), HttpClientError::Timeout => write!(f, "Timeout da requisição"), HttpClientError::InvalidUrl(url) => write!(f, "URL inválida: {}", url), } }}
impl From<reqwest::Error> for HttpClientError { fn from(e: reqwest::Error) -> Self { if e.is_timeout() { HttpClientError::Timeout } else if e.is_connect() { HttpClientError::NetworkError(e) } else { HttpClientError::NetworkError(e) } }}Agora a função de requisição retorna um erro claramente tipado:
async fn fetch_data(url: &str) -> Result<String, HttpClientError> { if !url.starts_with("http") { return Err(HttpClientError::InvalidUrl(url.to_string())); }
let response = reqwest::get(url).await?;
match response.status() { StatusCode::OK => { let body = response.text().await?; Ok(body) } status => Err(HttpClientError::BadStatus(status)), }}Essa abordagem permite que o código chamador saiba exatamente o que deu errado e tome decisões corretas: repetir a requisição em caso de erro de rede, mas não em caso de 400 Bad Request.
2. Timeouts e gerenciamento de conexão
Sem timeouts, seu cliente HTTP em Rust pode travar por minutos. A configuração avançada inclui três níveis: timeout de conexão, timeout de leitura e timeout geral de toda a requisição.
Configuração do cliente com timeouts
use std::time::Duration;use reqwest::Client;
async fn create_advanced_client() -> Result<Client, reqwest::Error> { let client = Client::builder() .timeout(Duration::from_secs(30)) // timeout geral .connect_timeout(Duration::from_secs(5)) // timeout de conexão .pool_max_idle_per_host(10) // máx. conexões idle .tcp_keepalive(Duration::from_secs(60)) // keepalive .build()?;
Ok(client)}Agora usamos o cliente com timeouts na prática