Midway 数据响应统一方案:ServerResponse 与 HttpServerResponse 实战指南

发布时间:2026/10/8 1:55:30
Midway 数据响应统一方案:ServerResponse 与 HttpServerResponse 实战指南 后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载导读从 v3.17.0 开始Midway 框架在 packages/core 中内置了ServerResponse与HttpServerResponse两套统一数据响应实现用于规范化服务端成功与失败的返回格式解决以往在ctx上扩展ok/fail方法、在中间件与错误过滤器中各写一套返回逻辑所导致的维护难题。阅读本文后你将掌握 JSON/文本/BLOB 通用响应、状态码与响应头定制、模板TPL全局覆盖与继承定制、流式与文件下载、SSE 推送以及 AI SDK 流式转发OpenAI/Anthropic/EventSource的完整落地写法。为什么需要统一数据响应在 Koa 场景下业务接口通常的处理模式是处理逻辑后返回一个结果过程中既会出现成功也会出现失败。最常见的实现是在ctx上扩展方法例如import { Controller, Get, Inject } from midwayjs/core; import { Context } from midwayjs/koa; Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async home() { try { // ... return this.ctx.ok(/*...*/); } catch (err) { return this.ctx.fail(/*...*/); } } }也有人会在 Web 中间件中统一处理成功返回、在错误过滤器中统一处理失败返回。这两种做法都面临同一个问题返回逻辑分散在多处难以统一维护格式一旦变更就需要改多个位置。为了解决这类问题框架提供了一套统一返回方案。以最常见的 JSON 返回为例通过创建HttpServerResponse实例并调用json()方法链式返回数据import { Controller, Get, Inject, HttpServerResponse } from midwayjs/core; import { Context } from midwayjs/koa; Controller(/) export class HomeController { Inject() ctx: Context; Get(/success) async home() { return new HttpServerResponse(this.ctx).success().json({ // ... }); } Get(/fail) async home2() { return new HttpServerResponse(this.ctx).fail().json({ // ... }); } }默认情况下HttpServerResponse在成功和失败场景下提供 JSON 通用包裹结构成功场景{ success: true, data: //... }失败场景{ success: false, message: //... }注意json()是数据设置方法必须在链式调用的最后一个位置调用。常用的响应格式HttpServerResponse需要传入当前请求的上下文对象ctx才能实例化const serverResponse new HttpServerResponse(this.ctx);之后以链式形式调用数据设置方法// json serverResponse.json({ a: 1, }); // text serverResponse.text(abcde); // blob serverResponse.blob(Buffer.from(hello world));除了设置数据的方法还提供了一些可组合使用的快捷方法// status serverResponse.status(200).text(abcde); // header serverResponse.header(Content-Type, text/html).text(divhello/div); // headers serverResponse.headers({ Content-Type: text/plain, Content-Length: 100 }).text(a.repeat(100));源码层面的实现细节从源码可以看到这些快捷方法的底层实现非常直接。status()写入ctx.res.statusCodeheader()/headers()写入ctx.res的响应头而json()、text()等数据方法会先自动设置对应的Content-Type响应头再调用模板方法见 packages/core/src/response/http.tsjson()默认设置Content-Type: application/jsontext()默认设置Content-Type: text/plainblob(data, mimeType?)默认设置Content-Type: application/octet-stream也支持第二个参数指定类型html()默认设置Content-Type: text/htmlHTML_TPL模板可从源码中看到见 packages/core/src/response/http.ts。链式调用的基础是success()与fail()两个状态方法它们内部只是维护isSuccess标志位默认为true并返回this以便继续调用见 packages/core/src/response/base.ts。响应模板TPL定制针对不同的数据设置方法框架提供了不同的模板TPL供用户自定义。以json()方法的模板为例其默认实现如下见 packages/core/src/response/base.tsclass ServerResponse { // ... static JSON_TPL (data: Recordany, any, isSuccess: boolean): unknown { if (isSuccess) { return { success: true, data, }; } else { return { success: false, message: data || fail, }; } }; }全局覆盖模板可以通过覆盖全局模板来达到自定义的目的HttpServerResponse.JSON_TPL (data, isSuccess) { if (isSuccess) { // ... } else { // ... } };通过继承定制独立模板如果不想影响全局默认模板可以通过继承的方式为不同场景定制不同的响应模板class CustomServerResponse extends HttpServerResponse {} CustomServerResponse.JSON_TPL (data, isSuccess) { if (isSuccess) { // ... } else { // ... } };使用时创建实例即可// ... Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async home() { return new CustomServerResponse(this.ctx).success().json({ // ... }); } }其他可覆盖的模板针对text、blob方法的模板同样可以覆盖HttpServerResponse.TEXT_TPL (data, isSuccess) { /*...*/}; HttpServerResponse.BLOB_TPL (data, isSuccess) { /*...*/};从源码看模板的调用存在一个巧妙机制数据方法通过Object.getPrototypeOf(this).constructor.JSON_TPL(...)动态获取模板这意味着它始终读取的是实例实际所属类上的静态模板因此子类覆盖模板天然生效且不会污染父类与全局见 packages/core/src/response/base.ts。同时模板调用时会把ctx作为第三个参数传入因此模板内部也可以读取上下文做更精细的判断如根据请求头或用户信息决定返回结构。数据流式响应使用内置HttpServerResponse的stream()方法可以处理流式数据返回import { Controller, Get, Inject, sleep, HttpServerResponse } from midwayjs/core; import { Context } from midwayjs/koa; Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async home() { const res new HttpServerResponse(this.ctx).stream(); setTimeout(() { for (let i 0; i 100; i) { await sleep(100); res.send(abc.repeat(100)); } res.end(); }, 1000); return res; } }通过STREAM_TPL可以修改数据的返回结构HttpServerResponse.STREAM_TPL (data) { /*...*/};注意该模板只处理成功的数据。从源码看stream()返回的是一个基于Transform的 HttpStreamResponse首次写入时会自动设置200状态码、Transfer-Encoding: chunked与Cache-Control: no-cache响应头并将 socket 超时设置为 0 以支持长连接写入的数据为字符串时直接write为对象时则 JSON 序列化后写入见 packages/core/src/response/stream.ts。文件流式响应从 v3.17.0 开始可以通过HttpServerResponse简单处理文件下载。传递一个文件路径即可默认使用application/octet-stream响应头返回import { Controller, Get, Inject, sleep, HttpServerResponse } from midwayjs/core; import { Context } from midwayjs/koa; Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async home() { const filePath join(__dirname, ../../package.json); return new HttpServerResponse(this.ctx).file(filePath); } }如需返回不同的类型可以通过第二个参数指定 MIME 类型import { Controller, Get, Inject, sleep, HttpServerResponse } from midwayjs/core; import { Context } from midwayjs/koa; Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async home() { const filePath join(__dirname, ../../package.json); return new HttpServerResponse(this.ctx).file(filePath, application/json); } }通过FILE_TPL可以修改返回结构HttpServerResponse.FILE_TPL (data: Readable, isSuccess: boolean) { /*...*/};源码实现中file()会通过createReadStream(filePath)创建可读流同时设置Content-Type默认为application/octet-stream以及Content-Disposition: attachment; filenamexxx响应头文件名取自路径的 basename见 packages/core/src/response/http.ts。SSE 响应从 v3.17.0 开始框架提供了内置的 SSEServer-Sent Events支持。SSE 的数据定义如下需要按该格式返回见 packages/core/src/interface.tsexport interface ServerSendEventMessage { data?: string | object; event?: string; id?: string; retry?: number; }通过HttpServerResponse定义一个返回实例import { Controller, Get, Inject, sleep, HttpServerResponse } from midwayjs/core; import { Context } from midwayjs/koa; Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async home() { const res new HttpServerResponse(this.ctx).sse(); // ... return res; } }可以通过send和sendEnd进行数据传递const res new HttpServerResponse(this.ctx).sse(); res.send({ data: abcde }); res.sendEnd({ data: end });调用sendEnd后请求将被关闭。也可以通过sendError发送错误const res new HttpServerResponse(this.ctx).sse(); res.sendError(new Error(test error));SSE 的底层行为从源码看sse()返回的 ServerSendEventStream 是一个基于Transform的流对象首次发送数据时会自动设置text/event-stream、Cache-Control: no-cache, no-transform、Connection: keep-alive、X-Accel-Buffering: no等 SSE 标准响应头并关闭 socket 超时、开启setNoDelay与setKeepAlive同时监听ctx.req的close事件以感知客户端断开见 packages/core/src/response/sse.ts。发送时会对每条消息进行格式化event、retry、id字段分别输出为event:、retry:、id:行data为对象时 JSON 序列化为字符串时按行拆分并统一换行符为\n最终以空行分隔帧。sendError内部会以event: error的事件帧发送错误消息sendEnd则默认发送close事件后结束事件名可通过sse({ closeEvent: xxx })选项自定义见 packages/core/src/response/sse.ts。转发 AI SDK 的 SSE 响应在一些 AI 网关场景中服务端需要负责鉴权、隐藏系统提示词、组装工具参数然后把 OpenAI、Anthropic 等 SDK 的流式返回原样转发给前端。sse()返回的对象提供了forward()方法可以把 SDK 返回的AsyncIterable转换为客户端可解析的 SSE 响应。当前内置支持openai和anthropic两种 SDK 协议格式。其他 SDK 可以使用通用的eventsource协议或者通过transform转换为自定义事件结构。安装 SDKnpm i openai anthropic-ai/sdk也可以在package.json中声明依赖{ dependencies: { openai: ^6.35.0, anthropic-ai/sdk: ^0.92.0 } }OpenAI 示例import { Controller, Get, Inject, HttpServerResponse } from midwayjs/core; import { Context } from midwayjs/koa; import OpenAI from openai; const client new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); Controller(/) export class HomeController { Inject() ctx: Context; Get(/openai) async openai() { const upstream await client.chat.completions.create({ model: gpt-4o-mini, // 替换为你要使用的模型 messages: [ { role: system, content: 你是一个有帮助的助手。, }, { role: user, content: 请介绍 Midway。, }, ], stream: true, }); const res new HttpServerResponse(this.ctx).sse(); res.forward(upstream, { protocol: openai, }); return res; } }protocol: openai会输出 OpenAI 客户端可解析的 SSE 数据帧并在上游正常结束时发送data: [DONE]。Anthropic 示例import { Controller, Get, Inject, HttpServerResponse } from midwayjs/core; import { Context } from midwayjs/koa; import Anthropic from anthropic-ai/sdk; const client new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, }); Controller(/) export class HomeController { Inject() ctx: Context; Get(/anthropic) async anthropic() { const upstream client.messages.stream({ model: claude-sonnet-4-5, // 替换为你要使用的模型 max_tokens: 2048, messages: [ { role: user, content: 请介绍 Midway。, }, ], thinking: { type: enabled, budget_tokens: 1024, }, }); const res new HttpServerResponse(this.ctx).sse(); res.forward(upstream, { protocol: anthropic, }); return res; } }protocol: anthropic会保留 Anthropic 的事件名例如message_start、content_block_delta、message_stop等前端可以继续按 Anthropic 的事件格式解析。通用 EventSource 协议如果只需要普通浏览器EventSource或自定义前端解析器可以使用默认的eventsource协议const res new HttpServerResponse(this.ctx).sse(); res.forward(upstream, { protocol: eventsource, }); return res;事件轻量处理与取消forward()支持对事件做轻量处理transform返回null表示跳过当前事件const res new HttpServerResponse(this.ctx).sse(); res.forward(upstream, { protocol: anthropic, transform: chunk { if (chunk.type ping) { return null; } return chunk; }, }); return res;当上游 SDK 调用传入了AbortController时也可以把它传给forward()。客户端断开连接后框架会调用abort()终止上游请求const abortController new AbortController(); const upstream await client.chat.completions.create({ model: gpt-4o-mini, // 替换为你要使用的模型 messages, stream: true, }, { signal: abortController.signal, }); const res new HttpServerResponse(this.ctx).sse(); res.forward(upstream, { protocol: openai, abortController, }); return res;注意forward()只负责把 SDK 流式事件转换为对应协议的 SSE 响应不会解析或改写模型返回的thinking、reasoning、工具调用等内容。如果前端需要展示这些信息请在前端按 OpenAI 或 Anthropic 的事件格式自行解析。通过SSE_TPL可以修改返回结构import { ServerSendEventMessage } from midwayjs/core; HttpServerResponse.SSE_TPL (data: ServerSendEventMessage) { /*...*/};注意这个模板只处理成功的数据不会处理sendError的情况且返回也必须是ServerSendEventMessage格式。forward 的源码级行为从源码看forward()的协议分派逻辑如下见 packages/core/src/response/sse.ts逐块遍历上游AsyncIterable先经过可选的transform处理返回null时跳过该块anthropic协议将块的type字段作为 SSE 事件名如message_start块的原始内容作为dataopenai与eventsource协议统一将块作为data输出上游正常结束时openai协议发送data: [DONE]后结束eventsource协议默认发送close事件事件名可通过closeEvent选项自定义传false可关闭该结束事件后结束转发过程中若AbortController已中止或流不可写会直接结束而不误发错误帧客户端断开时注册的abort回调会被依次触发实现上游请求的及时取消。基础数据响应 ServerResponse除了 Http 场景之外框架提供了基础的ServerResponse类用于其他场景如非 HTTP 的框架运行时。ServerResponse包含json、text、blob三种数据返回方法以及success和fail两个设置状态的方法行为与HttpServerResponse一致。其核心实现位于 packages/core/src/response/base.ts并通过 packages/core/src/response/index.ts 统一从midwayjs/core导出见 packages/core/src/index.ts。HttpServerResponse在此基础上扩展了 Http 相关的status、header、headers、file、html、redirect、sse、stream等方法见 packages/core/src/response/http.ts。通过继承、覆盖等行为可以非常简单地对响应值进行差异化处理。例如对不同的用户做返回区分// src/response/api.ts export class UserServerResponse extends HttpServerResponse {} UserServerResponse.JSON_TPL (data, isSuccess) { if (isSuccess) { return { status: 200, ...data, }; } else { return { status: 500, message: limit exceed }; } }; export class AdminServerResponse extends HttpServerResponse {} AdminServerResponse.JSON_TPL (data, isSuccess) { if (isSuccess) { return { status: 200, router: data.router, ...data }; } else { return { status: 500, message: interal error, ...data }; } };使用时按业务分支返回对应的实例import { Controller, Get, Inject, sleep, HttpServerResponse } from midwayjs/core; import { Context } from midwayjs/koa; import { UserServerResponse, AdminServerResponse } from ../response/api; Controller(/) export class HomeController { Inject() ctx: Context; Get(/) async home() { // ... if (this.ctx.user xxx) { return new AdminServerResponse(this.ctx).json({ router: /, dbInfo: { // ... }, userInfo: { role: admin, }, status: ok, }); } return new UserServerResponse(this.ctx).json({ status: ok, }); } }测试验证与参考实现框架在 packages/core/test/response/http.test.ts 中提供了完整的端到端测试测试用例直接使用 Node 原生http.createServer构造携带req/res/logger的伪上下文来实例化HttpServerResponse并配合eventsource客户端真实消费 SSE 推送覆盖了send、sendEnd、sendError、对象与字符串数据的序列化、超大 payload 传输等场景OpenAI 与 Anthropic 的forward()协议转发也通过真实 SDK 的流式响应进行了验证。总结Midway 的统一数据响应方案把成功/失败状态与数据序列化格式解耦成了两个正交的维度状态维度通过success()/fail()控制isSuccess标志格式维度通过JSON_TPL/TEXT_TPL/BLOB_TPL/STREAM_TPL/FILE_TPL/SSE_TPL/HTML_TPL等静态模板控制输出结构。无论是全局覆盖模板还是通过继承为不同用户、不同模块定制独立模板都不会互相污染而流式、文件下载、SSE 推送以及 AI SDK 流式转发等能力则让统一返回从普通 JSON 场景延伸到了实时与 AI 网关场景成为规范整个服务端返回逻辑的通用基础设施。赞分享后端微服务云原生【免费下载链接】midway A Node.js Serverless Framework for front-end/full-stack developers. Build the application for next decade. Works on AWS, Alibaba Cloud, Tencent Cloud and traditional VM/Container. Super easy integrate with React and Vue. 项目地址https://gitcode.com/gh_mirrors/mi/midway点击查看免费下载相关推荐SCI部署指南JVM、Node.js与浏览器环境的无缝集成方案SCI部署指南JVM、Node.js与浏览器环境的无缝集成方案 SCISmall Clojure Interpreter是一款高度可配置的Clojure/开发工具Midway 异常处理实战指南Catch 异常过滤器、Http 异常与统一错误响应Midway 异常处理实战指南Catch 异常过滤器、Http 异常与统一错误响应 Midway 在框架层内置了一套完整的异常处理机制从 MidwayEr后端微服务云原生终极Midway数据验证指南DTO模式与ValidateService实战应用终极Midway数据验证指南DTO模式与ValidateService实战应用 Midway是一个面向前端和全栈开发者的Node.js Serverless框后端微服务云原生上一篇G-Helper华硕笔记本性能优化的终极开源解决方案下一篇NCMconverter终极指南3步解锁网易云音乐加密格式限制创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询