Koa v3 如何将 Blob、ReadableStream、Response 等 WHATWG 对象作为响应体返回?
2026/9/12 11:17:14 网站建设 项目流程

Koa v3 如何将 Blob、ReadableStream、Response 等 WHATWG 对象作为响应体返回?

【免费下载链接】koaExpressive middleware for node.js using ES2017 async functions项目地址: https://gitcode.com/GitHub_Trending/ko/koa

如果你从 Koa v2 升级到 v3,或者在 Node.js v18+ 环境中直接开发新服务,你手上的响应数据可能不再是字符串或 Buffer,而是fetch拿到的Response对象、Web API 生成的BlobReadableStream。Koa v3 原生支持把这三类 WHATWG 对象直接赋给ctx.body,由框架完成状态码、响应头与请求体的写出。本文基于本仓库的 迁移指南、Response API 文档 和__tests__中的测试用例,给出一个可直接运行的示例,并说明每类对象在赋值后 Koa 实际做了什么、如何验证响应符合预期。

前提条件(均来自仓库文档):

  • Node.jsv18.0.0 或更高版本,这是 Koa v3 的硬性要求,见 Readme.md 与 migration-v2-to-v3.md;当前仓库 package.json 中engines.node>= 18
  • 通过npm install koa安装的 Koa v3(仓库当前版本为 3.2.1)。

一个可运行的示例:三种对象各占一个端点

迁移文档给出的最小示例是三种对象共用一个中间件(migration-v2-to-v3.md):

app.use(async ctx => { // Using a Blob ctx.body = new Blob(['Hello World'], { type: 'text/plain' }) // Using a ReadableStream ctx.body = new ReadableStream({ start(controller) { controller.enqueue('Hello World') controller.close() } }) // Using a Response object ctx.body = new Response('Hello World', { headers: { 'Content-Type': 'text/plain' } }) })

实际使用时更建议按路由拆开,每个端点只返回一种对象。下面是完整可运行的示例,基于 Readme.md 中 Hello Koa 的启动结构:

const Koa = require('koa'); const app = new Koa(); // 1. Blob 作为响应体 app.use(ctx => { if (ctx.path === '/blob') { ctx.body = new Blob(['Hello World'], { type: 'text/plain' }); return; } // 2. ReadableStream 作为响应体 // 注意:先设置 ctx.type,再赋 body, // 否则 Content-Type 会默认成 application/octet-stream if (ctx.path === '/stream') { ctx.type = 'text/plain'; ctx.body = new ReadableStream({ start(controller) { controller.enqueue(new TextEncoder().encode('Hello ')); controller.enqueue(new TextEncoder().encode('World')); controller.close(); } }); return; } // 3. Response 对象作为响应体(例如 fetch 的返回值) if (ctx.path === '/response') { ctx.body = new Response('Hello World', { status: 200, headers: { 'Content-Type': 'text/plain' } }); return; } ctx.body = 'Hello Koa'; }); app.listen(3000);

启动后访问http://localhost:3000/blob/stream/response三个端点即可。BlobReadableStreamResponse在 Node.js 中是全局构造器,无需引入任何模块。

赋值后 Koa 实际做了什么

这三类对象的赋值逻辑在 lib/response.js 的bodysetter 中,最终写出在 lib/application.js 的 respond 流程中。理解这两处行为,才能正确控制响应头。

Blob(lib/response.js#L209-L214)

  • 如果尚未设置Content-Type,Koa 将其设为bin(即application/octet-stream);
  • Content-Length自动取blob.size
  • 写出时通过Stream.Readable.from(body.stream())转成 Node 流(lib/application.js#L320)。

所以创建 Blob 时带上type(如{ type: 'text/plain' })或提前设置ctx.type,客户端拿到的才是正确的 MIME 类型,而不是默认的application/octet-stream

ReadableStream(lib/response.js#L202-L206)

  • 仅在未设置Content-Type时默认application/octet-stream
  • 不会设置Content-Length(长度取决于流式输出);
  • 写出时经Stream.Readable.from(body)接入Stream.pipeline写入响应(lib/application.js#L321-L328)。

Response(lib/response.js#L217-L226)

  • Koa 的response.status直接取Response.status
  • 遍历Response.headers,逐个set到 Koa 响应头;
  • 如果此时未设置Content-Type,会默认application/octet-stream
  • 写出时使用body?.body(即 Response 的流式 body),见 lib/application.js#L322。

这个行为让fetch的返回值可以直接透传。以下写法来自测试用例(tests/application/respond.test.js#L687-L715):

app.use(async ctx => { const stream = new ReadableStream({ start (controller) { controller.enqueue(new TextEncoder().encode('Streaming ')); controller.enqueue(new TextEncoder().encode('response ')); controller.enqueue(new TextEncoder().encode('from fetch')); controller.close(); } }) const response = new Response(stream, { status: 200, headers: { 'Content-Type': 'text/plain' } }) ctx.body = response })

同样适用于 JSON 响应体和 Blob 响应体(tests/application/respond.test.js#L662-L685、#L717-L737)——Response上携带的status和自定义头(如X-Custom-Header)都会被原样转到客户端。

如何验证结果

仓库使用 supertest 发起真实请求并断言响应,npm test(对应 package.json 中的"test": "node --test")可运行全部用例。三个关键断言如下,也即你本地验证时应检查的点(tests/application/respond.test.js#L514-L594):

// Blob:响应体字节与 Blob 内容一致 const res = await request(app.callback()).get('/').expect(200) assert.deepStrictEqual(res.body, Buffer.from(await new Blob(['Hello']).arrayBuffer())) // Blob:HEAD 请求下响应头(content-length 取自 blob.size) return request(app.callback()) .head('/') .expect(200) .expect('content-type', 'application/octet-stream') .expect('content-length', '11') // ReadableStream:多个 chunk 拼接为完整响应体 return request(app.callback()) .get('/') .expect(200) .expect('content-type', 'application/octet-stream') .expect(Buffer.from('Hello World'))

对你自己的服务,可以用curl -i对照检查:

curl -i http://localhost:3000/blob

对照上面的测试断言判断:

  • /blob:状态码 200,响应体为Hello WorldContent-Type为创建 Blob 时指定的text/plain,未指定时测试断言的默认值是application/octet-stream
  • /stream:状态码 200,Content-Type为提前设置的text/plain(未设置时为application/octet-stream),响应体为两个 chunk 拼接后的Hello World
  • /response:状态码与Response构造时的status一致,Content-Type取自Response.headers,响应体为Hello World

Response端点还可验证状态码透传:测试用例中new Response(null, { status: 201, ... })经 Koa 响应后客户端收到 201(tests/application/respond.test.js#L619-L631)。

限制与边界

  • 版本:docs/api/response.md 中response.body=列出的合法类型只有stringBufferStreamObject || Arraynull || undefined,没有这三类 WHATWG 对象——Blob/ReadableStream/Response支持是 v3 新增,从 v2 升级时请参阅 migration-v2-to-v3.md。
  • 默认 Content-Type:三类对象在未显式设置Content-Type时都会落到application/octet-stream。需要文本、JSON 或其他 MIME 时,用ctx.type = 'text/plain'(须先于ctx.body赋值,见示例)或在对象上携带正确的头(Blob 的type选项、Response 的headers)。
  • 流式写出与错误:三类对象最终都转为 Node 可读流,经Stream.pipeline(stream, res, ...)写入;管道出错时,若应用注册了error监听器,会回调ctx.onerror(err)(lib/application.js#L325-L329)。这与 NodeStream类型的 body 走同一条路径,Streambody 的onerror与连接关闭时销毁流的行为说明见 docs/api/response.md。
  • 文档没有覆盖的写法(例如把Request对象作为 body)不要自行类推,以lib/response.jsbodysetter 实际判断的类型为准。

【免费下载链接】koaExpressive middleware for node.js using ES2017 async functions项目地址: https://gitcode.com/GitHub_Trending/ko/koa

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询