JavaScript HTTP 客户端:正确发起请求——高级水平
使用 fetch 或 axios 进行基本的 HTTP 请求只是冰山一角。在真实的 Node.js 生产级应用中,你会遇到数十个细节问题:连接池、故障重试、日志记录拦截器、大数据流式传输以及超时的精细调整。在本文中,我们将深入探讨 JavaScript 中 HTTP 客户端的高级技术。你将学习如何管理 keep-alive、实现可靠的重试机制、使用拦截器以及创建自定义代理。本文面向已经熟悉 HTTP 基础知识并希望编写更高效、更容错代码的开发者。
1. 连接管理:Keep-Alive 和自定义代理
默认情况下,Node.js 使用全局 HTTP 代理池。然而,对于高负载系统,控制并发连接数及其复用至关重要。让我们看看如何使用 http 模块和 undici 库配置 keep-alive。
使用自定义代理的示例(Node.js 内置):
const http = require('http');const https = require('https');
// 创建带有连接池的自定义代理const agent = new http.Agent({ keepAlive: true, maxSockets: 50, // 最多 50 个并发套接字 maxFreeSockets: 10, // 池中最多 10 个空闲套接字 timeout: 60000, // 套接字空闲超时(毫秒)});
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('收到响应:', data));});
req.on('error', (e) => console.error('错误:', e.message));使用 undici 的现代方法(更快、更轻量):
const { Client, Pool } = require('undici');
// 创建带有 keep-alive 的池const client = new Pool('https://api.example.com', { connections: 100, pipelining: 10, keepAliveTimeout: 30000,});
// 执行请求client.request({ method: 'GET', path: '/data',}).then(({ statusCode, body }) => { console.log('状态码:', statusCode); body.setEncoding('utf8'); body.on('data', console.log);}).catch(err => console.error(err));使用自定义代理可以避免套接字泄漏问题,并通过复用连接提高性能。
2. 可靠的重试机制
网络是不稳定的。即使是暂时的故障也可能导致数据丢失。正确的重试策略应考虑错误类型、重试次数和指数退避。让我们为任何 HTTP 客户端实现一个通用的重试装饰器。
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) { // 不对客户端错误(4xx)进行重试 if (error.response && error.response.status >= 400 && error.response.status < 500) { throw error; } if (attempt === retries) { throw error; // 所有尝试均已耗尽 } const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000; console.log(`尝试 ${attempt} 失败。等待 ${delay}ms...`); await sleep(delay); } }}
// 使用示例async 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);关键点:排除 4xx-