Introducción: por qué un cliente HTTP avanzado en Rust es importante
Rust se ha ganado la reputación de ser un lenguaje para sistemas donde el rendimiento y la fiabilidad son importantes. Sin embargo, al escribir un cliente HTTP en Rust, muchos desarrolladores se detienen en los ejemplos básicos de la documentación. En proyectos reales —microservicios, parsers, herramientas CI/CD— una simple llamada a reqwest::get() no es suficiente. Caídas debido a errores de red, congelamientos ante respuestas lentas, fugas de conexiones — todo esto son consecuencias de un enfoque superficial.
En este artículo, analizaremos el nivel avanzado de trabajo con un cliente HTTP en Rust. Aprenderá cómo manejar errores correctamente, configurar tiempos de espera, implementar un mecanismo de reintentos (retries) y gestionar TLS. Todos los ejemplos están escritos en Rust utilizando la crate reqwest y tokio — el stack estándar para solicitudes HTTP asíncronas.
1. Manejo de errores en el cliente HTTP en Rust
Cualquier solicitud de red puede fallar: corte de conexión, error de DNS, estado de respuesta incorrecto. En Rust, se utiliza el potente sistema de tipos Result y Error para esto. El enfoque avanzado no es solo verificar el estado, sino registrar y clasificar los errores.
Manejo estructurado de errores
Creemos un tipo de error personalizado para el 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, "Error de red: {}", e), HttpClientError::BadStatus(s) => write!(f, "Estado incorrecto: {}", s), HttpClientError::Timeout => write!(f, "Tiempo de espera de la solicitud"), HttpClientError::InvalidUrl(url) => write!(f, "URL no vá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) } }}Ahora la función de solicitud devuelve un error claramente tipificado:
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)), }}Este enfoque permite que el código que llama sepa exactamente qué salió mal y tome las decisiones correctas: reintentar la solicitud en caso de error de red, pero no en caso de 400 Bad Request.
2. Tiempos de espera y gestión de conexiones
Sin tiempos de espera, su cliente HTTP en Rust podría congelarse durante minutos. La configuración avanzada incluye tres niveles: tiempo de espera de conexión, tiempo de espera de lectura y tiempo de espera total de la solicitud.
Configuración del cliente con tiempos de espera
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)) // tiempo de espera total .connect_timeout(Duration::from_secs(5)) // tiempo de espera de conexión .pool_max_idle_per_host(10) // máx. conexiones inactivas .tcp_keepalive(Duration::from_secs(60)) // keepalive .build()?;
Ok(client)}Ahora usamos el cliente con tiempos de espera en un