Client HTTP en Rust : techniques avancées et meilleures pratiques

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : pourquoi un client HTTP avancé en Rust est important

Rust a acquis la réputation d'être un langage pour les systèmes où la performance et la fiabilité sont essentielles. Cependant, lors de l'écriture d'un client HTTP en Rust, de nombreux développeurs s'arrêtent aux exemples de base de la documentation. Dans les projets réels — microservices, parseurs, outils CI/CD — un simple appel reqwest::get() ne résiste pas à la critique. Les plantages dus aux erreurs réseau, les blocages lors de réponses longues, les fuites de connexions — tout cela est la conséquence d'une approche superficielle.

Dans cet article, nous allons aborder le niveau avancé du travail avec un client HTTP en Rust. Vous apprendrez à gérer correctement les erreurs, configurer les timeouts, implémenter un mécanisme de tentatives (retries) et gérer TLS. Tous les exemples sont écrits en Rust en utilisant la crate reqwest et tokio — la pile standard pour les requêtes HTTP asynchrones.



1. Gestion des erreurs dans un client HTTP en Rust

Toute requête réseau peut échouer : rupture de connexion, erreur DNS, statut de réponse incorrect. En Rust, le puissant système de types Result et Error est utilisé pour cela. L'approche avancée consiste non seulement à vérifier le statut, mais aussi à journaliser et classer les erreurs.



Gestion structurée des erreurs

Créons un type d'erreur personnalisé pour le client 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, "Erreur réseau : {}", e), HttpClientError::BadStatus(s) => write!(f, "Statut incorrect : {}", s), HttpClientError::Timeout => write!(f, "Timeout de la requête"), HttpClientError::InvalidUrl(url) => write!(f, "URL invalide : {}", 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) } }}


Maintenant, la fonction de requête retourne une erreur clairement typée :

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)), }}


Cette approche permet au code appelant de savoir exactement ce qui s'est mal passé et de prendre les bonnes décisions : répéter la requête en cas d'erreur réseau, mais pas en cas de 400 Bad Request.



2. Timeouts et gestion des connexions

Sans timeouts, votre client HTTP en Rust peut se bloquer pendant des minutes. La configuration avancée comprend trois niveaux : timeout de connexion, timeout de lecture et timeout global de la requête entière.



Configuration du client avec 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 global .connect_timeout(Duration::from_secs(5)) // timeout de connexion .pool_max_idle_per_host(10) // max. connexions inactives .tcp_keepalive(Duration::from_secs(60)) // keepalive .build()?;

Ok(client)}


Maintenant, utilisons le client avec timeouts en ré

Blogs

Book Recommendations