news 2026/9/29 3:19:47

Midway WebSocket 框架演进全解析:从 @midwayjs/ws 的引入、中间件到守卫体系

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Midway WebSocket 框架演进全解析:从 @midwayjs/ws 的引入、中间件到守卫体系
  • 后端
  • 微服务
  • 云原生

【免费下载链接】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
点击查看免费下载

本文以 packages/ws/CHANGELOG.md 为时间主线,梳理 Midway 3.x 中@midwayjs/ws组件从诞生到成熟的完整演进路径:WebSocket 支持何时引入、连接/消息中间件与守卫(guard)如何落地、底层 ws 库为何一路升级到 v8。并结合 framework.ts、interface.ts、configuration.ts 及测试夹具,讲清楚升级鉴权、心跳检测、事件绑定、响应分发等核心机制在源码层面的实现原理。读完本文,你将掌握@midwayjs/ws的能力边界、配置参数与控制器编写范式,能够独立评估和升级你的 WebSocket 应用。

一、版本演进总览:一份 CHANGELOG 背后的功能脉络

@midwayjs/ws的 CHANGELOG 是典型的 Conventional Commits 生成日志,绝大多数条目是 "Version bump only"(纯版本号递增、无代码变更),但其中穿插的关键条目恰恰勾勒出该组件最重要的功能里程碑:

版本日期关键变更类型
2.11.02021-06-10add ws support(首次为 Midway 引入 WebSocket 框架能力)Feature
2.10.72021-04-17add event name args(为事件绑定补充事件名参数)Fix
3.0.0-beta.72021-12-03middleware with ctx.body(消息中间件支持ctx.body交互)Fix
3.0.0-beta.142022-01-04update dependency ws to v8(底层 ws 库升级至 v8 大版本)Fix
3.4.0-beta.72022-07-12support socket connection and message middleware(连接与消息双通道中间件)Feature
3.6.02022-10-10add guard(引入守卫机制)Feature
3.7.02022-10-29纯版本号更新Note

从这张表可以清晰看出@midwayjs/ws的能力演进顺序:先打通连接,再补充事件参数,随后完善中间件体系,接着升级底层依赖到 ws v8,最后引入守卫。值得注意的是,CHANGELOG 在 2.10.x 及更早版本中混入了@midwayjs/socketio的版本记录(如 "Version bump only for package @midwayjs/socketio"),这反映了 monorepo 早期两包共用发布节奏的历史,阅读时可留意区分。

当前仓库中 package.json 显示的版本为4.2.3,依赖ws@8.21.0,且声明了node >= 20的运行时要求——说明从 v3 一路走来的 ws v8 依赖策略延续至今。

二、2.11.0:WebSocket 支持是如何被引入 Midway 的

CHANGELOG 最早的重大功能条目是 2.11.0 的 "add ws support"(issue #1058)。它标志着 Midway 正式具备了原生 WebSocket 场景能力,与 koa、express、egg 等传统 Web 场景并列。

从 index.ts 的导出结构可以确认组件形态:

export { MidwayWSFramework as Framework } from './framework'; export * from './interface'; export { WebSocketConfiguration as Configuration } from './configuration';

组件由三部分组成:MidwayWSFramework(框架主体,被@Framework()装饰器标注)、WebSocketConfiguration(组件配置,声明namespace: 'webSocket')、以及对外暴露的类型定义。这意味着在业务侧只需在src/configuration.ts中通过imports引入@midwayjs/ws,框架层就会自动扫描注册的 WS 控制器并挂载 WebSocket 服务。

与 HTTP 服务器共享端口的设计

在 framework.ts 的run()方法中可以看到一个关键设计决策:WebSocket 服务并不总是独立端口,而是优先复用 HTTP 服务器:

if (!this.configurationOptions.port) { server = this.applicationContext.get(HTTP_SERVER_KEY); this.logger.info('[midway:ws] WebSocket server find shared http server and will be attach.'); } else { server = this.configurationOptions.server ?? http.createServer(); }

即:配置了port则自建 HTTP server 独立监听;未配置port则从容器中取出 koa/express 等已启动的 HTTP server,在其上监听upgrade事件完成 WebSocket 握手。这正是测试夹具 base-app/src/configuration.ts 中显式配置port: 3000的原因——独立端口便于单测环境启动。

noServer: true与手动握手

组件初始化时(applicationInitialize)强制noServer = true,不依赖 ws 库自动监听,而是自行接管握手流程。随后框架通过loadMidwayController()读取WS_CONTROLLER_KEY元数据注册的控制器并绑定connection事件,这一阶段已经能支撑基础的连接、收发、断开流程。

三、3.4.0-beta.7:连接中间件与消息中间件的双层体系

如果说 2.11.0 打通了"能用",3.4.0-beta.7 的 "support socket connection and message middleware"(issue #1984)则让@midwayjs/ws进入"好用"阶段。它引入了两条独立的中间件通道:

  • 连接中间件(connection middleware):作用于连接建立阶段,在事件处理器运行前执行,适合做连接级鉴权、日志、初始化;
  • 消息中间件(message middleware):作用于每条消息处理链路,与 koa 风格的洋葱模型一致,适合做消息解析、校验、透传。

连接中间件的注册与执行

框架在 applicationInitialize 中向应用对象注入了三个扩展方法:useConnectionMiddleware、getConnectionMiddleware、onWebSocketUpgrade。其中连接中间件通过独立的connectionMiddlewareManager管理(framework.ts):

public useConnectionMiddleware(middleware) { this.connectionMiddlewareManager.insertLast(middleware); }

在每次新连接到达时(addNamespace 内),框架将"全局连接中间件 + 控制器级连接中间件"合并,通过middlewareService.compose组装后执行,并且整个连接回调被包在traceService.runWithEntrySpan('ws.connect ...')中,自动生成ws.connect链路追踪 Span。

消息中间件与事件绑定

消息处理走的是另一条链路(framework.ts):框架根据控制器上@OnWSMessage('eventName')元数据,用socket.on(messageEventName, handler)绑定监听,处理器内部再组合控制器级中间件 + 事件级中间件 + 目标方法,同样以compose方式串行执行,并用ws.message ${eventName}作为追踪 Span 名。

值得注意的一个兼容细节:处理器根据消息最后一个参数是否为函数,自动区分ack 回调(最后参数是函数则直接回传结果,供客户端做请求-响应式通信)与emit 广播(否则走响应分发逻辑)。这与 2.10.7 中 "add event name args" 修复一脉相承——事件处理器可以拿到完整的事件名参数。

四、3.6.0:守卫(Guard)如何参与 WebSocket 调用链

3.6.0 的 "add guard"(issue #2345)把 Midway 3.x 的守卫机制带入了 WebSocket 场景。在连接事件处理中(framework.ts),事件处理器被组装时会在目标方法前插入守卫检查:

const isPassed = await this.app.getFramework().runGuard(ctx, target, wsEventInfo.propertyName); if (!isPassed) { throw new MidwayInvokeForbiddenError(wsEventInfo.propertyName, target); }

守卫通过runGuard统一执行,失败时抛出MidwayInvokeForbiddenError中断调用。这使鉴权逻辑可以从中间件中剥离,以声明式守卫的形式复用同一套规则,与 HTTP 场景保持一致的心智模型。

升级前鉴权:握手阶段的另一道防线

除了连接事件内的守卫,框架还提供握手阶段的onWebSocketUpgrade钩子(framework.ts 与接口定义 interface.ts):

export type UpgradeAuthHandler = ( request: IncomingMessage, socket: any, head: Buffer ) => Promise<boolean>;

在server.on('upgrade')回调中(framework.ts),若设置了该 handler,则先执行鉴权:返回false或抛出异常时直接socket.destroy()拒绝握手;通过后才调用this.app.handleUpgrade完成协议升级并发出connection事件。这样便形成了"握手鉴权(拦截未授权连接)+ 守卫(控制事件执行)"的双层安全模型。

五、底层依赖演进:ws 库从 v7 到 v8 的升级之路

CHANGELOG 中占比最大的实质变更类型是依赖升级,全部指向同一个库——ws:

版本升级目标
3.0.0-beta.14ws v8(大版本升级,#1488)
3.0.1ws v8.4.2
3.0.4ws v8.5.0
3.4.0-beta.10ws v8.8.1
3.5.3ws v8.9.0

@midwayjs/ws直接构建在ws库之上,WebSocket.Server、socket.ping/pong、terminate、readyState等均为底层 API(见 framework.ts 的import * as WebSocket from 'ws')。升级到 v8 意味着框架可以获得更严格的消息处理、更好的性能与更完整的类型定义。当前仓库 package.json 已将ws锁定在8.21.0,并配套@types/ws@8.5.14提供 TypeScript 类型支持——在阅读历史版本时,若遇到与底层握手或心跳相关的问题,可优先对照当时代入的 ws 版本行为差异。

六、源码级原理:@midwayjs/ws的核心运行时机制

综合 framework.ts 全文,可以将组件运行时拆解为五个环节:

  1. 初始化(applicationInitialize):创建WebSocket.Server({ noServer: true }),注入三个扩展方法,扫描 WS 控制器;
  2. 握手(run()内server.on('upgrade')):可选升级鉴权 →handleUpgrade→ 触发connection;
  3. 连接上下文(addNamespace的 connection 回调):创建匿名请求上下文,注册socket、request、app到请求级容器(socket.requestContext),使控制器内@Inject() ctx可注入IMidwayWSContext;
  4. 事件绑定:按WS_EVENT_KEY元数据遍历,将@OnWSConnection/@OnWSMessage/@OnWSDisConnection分别映射到connection、自定义消息事件、close;
  5. 响应分发(bindSocketResponse):处理器返回值根据事件元数据决定去向——EMIT回发当前连接、BROADCAST遍历app.clients中所有readyState === WebSocket.OPEN的连接广播,均通过runWithExitSpan('ws.emit ...')打点追踪;没有@WSBroadcast/@WSEmit装饰器时,默认直接回发本连接。

响应格式统一经过formatResult(framework.ts):对象类型自动JSON.stringify,其余原样发送——这正是测试中客户端收到{"name":"harry","result":6}JSON 字符串的原因。

七、配置项详解:来自 configuration.ts 与 interface.ts 的权威参数

组件默认配置定义在 configuration.ts:

@Configuration({ namespace: 'webSocket', importConfigs: [{ default: { webSocket: { enableServerHeartbeatCheck: false, serverHeartbeatInterval: 30000, }, midwayLogger: { clients: { wsLogger: { fileLogName: 'midway-ws.log' }, }, }, }, }], }) export class WebSocketConfiguration {}

完整可配置项见 interface.ts 的IMidwayWSConfigurationOptions:

配置项默认值说明
enableServerHeartbeatCheckfalse是否启用服务端心跳检测
serverHeartbeatInterval30000心跳检测间隔(毫秒)
port无指定端口时独立监听;缺省时复用 HTTP server
server无自定义 HTTP server 实例
pubClient/subClient无预留的发布/订阅客户端(广播扩展)
其余ws.ServerOptions—透传给底层WebSocket.Server(如maxPayload等)

心跳机制在 startHeartBeat 中实现:周期性遍历所有连接,若某连接isAlive === false则terminate()清理,否则置为false并发送ping();客户端回pong时(connection 回调内socket.on('pong'))恢复isAlive = true。测试夹具 base-app-heartbeat/src/configuration.ts 将间隔设为1000ms 以加速验证。

八、实战:用 @midwayjs/ws 编写一个 WS 控制器

以仓库测试夹具 api.ts 为例,一个完整的 WS 控制器长这样:

import { Inject, OnWSConnection, OnWSDisConnection, OnWSMessage, Provide, WSController } from '@midwayjs/core'; import { IMidwayWSContext } from '@midwayjs/ws'; @Provide() @WSController() export class APIController { @Inject() ctx: IMidwayWSContext; @Inject() userService: UserService; @OnWSConnection() init(socket, request) { // 连接建立时触发,可拿到 socket 与 HTTP 握手请求 } @OnWSMessage('message') async gotMyMessage(data) { // 收到 'message' 事件,返回值自动回发(可配合 @WSEmit / @WSBroadcast 控制分发) return { name: 'harry', result: parseInt(data) + 5 }; } @OnWSDisConnection() disconnect(id: number) { // 连接断开时触发 } }

配套的 index.test.ts 验证了三类行为:客户端发送1收到{"name":"harry","result":6}(消息回发);base-app-broadcast夹具下两个客户端同时收到广播(total === 12,即 6+6 双端累加);base-app-filter夹具验证中间件可将消息拦截改写为packet error;心跳测试则确认客户端能收到ping帧。这些测试全部通过@midwayjs/mock的createWebSocketClient与createLightApp在纯 Node 环境运行,不需要真实浏览器。

九、小结:从 CHANGELOG 读懂组件的成熟路径

回看 CHANGELOG.md,@midwayjs/ws的演进是一套典型的"基础设施逐步完善"路径:先接入(2.11.0)→ 补齐事件参数(2.10.7)→ 升级底层 ws v8(3.0.0-beta.14)→ 完善中间件(3.4.0-beta.7)→ 引入守卫(3.6.0),期间伴随多次依赖修补与 monorepo 版本同步。对于正在使用或计划引入@midwayjs/ws的开发者,本文梳理的配置参数、控制器范式、双层中间件与双层鉴权模型,可直接映射到 framework.ts 与 interface.ts 的源码中进行二次确认,做到"文档有据、源码可查"。

  • 后端
  • 微服务
  • 云原生

【免费下载链接】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
点击查看免费下载
上一篇:TiXL 交互 Gizmo 指南:在输出视口中直接拖拽操控三维对象
下一篇:FF14钓鱼计时器「渔人的直感」使用教程:从错过鱼王到轻松收杆

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 3:18:05

Winform QQ登录界面源码实战:从UI拆解到异步登录与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 3:17:39

DeepSeek接入VScode和IDEA:TaoToken统一Key配置与验证指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 3:17:26

PCM+风冷混合热管理的最优解:占包重<10%与160Wh/kg的权衡方案

TL;DR印度也是一个巨大的新能源汽车市场&#xff0c;今天看一下印度学者都在研究什么&#xff1f;印度理工学院孟买分校&#xff08;IIT Bombay&#xff09;Thakur、Amale与Kumar团队于2026年8月在ASME《Journal of Thermal Science and Engineering Applications》发表的研究&…

作者头像 李华
网站建设 2026/9/29 3:17:23

Spring AI基础入门实战:用TaoToken统一Key打通ChatClient配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华