深入 @scalar/void-server:Scalar 开源 HTTP 请求镜像服务器的设计与演进
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
本篇文章以@scalar/void-server的版本变更记录(CHANGELOG.md)为主线,结合其 README.md 与源码实现,系统讲解 Scalar 开源 API 平台中这个"请求镜像(Echo)服务器"的完整能力。你将掌握它的安装与启动方式、JSON/HTML/XML/ZIP/SSE 等多样化的响应机制、HTTP 错误码路由、WebSocket 回显、安全加固手段,以及它从 2.0.0 到 2.5.9 的演进脉络与背后工程决策,可以直接用它调试 API 客户端、验证代理与网关行为。
Void Server 是什么:为 HTTP 请求"照镜子"
从 README.md 的定义看,void-server 是一个基于 Hono 的服务器,它的核心行为只有一句话:把收到的请求数据原样回显给你("An Hono server that responds with the request data. Kind of a mirror for HTTP requests.")。
它解决的是一个非常实际的工程痛点:当你调试 API 客户端、SDK、代理或网关时,常常需要一个"固定目标"来确认请求到底发出了什么——方法、路径、请求头、Cookie、查询参数、认证信息、请求体。void-server 就是这个目标:任何请求打进来,它都会把完整快照作为响应返回,让你一眼看清请求的真实面貌。
其 package.json(packages/void-server/package.json)将自身定位描述为 "Mirror for HTTP requests",关键词包括scalar、http testing、hono。官方还维护了一个公开部署实例供直接体验(见 README),你可以把它当作临时调试靶场。
快速上手:安装与最小启动示例
安装(需要 Node.js >= 22,见 package.json 的engines字段,该要求自 2.4.0 起生效):
npm add @scalar/void-server最小启动示例(完整继承自 README.md):
import { serve } from '@hono/node-server' import { attachVoidWebSocket, createVoidServer } from '@scalar/void-server' const app = createVoidServer() const httpServer = serve( { fetch: app.fetch, port: 3000, }, (info) => { console.log(`Listening on http://localhost:${info.port}`) console.log(`WebSocket echo at ws://localhost:${info.port}/<any-path>`) }, ) attachVoidWebSocket(httpServer)启动后,访问任意路径即可获得请求数据的 JSON 快照,例如:
http://localhost:3000/—— 返回请求数据的 JSON 展示http://localhost:3000/404—— 返回 404 状态与对应错误文本http://localhost:3000/foobar.html—— 返回 HTML 格式的请求快照http://localhost:3000/foobar.xml—— 返回 XML 格式的请求快照http://localhost:3000/foobar.zip—— 返回包含request.json的 ZIP 压缩包http://localhost:3000/?foo=bar&foo=rab—— 演示查询参数数组(foo同时携带两个值)ws://localhost:3000/any-path—— WebSocket 回显,连接默认 60 秒后关闭
仓库自带的开发入口 playground/index.ts 展示了同样的模式:通过环境变量HOST(默认0.0.0.0)与PORT(默认8080)启动服务,并同时挂载 WebSocket 回显。
请求镜像的底层实现:getRequestData
所有请求最终都会经过 get-request-data.ts 汇聚成统一的数据结构,该结构包含以下字段:
| 字段 | 说明 |
|---|---|
method | HTTP 方法(GET/POST/PUT/DELETE 等) |
path | 请求路径 |
headers | 全部请求头(由Object.fromEntries(c.req.raw.headers)展开) |
authentication | 解析后的认证信息(见下文) |
cookies | 由 Hono 的getCookie解析出的 Cookie 对象 |
query | 查询参数,数组参数保持为数组,单个值则归一为字符串 |
body | 请求体,自动按内容类型解析 |
认证信息的自动解析
源码对Authorization请求头做了两类识别(get-request-data.ts):
- Basic 认证:当请求头以
Basic开头时,输出type: 'http.basic'、原始token,并借助依赖js-base64的decode解出明文的value; - Bearer 认证:当请求头以
Bearer开头时,输出type: 'http.bearer'与原始token。
这让你可以验证客户端是否正确编码/携带了凭证,而无需真实后端参与。
查询参数数组
从 2.0.11 起支持查询参数数组(对应 CHANGELOG 中 "feat: query parameter arrays")。实现上先调用c.req.queries()拿到所有参数,再对每个 key 判断:若值多于一个则保留为数组,否则退化为单值字符串(get-request-data.ts)。因此?foo=bar&foo=rab会得到foo: ['bar', 'rab']。
请求体解析:JSON / 文本 / 表单 / 文件上传
get-body.ts 按Content-Type分流解析请求体:
application/x-www-form-urlencoded或multipart/form-data:调用 Hono 的parseBody({ dot: true, all: true }),随后经过transformFormData规整(解析失败时静默返回空对象);- 其余情况先按文本读取,再尝试
JSON.parse:成功则返回 JSON 对象,失败则保留原始文本。
transformFormData对表单值做了类型化处理(get-body.ts):字符串原样保留;File实例会被转换为{ name, sizeInBytes, type, lastModified }结构(lastModified由时间戳转为 ISO 字符串);数组中的文件项同样逐个转换;嵌套对象递归处理。这是 2.0.3 "form data and multipart form data" 特性的具体落地,适合用来检查 SDK 上传文件的字段名、MIME 类型与大小。
多样化的响应格式:JSON、HTML、XML 与 ZIP
void-server 最核心的使用场景是按需返回不同格式的请求快照。路由策略在 create-void-server.ts 中实现,分为两层:
1. 基于文件扩展名强制格式(2.0.4 起)
路径以.html、.xml、.zip、.json结尾时,直接按对应格式返回(正则/:filename{.+\\.(html|xml|json|zip)$}):
- JSON:
createJsonResponse直接c.json(data),这是默认格式; - HTML:
createHtmlResponse设置Content-Type: text/html,用 Hono 的html标签模板递归渲染对象树(create-html-response.ts),键与值均经过自动转义以防御 XSS,这也是 2.3.0 "escape html and xml, add security headers" 的核心内容之一; - XML:
createXmlResponse通过工作区依赖@scalar/helpers提供的json2xml转换,并显式返回Content-Type: application/xml; charset=UTF-8(create-xml-response.ts)。配套的转义逻辑同样来自 2.3.0 与 2.2.2 的 "better xml rendering";2.2.5 修复了重复的 XML 定义,2.0.14 修复了缺失 XML 头的问题; - ZIP:
createZipFileResponse不依赖第三方压缩库,而是手工按 ZIP 二进制格式拼装(create-zip-file-response.ts):写入本地文件头(签名0x04034b50)、文件名为request.json的文件内容(格式化 JSON)、中央目录头(签名0x02014b50)与目录结束记录(签名0x06054b50),并自行实现了 CRC-32 校验表(多项式0xEDB88320)计算文件校验值。2.4.7 的修复("fix zip responses to return a valid archive with request JSON payload")确保了产物是能被标准解压工具识别的合法压缩包。
2. 基于 Accept 头的内容协商
对于不以扩展名结尾的普通路径,app.all('/*')使用 Hono 的accepts工具按Accept请求头协商(create-void-server.ts):支持text/html、application/xml、application/zip,默认回落到application/json。也就是说,同一个 URL 可以因客户端声明的 Accept 不同而返回不同格式——非常适合测试内容协商逻辑。
HTTP 状态码路由:任意 4xx/5xx 即点即得
从 2.0.3 起("routes for all HTTP errors e.g. /503"),void-server 提供了一条通配路由app.all('/:status{[4-5][0-9][0-9]}')(create-void-server.ts):任何形如4xx或5xx的路径都会以该数字作为 HTTP 状态码返回。状态文本来自 constants.ts 中维护的错误码映射表,覆盖 400~511 的常见状态,例如:
400 Bad Request、401 Unauthorized、403 Forbidden、404 Not Found418 I'm a teapot、429 Too Many Requests、451 Unavailable For Legal Reasons500 Internal Server Error、502 Bad Gateway、503 Service Unavailable、504 Gateway Timeout
映射表中未覆盖的状态码(如 405、420、425 等)会回退为Unknown Error。此外,2.0.10 引入了独立的/:status{204}路由,返回空响应体(c.body(null, 204),见 create-void-server.ts),便于测试客户端对 204 No Content 的处理。
流式响应:SSE 与 WebSocket 回显
/stream:服务端事件(SSE)长连接
2.1.0 引入/stream端点,用于测试 SSE 客户端。实现见 create-stream-response.ts:设置Content-Type: text/event-stream、Cache-Control: no-cache、Connection: keep-alive三个头后,通过 Hono 的stream每秒写入一行data: ping,永不结束。2.4.5 的修复("fix stream endpoint headers for server-sent events")正是针对这批 SSE 响应头。
attachVoidWebSocket:任意路径的 WebSocket 回显
2.5.0 是 WebSocket 能力的里程碑("feat: add WebSocket echo endpoint with connection timeout")。核心实现位于 void-websocket.ts:
- 基于
ws库的WebSocketServer({ noServer: true }),在已有 Node HTTP 服务器上监听upgrade事件; - 只有带
Upgrade: websocket头的请求才会被处理,其余升级请求直接socket.destroy(); - 默认接受任意路径(与 HTTP 侧"镜像一切"的语义一致);指定
path时按 URL 去掉查询串后的路径精确匹配; - 连接建立后,
message事件把收到的文本或二进制帧原样回发(ws.send(data, { binary: isBinary })),即"回显"; - 每个连接绑定一个超时定时器,到点后以 1000 状态码关闭(
'Connection timeout'),并在error/close时清理定时器,避免长驻服务堆积空闲 socket。
重复调用幂等:attachVoidWebSocket通过 Symbol 标记(VOID_WEBSOCKET_SERVER)把WebSocketServer挂在 HTTP 服务器上;第二次调用直接返回已有实例,不会重复注册监听器,且后续传入的选项会被忽略(见 void-websocket.ts 及 README 说明)。
WebSocket 选项如下:
| 选项 | 说明 |
|---|---|
path | 限定接受的升级路径;默认接受任意路径 |
connectionTimeoutMs | 连接最大存活时长(毫秒)。未传、为 0 或负数时,回退到环境变量VOID_WEBSOCKET_TIMEOUT_MS,再回退到默认值 60 秒(DEFAULT_VOID_WEBSOCKET_TIMEOUT_MS = 60_000,见 void-websocket.ts) |
attachVoidWebSocket(httpServer, { path: '/ws', connectionTimeoutMs: 30_000, })配套测试位于 void-websocket.test.ts,覆盖回显与超时行为。
安全与可观测性:安全头、CORS 与请求日志
安全头与逃逸(2.3.0)
2.3.0 是安全相关的关键版本("feat: escape html and xml, add security headers, add more tests"):
- CORS:所有请求默认启用 Hono 的
cors()中间件; - 安全响应头:在 create-void-server.ts 中通过中间件为所有响应追加
X-Content-Type-Options: nosniff与Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; - XSS 防护:HTML 响应的键值由 Hono
html标签模板自动转义,XML 响应经由@scalar/helpers的json2xml转义(对应 helpers 的 "feat: escape XML in json2xml")。
请求日志与 logger 选项(2.5.2)
Void Server 默认在 CI 之外启用 Hono 请求日志(app.use(honoLogger(...)),见 create-void-server.ts)。2.5.2 为createVoidServer增加了logger选项,类型为boolean | 日志回调:
- 默认行为:
options.logger ?? !process.env.CI,即 CI 环境下自动关闭,本地开发自动开启; false:当你的平台(如托管服务、网关)已经采集请求日志时关闭内置日志;true:强制使用 Hono 默认 logger;- 函数:自定义日志写入目标,例如
(line) => console.info(line)。
// 关闭日志 const app = createVoidServer({ logger: false }) // 自定义日志输出 const app = createVoidServer({ logger: (line) => console.info(line) })版本演进时间线:从 2.0.0 到 2.5.9
CHANGELOG 完整记录了该包的演进。按主题归纳如下:
| 版本 | 主题 | 关键变更 |
|---|---|---|
| 2.0.0 | 初始版本 | 包首次发布(init) |
| 2.0.1 | 响应格式 | 新增 HTML 与 XML 响应 |
| 2.0.2 | 工程化 | TypeScript 升级到 5.5 |
| 2.0.3 | 表单与错误路由 | 支持表单与 multipart 表单数据;新增全部 HTTP 错误路由(如/503) |
| 2.0.4 | 扩展名路由 | .json路径强制 JSON 响应,.xml路径强制 XML 响应 |
| 2.0.5 / 2.0.9 | Node 兼容 | 修复 Node 18 无全局File、file undefined问题 |
| 2.0.7 / 2.0.8 | 依赖清理 | 移除 undici 依赖、移除node:buffer导入、修正 package.json 目录 |
| 2.0.10 | 状态码路由 | /204返回空响应体 |
| 2.0.11 | 查询参数 | 支持查询参数数组 |
| 2.0.12/2.0.13 | 类型安全 | 增加默认导出;启用noUncheckedIndexedAccess |
| 2.0.14 | XML 修复 | 修复缺失 XML 头 |
| 2.0.16/2.0.17 | 工程化 | 更严格的 TS 配置;修复 Docker 部署 |
| 2.1.0 | 日志与 SSE | 采用 Hono logger;新增/stream服务端事件端点 |
| 2.1.1 | 构建 | 构建工具迁移到 esbuild |
| 2.2.0 | 运行时要求 | 要求 Node 20 及以上 |
| 2.2.1/2.2.2 | 输出细节 | 统一直撇号;支持 base64 Unicode 字符;优化 XML 渲染 |
| 2.2.5 | XML 修复 | 修复重复 XML 定义 |
| 2.3.0 | 安全加固 | HTML/XML 逃逸、安全响应头、新增更多测试 |
| 2.4.0 | 运行时要求 | Node 版本要求提升到 >=22(LTS) |
| 2.4.3 | 构建流水线 | 新的构建流水线 |
| 2.4.5 | SSE 修复 | 修复服务端事件响应头 |
| 2.4.7 | ZIP 修复 | 修复 ZIP 响应以返回包含请求 JSON 载荷的合法压缩包 |
| 2.5.0 | WebSocket | 新增 WebSocket 回显端点与连接超时 |
| 2.5.2 | 可配置性 | createVoidServer支持 logger 选项 |
| 2.5.3 | 发布工程 | 重新发布以修正 npm 上[object Object]的 README;package.json的 README 生成元数据字段由readme更名为scalarReadme |
| 2.5.4/2.5.5 | 文档 | 更新 README 中的 Scalar 平台概览 |
| 2.5.6/2.5.7/2.5.8 | 发布工程 | 通过 npm trusted publishing 重新发布全部包,无功能变化 |
此外,包持续依赖工作区内的@scalar/helpers(packages/helpers),其历次更新(如 v1 本地存储到 v2 IndexedDB 迁移器、history/auth 独立 store、认证持久化修复、代理重定向导入导出修复等)也会随版本同步进入 void-server 的变更记录中,体现了 monorepo 依赖管理的连锁升级机制。
源码结构导览
想深入阅读实现,可从以下文件入手:
- src/create-void-server.ts:
createVoidServer主入口,路由与中间件编排 - src/void-websocket.ts:
attachVoidWebSocket与超时机制 - src/utils/get-request-data.ts:请求快照汇聚
- src/utils/get-body.ts:请求体解析
- src/utils/create-html-response.ts / create-xml-response.ts / create-zip-file-response.ts / create-stream-response.ts:四种特殊响应格式
- src/utils/constants.ts:HTTP 错误码映射
- src/create-void-server.test.ts 与 src/void-websocket.test.ts:核心行为测试
结语
@scalar/void-server以"请求镜像"这一极简理念,借助 Hono 生态实现了从 JSON 快照、多格式回显、状态码路由到 SSE 与 WebSocket 回显的完整调试能力,并通过 2.3.0 的安全加固、2.5.0 的 WebSocket 超时与 2.5.2 的日志可配置化,逐步打磨成一个可生产部署的 HTTP 测试基础设施。它的版本历史本身就是一份优秀的最小化服务演进范本:功能按需生长、运行时要求随 LTS 同步升级、发布工程问题持续修正。无论是接入 API 客户端开发调试,还是作为 CI 中的代理验证靶场,它都是一个轻量而可靠的选择。
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考