ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

undici 测试编写指南:为自动化测试优化 Keep-Alive 与断开重连防护

undici 测试编写指南:为自动化测试优化 Keep-Alive 与断开重连防护 后端网络通信【免费下载链接】undiciAn HTTP/1.1 client, written from scratch for Node.js项目地址https://gitcode.com/gh_mirrors/un/undici点击查看免费下载Undici 默认面向生产环境调优会在 HTTP 请求完成后将 socket 保留数秒以复用连接但这在自动化测试中会导致执行时间变长。本指南讲解如何在测试中收紧 Keep-Alive 超时、如何识别并防护静默重连对测试结果的掩盖以及如何用disconnect守卫捕捉意外断连帮助你在 undici 的测试代码中写出更快、更可靠、更不易漏报的用例。1. 为什么默认配置不适合测试Undici 是一个从零为 Node.js 编写的 HTTP/1.1 客户端见 README.md其默认行为围绕生产场景设计一个请求完成后socket 会被保留几秒钟默认keepAliveTimeout为 4 秒即4000ms见 client.js以便下一个请求直接复用连接省去重新建连的开销。在生产环境中这是巨大的性能优势但在自动化测试中却是负担每个测试用例结束时空闲 socket 仍占用着文件描述符与事件循环资源测试进程可能因为未关闭的连接而迟迟无法退出或者需要额外等待超时多个测试共享长连接时偶发状态会跨用例泄漏导致用例间相互干扰。因此测试场景下通常需要把 Keep-Alive 窗口压缩到极小让 socket 几乎在响应完成后立即释放。2. 测试环境推荐配置10ms Keep-Alive官方推荐的做法是创建一个专门用于测试的Agent将 socket 的保持时间压到 10msimport { request, setGlobalDispatcher, Agent } from undici const agent new Agent({ keepAliveTimeout: 10, // milliseconds keepAliveMaxTimeout: 10 // milliseconds }) setGlobalDispatcher(agent)这样request()、fetch()等所有走全局调度器的请求都会使用这个测试 Agent。设置后每个请求完成后 socket 最多被保留 10ms 即被回收测试进程更容易干净退出整体执行时间显著缩短。2.1 相关参数的实际含义与默认值从 Client 构造函数 与类型定义可以看出与 Keep-Alive 相关的参数在构造Agent/Client时均受支持具体语义如下对应文档见 Client.md参数默认值含义keepAliveTimeout40004s空闲 socket 在无请求时可被保留的最长时间毫秒。若响应中服务器未给出keep-alive提示使用该值keepAliveMaxTimeout60000010minkeepAliveTimeout可达到的上限。若服务器通过keep-alive响应头给出了更大的时间提示实际值仍会被钳制在该上限内keepAliveTimeoutThreshold20002s从服务器keep-alive提示中减去的毫秒数用于留出缓冲、避免恰好撞上服务端超时默认值来源见 client.js。在 H1 实现的响应头解析处client-h1.js最终生效的超时被计算为timeout Math.min(serverKeepAliveHint - keepAliveTimeoutThreshold, keepAliveMaxTimeout)即先按服务器的 keep-alive 提示减去阈值再受keepAliveMaxTimeout钳制。测试中把两者都设为 10ms意味着无论服务器提示什么socket 都会在 10ms 内释放。2.2 参数校验规则构造函数会对这些参数做严格校验client.jskeepAliveTimeout与keepAliveMaxTimeout必须是非null的有限数字且必须 0否则抛出InvalidArgumentError(invalid keepAliveTimeout)或invalid keepAliveMaxTimeoutkeepAliveTimeoutThreshold必须是有限数字旧版参数名keepAlive、idleTimeout、maxKeepAliveTimeout已不再支持传入会分别抛出 use pipelining0 instead、use keepAliveTimeout instead、use keepAliveMaxTimeout instead 的InvalidArgumentError见 client.js。因此上述 10ms 配置不仅合法而且恰好位于最小合法值之上是测试场景下的合理选择。3. 用 disconnect 守卫捕捉意外断连3.1 问题的本质静默重连会掩盖 BugUndici 的Client在 socket 出错后会自动重连见 Client.md 中disconnect事件说明The client reconnects if or onceclient.size 0。这在生产中是健壮性设计但在测试中却是陷阱假设服务器返回了畸形响应、解析器报错、或协议被违反Client会悄悄断开 socket 再重连下一次请求照常成功——测试可能意外地仍然通过。这类断连恰好是测试最应该暴露的问题却被静默重连机制掩盖了。3.2 守卫代码在非关闭状态下断连即失败要捕捉这类问题可在创建Client后立即挂上disconnect事件守卫const { Client } require(undici) const { test, after } require(node:test) const { tspl } require(matteo.collina/tspl) test(example with disconnect guard, async (t) { t tspl(t, { plan: 1 }) const client new Client(http://localhost:3000) after(() client.close()) client.on(disconnect, () { if (!client.closed !client.destroyed) { t.fail(unexpected disconnect) } }) // ... test logic ... })要点解析client.close()与client.destroy()都会触发disconnect事件但这些属于预期断连。守卫通过!client.closed !client.destroyed把它们排除在外只有在测试活跃期间client 既未 close 也未 destroy发生的断连才会调用t.fail(unexpected disconnect)让测试立即失败该模式在 undici 自己的测试库中被广泛使用例如 client-reconnect.js、client-pipelining.js、client-stream.js 等均采用if (!client.closed !client.destroyed) t.fail(unexpected disconnect)的写法。3.3 底层实现disconnect 事件从哪里来从源码看disconnect事件在以下路径被发出H1 连接在 socket 关闭/出错时client-h1.jsclient.emit(disconnect, client[kUrl], [client], err)err为导致断连的错误如SocketError成功 upgradeWebSocket/upgrade请求后 socket 从Client剥离时client-h1.js携带InformationalError(upgrade)H2 连接关闭时client-h2.js。因此监听client.on(disconnect, (origin, targets, err) {})可以拿到originURL、targets受影响的 dispatcher 数组和err断连原因。守卫判断client 是否仍在服务状态即可区分预期断连与意外断连。3.4 仓库中的现成实现h2-disconnect-guard仓库测试工具里已经封装了一个更完整的守卫test/utils/h2-disconnect-guard.js中的guardAgainstUnexpectedDisconnect(t, client)见 h2-disconnect-guard.jsfunction guardAgainstUnexpectedDisconnect (t, client) { const onDisconnect (_url, _targets, err) { if (client.closed || client.destroyed) { return } if (err ! null SELF_INFLICTED_DISCONNECTS.has(err.message)) { return } t.fail(unexpected disconnect: ${err?.message ?? no error}) } client.on(disconnect, onDisconnect) return () client.off(disconnect, onDisconnect) }它在基础守卫之上增加了一个豁免集合SELF_INFLICTED_DISCONNECTS当前包含socket idle timeout当 H2 socket 因自身 Keep-Alive 空闲超时被Client主动丢弃时这是测试自己造成的断连而非对端强制的守卫不会误报。它返回一个移除守卫的函数便于在用例结束时off掉监听。使用示例见 http2-connection.js 与 h2-disconnect-guard.js。4. 什么时候应当跳过守卫守卫的目的是捕捉测试没有主动要求、却发生了的断连。如果某个测试本身就预期会发生断连继续挂守卫只会产生误报此时应跳过守卫。典型场景包括Signal 中止signal.emit(abort)、ac.abort()触发请求取消服务端主动销毁res.destroy()、req.socket.destroy()请求体中途被客户端销毁data.body.destroy()超时错误HeadersTimeoutError、BodyTimeoutError引发的连接关闭成功 upgradesocket 被从Client剥离如 WebSocket 握手成功后见上文InformationalError(upgrade)的发出路径重试/重连类测试断连本身就是触发重试的机制属于被测行为的一部分畸形响应的 HTTP 解析错误HTTPParserError——这类测试的意图就是验证解析错误处理断连是预期结果。判断口诀只有当连接存活状态未 close、未 destroy下的断连会让测试结果失真时才需要守卫凡是断连就是被测对象本身的预期行为时请跳过。5. 综合实战模板将两部分结合起来一个快速 可靠的测试用例骨架如下import { test, after } from node:test import { Client, Agent, setGlobalDispatcher, request } from undici // 1) 收紧全局 Keep-Alive避免长连接拖慢测试 const agent new Agent({ keepAliveTimeout: 10, keepAliveMaxTimeout: 10 }) setGlobalDispatcher(agent) test(HTTP 请求不出现意外断连, async (t) { const client new Client(http://localhost:3000) after(() client.close()) // 2) 挂上 disconnect 守卫 client.on(disconnect, () { if (!client.closed !client.destroyed) { t.fail(unexpected disconnect) } }) // 3) 正常测试逻辑 const { statusCode } await request(http://localhost:3000, { dispatcher: client }) t.assert.equal(statusCode, 200) })在真实仓库中可参考 client-reconnect.js 这类同时涉及断连与重连的测试观察守卫与预期断连分支如何共存。这样写出的测试既保持了 Undici 生产配置所没有的短平快又能第一时间暴露解析器错误、协议违规等被静默重连掩盖的深层问题。赞分享后端网络通信【免费下载链接】undiciAn HTTP/1.1 client, written from scratch for Node.js项目地址https://gitcode.com/gh_mirrors/un/undici点击查看免费下载相关推荐TypeSpec 扩展开发指南为诊断Diagnostic编写 Code Fix 与自动化测试TypeSpec 扩展开发指南为诊断Diagnostic编写 Code Fix 与自动化测试 导读 Code fix代码修复是 TypeSpec 编译编程语言编译器后端navi测试编写自动化测试开发完全指南navi测试编写自动化测试开发完全指南 navi作为一个功能强大的交互式命令行备忘单工具其测试框架的设计同样体现了高效和实用的理念。本文将带你深入了解nav开发工具chsrc测试框架如何编写和维护自动化测试chsrc测试框架如何编写和维护自动化测试 chsrc作为全平台命令行换源工具其测试框架是确保软件质量和稳定性的关键。通过精心设计的自动化测试chsrc能CLI开发工具上一篇Heya实战教程5步创建完美的用户引导邮件序列下一篇SkyPilot 在 EKS 上使用 IAM Role 访问 S3EKS Pod Identity 与 IRSA 完整配置指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表