- 后端
- 微服务
- 云原生
【免费下载链接】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. 🌈
本文以 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.0 | 2021-06-10 | add ws support(首次为 Midway 引入 WebSocket 框架能力) | Feature |
| 2.10.7 | 2021-04-17 | add event name args(为事件绑定补充事件名参数) | Fix |
| 3.0.0-beta.7 | 2021-12-03 | middleware with ctx.body(消息中间件支持ctx.body交互) | Fix |
| 3.0.0-beta.14 | 2022-01-04 | update dependency ws to v8(底层 ws 库升级至 v8 大版本) | Fix |
| 3.4.0-beta.7 | 2022-07-12 | support socket connection and message middleware(连接与消息双通道中间件) | Feature |
| 3.6.0 | 2022-10-10 | add guard(引入守卫机制) | Feature |
| 3.7.0 | 2022-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.14 | ws v8(大版本升级,#1488) |
| 3.0.1 | ws v8.4.2 |
| 3.0.4 | ws v8.5.0 |
| 3.4.0-beta.10 | ws v8.8.1 |
| 3.5.3 | ws 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 全文,可以将组件运行时拆解为五个环节:
- 初始化(applicationInitialize):创建
WebSocket.Server({ noServer: true }),注入三个扩展方法,扫描 WS 控制器; - 握手(
run()内server.on('upgrade')):可选升级鉴权 →handleUpgrade→ 触发connection; - 连接上下文(
addNamespace的 connection 回调):创建匿名请求上下文,注册socket、request、app到请求级容器(socket.requestContext),使控制器内@Inject() ctx可注入IMidwayWSContext; - 事件绑定:按
WS_EVENT_KEY元数据遍历,将@OnWSConnection/@OnWSMessage/@OnWSDisConnection分别映射到connection、自定义消息事件、close; - 响应分发(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:
| 配置项 | 默认值 | 说明 |
|---|---|---|
enableServerHeartbeatCheck | false | 是否启用服务端心跳检测 |
serverHeartbeatInterval | 30000 | 心跳检测间隔(毫秒) |
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. 🌈
相关推荐
Midway 安全组件 @midwayjs/security 全解析:从演进历史到配置实战
Midway 安全组件 @midwayjs/security 全解析:从演进历史到配置实战 @midwayjs/security 是 Midway 框架内置的通
后端微服务云原生Midway gRPC 组件能力演进全解析:从框架引入到 Stream API 与 Guard 支持
Midway gRPC 组件能力演进全解析:从框架引入到 Stream API 与 Guard 支持 导读 本文以 packages/grpc/CHANGELO
后端微服务云原生Midway 核心包演进全解析:从 @midwayjs/core 的变更日志看 IoC 容器与 Web 框架的架构变迁
Midway 核心包演进全解析:从 @midwayjs/core 的变更日志看 IoC 容器与 Web 框架的架构变迁 导读 packages/core/CHA
后端微服务云原生
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考