Einleitung: Warum ein fortgeschrittener HTTP-Client in Rust wichtig ist
Rust hat sich den Ruf einer Sprache für Systeme erworben, bei denen Leistung und Zuverlässigkeit entscheidend sind. Beim Schreiben eines HTTP-Clients in Rust bleiben viele Entwickler jedoch bei den grundlegenden Beispielen aus der Dokumentation stehen. In realen Projekten – Mikrodiensten, Parsern, CI/CD-Tools – reicht ein einfacher Aufruf von reqwest::get() nicht aus. Abstürze aufgrund von Netzwerkfehlern, Hänger bei langen Antworten, Verbindungslecks – all dies sind Folgen einer oberflächlichen Herangehensweise.
In diesem Artikel behandeln wir die fortgeschrittene Arbeit mit einem HTTP-Client in Rust. Sie erfahren, wie Sie Fehler richtig behandeln, Timeouts konfigurieren, einen Mechanismus für Wiederholungsversuche (Retries) implementieren und TLS verwalten. Alle Beispiele sind in Rust unter Verwendung der Crate reqwest und tokio geschrieben – dem Standard-Stack für asynchrone HTTP-Anfragen.
1. Fehlerbehandlung im HTTP-Client in Rust
Jede Netzwerkanfrage kann fehlschlagen: Verbindungsabbruch, DNS-Fehler, ungültiger Antwortstatus. In Rust wird dafür das leistungsstarke Typsystem Result und Error verwendet. Ein fortgeschrittener Ansatz besteht nicht nur darin, den Status zu überprüfen, sondern Fehler zu protokollieren und zu klassifizieren.
Strukturierte Fehlerbehandlung
Wir erstellen einen benutzerdefinierten Fehlertyp für den HTTP-Client:
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, "Netzwerkfehler: {}", e), HttpClientError::BadStatus(s) => write!(f, "Ungültiger Status: {}", s), HttpClientError::Timeout => write!(f, "Anfrage-Timeout"), HttpClientError::InvalidUrl(url) => write!(f, "Ungültige URL: {}", 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) } }}Nun gibt die Anfragefunktion einen klar typisierten Fehler zurück:
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)), }}Dieser Ansatz ermöglicht es dem aufrufenden Code, genau zu wissen, was schiefgelaufen ist, und die richtigen Entscheidungen zu treffen: die Anfrage bei einem Netzwerkfehler zu wiederholen, aber nicht bei einem 400 Bad Request.
2. Timeouts und Verbindungsverwaltung
Ohne Timeouts kann Ihr HTTP-Client in Rust minutenlang hängen bleiben. Die fortgeschrittene Konfiguration umfasst drei Ebenen: Timeout für die Verbindung, für das Lesen und ein allgemeines Timeout für die gesamte Anfrage.
Konfiguration des Clients mit 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)) // allgemeines Timeout .connect_timeout(Duration::from_secs(5)) // Timeout für Verbindungsaufbau .pool_max_idle_per_host(10) // max. idle-Verbindungen .tcp_keepalive(Duration::from_secs(60)) // Keepalive .build()?;
Ok(client)}Jetzt verwenden wir den Client mit Timeouts in der Praxis