- 后端
- 网络
- 通信
【免费下载链接】undici
An HTTP/1.1 client, written from scratch for Node.js
undici 是一个为 Node.js 从零实现的 HTTP/1.1 客户端,同时也提供了符合 Web 标准的fetch、Request、Headers等 API。本文以 docs/docs/getting-started.md 为主线,结合仓库源码,系统讲解 undici 的安装、fetch用法、三种 Dispatcher(Agent/Pool/Client)的连接复用机制、超时配置、结构化错误处理,以及代理、Mock 测试、全局安装等常见实战模式。读完本文,你将掌握如何用 undici 写出高并发、连接可复用、超时与错误可控的生产级 HTTP 客户端代码。
安装
undici 通过 npm 安装,推荐在 Node.js 项目中作为依赖引入:
npm install undici安装完成后即可在项目中使用 CommonJS 或 ESM 方式引入。注意:undici 自身也提供了 Node.js 内置fetch的替代实现(详见下文"自定义全局 fetch"一节),当你想完全掌控 HTTP 客户端的连接管理、超时与错误行为时,显式引入 undici 是更优选择。
Fetch:最快的起步方式
undici 导出的fetch严格遵循 Fetch 标准,其用法与浏览器 API 完全一致,是开始使用 undici 最快捷的方式:
import { fetch } from 'undici' const res = await fetch('https://example.com') const data = await res.json() console.log(data)fetch返回标准的Response对象,res.json()、res.text()、res.arrayBuffer()、res.formData()等方法都可用。在仓库中,fetch实现在 lib/web/fetch/index.js,而request/stream/pipeline/upgrade/connect等底层 API 统一由 lib/api/index.js 导出。
使用 Request 对象
undici 同时导出了符合 Fetch 标准的Request类,可以显式构造请求再交给fetch:
import { fetch, Request } from 'undici' const req = new Request('https://example.com', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ hello: 'world' }) }) const res = await fetch(req) console.log(res.status)Request支持method、headers、body、signal等标准选项,适合把"请求描述"与"请求发送"分离,便于在多个调用点复用同一个请求对象。
流式读取响应
res.body是一个 Web 标准的ReadableStream。可以用node:stream/promises的pipeline直接把响应体流式写入文件,对大文件下载非常友好(边下载边落盘,内存占用恒定):
import { fetch } from 'undici' import { pipeline } from 'node:stream/promises' import { createWriteStream } from 'node:fs' const res = await fetch('https://example.com/large-file.zip') await pipeline(res.body, createWriteStream('./file.zip'))务必消费或取消响应体。Node.js 的垃圾回收并不足够激进,无法及时释放连接。如果响应体未被读取就丢弃,会导致连接泄漏、请求停滞。参见 Specification Compliance - Garbage Collection。这也是 undici 的
Client提供body.dump()这类"丢弃并消费响应体"方法的原因。
更多fetch细节见 API Reference: Fetch。
Dispatcher:连接复用与连接池
默认情况下,fetch、request、stream、pipeline每次调用都会新建一条连接。对高频请求同一 origin 的应用来说,这是极大的浪费。undici 提供了dispatcher(调度器)来在内部管理连接。
dispatcher 的本质是"分发器":它接收请求参数(origin、path、method 等)与 handler,内部决定使用哪条连接来发送请求。从源码结构看,request等 API 最终都会调用getGlobalDispatcher()拿到当前 dispatcher 并执行dispatch(见 lib/api/index.js 与 lib/global.js)。
Agent—— 多 origin 场景的通用调度器
Agent是最通用的 dispatcher,它按 origin 分别维护连接池,是大多数应用的推荐默认选择。通过setGlobalDispatcher可以全局生效:
import { Agent, setGlobalDispatcher, fetch } from 'undici' const agent = new Agent({ keepAliveTimeout: 30_000, keepAliveMaxTimeout: 600_000 }) setGlobalDispatcher(agent) // 之后所有的 fetch/request/stream/pipeline 调用都会复用连接 const res = await fetch('https://api.example.com/data')也可以按请求单独指定 dispatcher:
await fetch('https://api.example.com/data', { dispatcher: agent })从 lib/dispatcher/agent.js 的实现可以看到,Agent内部维护kClients(Map)与kOrigins(Set):
- 每个 origin 首次请求时通过
factory惰性创建底层 dispatcher,之后同一 origin 的请求复用; - 默认工厂函数(lib/dispatcher/agent.js#L18-L22)会按
connections选项决定创建Client(连接数 1)还是Pool(连接池); maxOrigins选项限制可管理的 origin 数量,超出会抛出MaxOriginsReachedError;allowH2: false时会对该 origin 使用独立的#http1-only连接池键,强制走 HTTP/1.1。
Pool—— 单一 origin 的固定连接池
Pool面向单一 origin,管理一组固定数量的连接,让你显式控制并发度:
import { Pool, request } from 'undici' const pool = new Pool('https://api.example.com', { connections: 10 }) const { body } = await request('https://api.example.com/data', { dispatcher: pool }) const data = await body.json() pool.close()connections: 10表示最多维护 10 条并发连接,超出时请求进入内部队列等待空闲连接。用完记得调用pool.close()优雅关闭。
Client—— 单连接与管道化(pipelining)
Client对应单条 TCP 连接。它支持 pipelining——在响应返回之前就连续发送多个请求,显著提升吞吐:
import { Client } from 'undici' const client = new Client('https://api.example.com', { pipelining: 5 }) const { body } = await client.request({ path: '/', method: 'GET' }) await body.dump() client.close()
pipelining只应在可信的远端服务器上启用。HTTP/1.1 管道化要求服务器按顺序处理并响应请求,若服务器行为不规范,可能造成响应错乱。
Client还支持path、method等底层请求参数,返回的body是一个可流式读取的对象(支持json()、text()、dump()等方法)。从 lib/dispatcher/client.js 的实现可以看到,Client是 undici 连接管理的核心单元,Agent与Pool最终都建立在它之上。
更多 dispatcher 选项与生命周期管理,参见:
- API Reference: Agent
- API Reference: Pool
- API Reference: Client
超时:两个层面,精确控制
undici 在两个层面施加超时:
headersTimeout—— 等待响应头到达的时间(默认300s)。bodyTimeout—— 相邻两个响应体数据块之间的最大间隔时间(默认300s)。
两个选项既可配置在 dispatcher 上,也可按请求覆盖:
import { Agent, setGlobalDispatcher } from 'undici' const agent = new Agent({ headersTimeout: 5_000, bodyTimeout: 30_000 }) setGlobalDispatcher(agent)这些默认值与参数校验都可以在源码中验证:见 lib/dispatcher/client.js#L193-L198(headersTimeout/bodyTimeout必须是非负整数)以及 lib/dispatcher/client.js#L307-L317(默认值300e3即 300 秒)。
此外还有两个连接层超时值得了解:
- 连接超时(connect timeout):
connect选项中的timeout默认10 秒(10e3),超时抛出ConnectTimeoutError,见 lib/core/connect.js#L68-L75。 - keep-alive 超时:
keepAliveTimeout默认4 秒,keepAliveMaxTimeout默认600 秒,keepAliveTimeoutThreshold默认 2 秒(lib/dispatcher/client.js#L307-L309)。空闲连接在该窗口内保持复用。
超时错误会以HeadersTimeoutError和BodyTimeoutError抛出。完整错误列表见 API Reference: Errors。
错误处理:通过error.code精确分支
undici 通过error.code暴露结构化错误,方便在catch中精确分支:
import { request, errors } from 'undici' try { const { body } = await request('https://example.com') await body.json() } catch (err) { switch (err.code) { case 'UND_ERR_CONNECT_TIMEOUT': console.error('Connection timed out') break case 'UND_ERR_HEADERS_TIMEOUT': console.error('Headers timed out') break case 'UND_ERR_BODY_TIMEOUT': console.error('Body timed out') break case 'UND_ERR_ABORTED': console.error('Request was aborted') break default: console.error(err) } }错误码体系定义在 lib/core/errors.js:所有错误都继承自UndiciError(基类code === 'UND_ERR'),常见的子类与code对应关系如下:
| code | 错误类 | 触发场景 |
|---|---|---|
UND_ERR | UndiciError | 所有 undici 错误的基类 |
UND_ERR_CONNECT_TIMEOUT | ConnectTimeoutError | 建立连接超时(默认 10s) |
UND_ERR_HEADERS_TIMEOUT | HeadersTimeoutError | 等待响应头超时(默认 300s) |
UND_ERR_BODY_TIMEOUT | BodyTimeoutError | 响应体块间隔超时(默认 300s) |
UND_ERR_INVALID_ARG | InvalidArgumentError | 参数非法(如负数超时) |
UND_ERR_ABORTED | RequestAbortedError | 请求被 AbortController 中止 |
UND_ERR_HEADERS_OVERFLOW | HeadersOverflowError | 响应头超出限制 |
各错误类通过Symbol.for('undici.error.UND_ERR_*')唯一标识(见 lib/core/errors.js#L20-L90),同时支持instanceof判断与code字符串判断两种方式。
中止请求(Abort)
undici 原生支持标准AbortController,这是浏览器生态兼容的关键设计:
import { request } from 'undici' const ac = new AbortController() setTimeout(() => ac.abort(), 1000) try { const { body } = await request('https://example.com', { signal: ac.signal }) await body.dump() } catch (err) { console.error(err.code) // UND_ERR_ABORTED }传入signal: ac.signal后,调用ac.abort()会中止请求并抛出code === 'UND_ERR_ABORTED'的错误。注意:请求被中止时同样需要消费/取消响应体,避免连接残留。
常见实战模式
代理(Proxy)
通过ProxyAgent使用 HTTP(S) 代理,或用EnvHttpProxyAgent自动读取环境变量中的代理配置:
import { ProxyAgent, setGlobalDispatcher } from 'undici' const proxy = new ProxyAgent('http://proxy.internal:8080') setGlobalDispatcher(proxy)EnvHttpProxyAgent会从HTTP_PROXY/HTTPS_PROXY/NO_PROXY等环境变量中读取代理设置,适合容器、CI 等场景。详见 Best Practices: Proxy 和 API Reference: ProxyAgent。
测试中的 Mock
undici 内置了完整的 Mock 体系,无需启动真实服务器即可模拟接口:
import { MockAgent, setGlobalDispatcher, request } from 'undici' const mockAgent = new MockAgent() setGlobalDispatcher(mockAgent) const mockPool = mockAgent.get('https://api.example.com') mockPool.intercept({ path: '/users' }).reply(200, [{ id: 1 }]) const { body } = await request('https://api.example.com/users') console.log(await body.json())MockAgent支持按 path、method 精确拦截,并可在测试结束后断言所有 mock 是否都被命中。详见 Best Practices: Mocking Request 和 API Reference: MockAgent。
用 undici 写测试套件
在测试环境中,设置较短的 keep-alive 超时可以避免测试收尾时长时间等待连接释放:
import { Agent, setGlobalDispatcher } from 'undici' const agent = new Agent({ keepAliveTimeout: 10, keepAliveMaxTimeout: 10 }) setGlobalDispatcher(agent)详见 Best Practices: Writing Tests。
自定义全局 fetch:install()
你可以用install()覆盖 Node.js 内置的全局对象,让全局的fetch、Headers、Response、Request、FormData等全部来自 undici 而非 Node.js 内置实现:
import { install } from 'undici' install() // 全局的 fetch, Headers, Response, Request, FormData // 现在来自 undici,而不是 Node.js 内置 bundle const res = await fetch('https://example.com')从 lib/global.js#L61-L76 可以看到,install()实际会替换的全局对象包括fetch、Headers、Response、Request、FormData,以及WebSocket、CloseEvent、ErrorEvent、MessageEvent、EventSource。这让 undici 在 Node.js 中提供了一套完整、行为一致的 Web 平台 API。更多细节见 API Reference: Global Installation。
另外,setGlobalDispatcher的实现(lib/global.js#L18-L55)会把 dispatcher 挂载到globalThis上的版本化 Symbol(undici.globalDispatcher.2),当globalThis被冻结不可扩展时还会回退到内部存储,保证极端环境下依然可用。
进一步阅读
- Undici vs. Built-in Fetch —— 何时安装 undici、何时使用 Node.js 内置 fetch 的取舍
- API Reference: Dispatcher —— 完整的 dispatcher API 文档
- Examples —— 可运行的代码示例(
fetch.js、request.js、proxy.js、proxy-agent.js等) - API Reference: Errors —— 全部错误类型与
code清单
- 后端
- 网络
- 通信
【免费下载链接】undici
An HTTP/1.1 client, written from scratch for Node.js
相关推荐
etcd/clientv3 官方 Go 客户端实战指南:从快速上手到连接管理、错误处理与性能调优
etcd/clientv3 官方 Go 客户端实战指南:从快速上手到连接管理、错误处理与性能调优 etcd/clientv3 是 etcd 官方提供的 Go v
后端云原生容器编排微服务Mail-in-a-Box常见错误解决:从550错误到连接超时
Mail in a Box常见错误解决:从550错误到连接超时 一、邮件发送失败:550错误深度解析 1.1 550错误的技术本质与常见触发场景 550错误(S
后端通信undici PoolStats 指南:深入理解连接池与请求计数器快照
undici PoolStats 指南:深入理解连接池与请求计数器快照 PoolStats 是 undici 提供的只读快照对象,用于在任意时刻查看 Pool
后端网络通信
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考