HTTP-Client in JavaScript: Anfragen richtig stellen – Fortgeschrittenes Niveau
Basis-HTTP-Anfragen mit fetch oder axios sind nur die Spitze des Eisbergs. In realen Produktionsanwendungen unter Node.js stößt man auf Dutzende von Nuancen: Verbindungspools, Wiederholungsversuche bei Fehlern, Interceptors für die Protokollierung, Streaming großer Datenmengen und die Feinabstimmung von Timeouts. In diesem Artikel behandeln wir fortgeschrittene Techniken für die Arbeit mit HTTP-Clients in JavaScript. Sie erfahren, wie Sie Keep-Alive verwalten, einen zuverlässigen Retry-Mechanismus implementieren, Interceptors einsetzen und benutzerdefinierte Agenten erstellen. Das Material richtet sich an Entwickler, die bereits mit den Grundlagen von HTTP vertraut sind und effizienteren sowie ausfallsichereren Code schreiben möchten.
1. Verbindungsverwaltung: Keep-Alive und benutzerdefinierte Agenten
Standardmäßig verwendet Node.js einen globalen Pool von HTTP-Agenten. Für hochlastige Systeme ist es jedoch wichtig, die Anzahl der gleichzeitigen Verbindungen und deren Wiederverwendung zu kontrollieren. Betrachten wir die Konfiguration von keep-alive mit dem Modul http und der Bibliothek undici.
Beispiel mit benutzerdefiniertem Agenten (Node.js built-in):
const http = require('http');const https = require('https');
// Erstellen eines benutzerdefinierten Agenten mit Verbindungspoolconst agent = new http.Agent({ keepAlive: true, maxSockets: 50, // Maximal 50 gleichzeitige Sockets maxFreeSockets: 10, // Nicht mehr als 10 freie im Pool timeout: 60000, // Timeout bei Socket-Inaktivität (ms)});
const options = { hostname: 'api.example.com', port: 80, path: '/data', agent: agent,};
const req = http.get(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => console.log('Antwort erhalten:', data));});
req.on('error', (e) => console.error('Fehler:', e.message));Moderner Ansatz mit undici (schneller und leichter):
const { Client, Pool } = require('undici');
// Erstellen eines Pools mit Keep-Aliveconst client = new Pool('https://api.example.com', { connections: 100, pipelining: 10, keepAliveTimeout: 30000,});
// Anfrage ausführenclient.request({ method: 'GET', path: '/data',}).then(({ statusCode, body }) => { console.log('Status:', statusCode); body.setEncoding('utf8'); body.on('data', console.log);}).catch(err => console.error(err));Die Verwendung benutzerdefinierter Agenten hilft, Probleme mit Socket-Leaks zu vermeiden und verbessert die Leistung durch die Wiederverwendung von Verbindungen.
2. Zuverlässiger Mechanismus für Wiederholungsversuche (Retry)
Netzwerke sind instabil. Selbst vorübergehende Ausfälle können zu Datenverlust führen. Eine korrekte Retry-Strategie sollte den Fehlertyp, die Anzahl der Versuche und eine exponentielle Verzögerung berücksichtigen. Wir implementieren einen universellen Retry-Dekorator für jeden HTTP-Client.
const axios = require('axios');const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
async function withRetry(requestFn, retries = 3, baseDelay = 1000) { for (let attempt = 1; attempt <= retries; attempt++) { try { return await requestFn(); } catch (error) { // Keine Wiederholung bei Client-Fehlern (4xx) if (error.response && error.response.status >= 400 && error.response.status < 500) { throw error; } if (attempt === retries) { throw error; // Alle Versuche ausgeschöpft } const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000; console.log(`Versuch ${attempt} fehlgeschlagen. Warte ${delay}ms...`); await sleep(delay); } }}
// Verwendungasync function fetchData() { const response = await withRetry(() => axios.get('https://api.example.com/data'), 5, 500); return response.data;}
fetchData().then(console.log).catch(console.error);Wichtige Punkte: Ausschluss von 4xx-