
后端微服务云原生【免费下载链接】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点击查看免费下载导读本文基于 Midway 官方扩展文档site/docs/extensions/ws.md系统讲解如何通过midwayjs/ws组件在 Midway 应用中快速搭建基于 ws 的 WebSocket 服务。WebSocket 协议允许客户端通常是浏览器与服务端保持持久化连接特别适合游戏、聊天室、实时推送等需要双向实时通信的场景。读完本文你将掌握从依赖安装、组件开启、WSController服务定义、消息收发与广播、WebSocket Server 实例获取到心跳检查、握手鉴权、本地测试以及完整配置项的全部实操能力并结合仓库源码理解其底层实现原理。能力与服务场景midwayjs/ws是 Midway 对 Node 端 ws 模块的官方封装让开发者可以用装饰器风格编写 WebSocket 服务而无须直接操作底层ws的 API。其相关服务能力如下描述支持情况可用于标准项目✅可用于 Serverless❌可用于一体化✅包含独立主框架❌包含独立日志❌从源码结构看组件提供MidwayWSFramework对应Framework导出与WebSocketConfiguration对应Configuration导出见 packages/ws/src/index.ts命名空间为webSocket因此它不能独立承担主框架职责但既可以作为独立 WebSocket 服务启动也可以附加在其他主框架如midwayjs/koa之下复用 HTTP 服务。安装依赖在现有项目中安装 WebSocket 依赖$ npm i midwayjs/ws4 --save或者在package.json中增加如下依赖后重新安装{ dependencies: { midwayjs/ws: ^4.0.0 // ... } }组件还依赖midwayjs/core、ws等基础模块项目脚手架通常会一并安装。组件在启动时会注册独立的日志客户端wsLogger日志文件名为midway-ws.log见 packages/ws/src/configuration.ts。开启组件作为独立服务启动midwayjs/ws可以独立提供 WebSocket 服务在src/configuration.ts中将其导入即可// src/configuration.ts import { Configuration } from midwayjs/core; import * as ws from midwayjs/ws; Configuration({ imports: [ws], // ... }) export class MainConfiguration { async onReady() { // ... } }此时框架会使用webSocket配置中指定的端口默认7001自行创建 HTTP 服务并监听upgrade事件。附加在其他主框架下也可以附加在midwayjs/koa等其他主框架下WebSocket 服务与 Web 框架共享同一个 HTTP 服务与端口// src/configuration.ts import { Configuration } from midwayjs/core; import * as koa from midwayjs/koa; import * as ws from midwayjs/ws; Configuration({ imports: [koa, ws], // ... }) export class MainConfiguration { async onReady() { // ... } }在源码中MidwayWSFramework.run()会先判断配置是否携带port未配置端口时从容器中取出HTTP_SERVER_KEY对应的共享 HTTP Server 并挂载 WebSocket 升级处理配置了端口时才创建独立 HTTP 服务并监听该端口见 packages/ws/src/framework.ts。这正是可独立启动、也可与 Web 框架共端口两种模式的底层来源。目录结构WebSocket 项目的基础目录结构与传统 Midway 应用类似只是新增了socket目录用于存放 WebSocket 业务服务代码. ├── package.json ├── src │ ├── configuration.ts ## 入口配置文件 │ ├── interface.ts │ └── socket ## ws 服务的文件 │ └── hello.controller.ts ├── test ├── bootstrap.js ## 服务启动入口 └── tsconfig.json仓库测试夹具同样遵循这一约定例如 packages/ws/test/fixtures/base-app/src/socket/api.ts 就是放在socket目录下的控制器。提供 Socket 服务WSController 与 OnWSConnectionMidway 通过WSController装饰器定义 WebSocket 服务控制器import { WSController } from midwayjs/core; WSController() export class HelloSocketController { // ... }当有客户端连接时会触发connection事件。使用OnWSConnection()修饰一个方法每个客户端第一次连接服务时该方法将被自动调用import { WSController, OnWSConnection, Inject } from midwayjs/core; import { Context } from midwayjs/ws; import * as http from http; WSController() export class HelloSocketController { Inject() ctx: Context; OnWSConnection() async onConnectionMethod(socket: Context, request: http.IncomingMessage) { console.log(namespace / got a connection ${this.ctx.readyState}); } }:::info 这里的ctx等价于 WebSocket 实例本身。 :::从实现上看OnWSConnection装饰器以及消息、广播、断连等会将事件元数据注册到控制器类上WS_EVENT_KEY框架启动时通过DecoratorManager.listModule(WS_CONTROLLER_KEY)扫描控制器并对每个连接触发connection事件后执行对应的方法见 packages/ws/src/framework.ts 与 packages/core/src/decorator/ws/webSocketEvent.ts。需要特别说明的是当前实现中 WebSocket 只支持单个命名空间ws just one namespace框架只取第一个扫描到的控制器注册连接处理因此业务上通常只需维护一个socket控制器。消息与响应WebSocket 通过事件监听的方式获取数据。OnWSMessage()用于格式化接收到的事件每次客户端发送事件被修饰的方法都会被执行import { WSController, OnWSMessage, Inject } from midwayjs/core; import { Context } from midwayjs/ws; WSController() export class HelloSocketController { Inject() ctx: Context; OnWSMessage(message) async gotMessage(data) { return { name: harry, result: parseInt(data) 5 }; } }方法的返回值会被自动回送给发起请求的客户端当返回值为对象时框架会通过JSON.stringify序列化后发送源码中的formatResult处理见 packages/ws/src/framework.ts这也是测试中客户端拿到的是 JSON 字符串的原因。广播WSBroadCast通过WSBroadCast()装饰器可以将方法返回值发送到所有已连接的客户端import { WSController, OnWSConnection, Inject } from midwayjs/core; import { Context } from midwayjs/ws; WSController() export class HelloSocketController { Inject() ctx: Context; OnWSMessage(message) WSBroadCast() async gotMyMessage(data) { return { name: harry, result: parseInt(data) 5 }; } OnWSDisConnection() async disconnect(id: number) { console.log(disconnect id); } }通过OnWSDisConnection装饰器可以在客户端断连时做一些额外处理如清理房间成员、释放资源。测试夹具 packages/ws/test/fixtures/base-app-broadcast/ 展示了广播场景两个客户端同时监听message其中一个发送消息后两个客户端都会收到返回结果见 packages/ws/test/index.test.ts 的should test websocket broadcast用例。事件装饰器的更多能力从 packages/core/src/decorator/ws/webSocketEvent.ts 可以看到完整的事件类型体系WSEventTypeEnumON_CONNECTION连接建立OnWSConnectionON_DISCONNECTION连接断开OnWSDisConnectionON_MESSAGE接收消息OnWSMessageON_SOCKET_ERRORsocket 错误EMIT定向发送WSEmit(messageName, roomName)BROADCAST广播WSBroadCast(messageName, roomName)其中OnWSMessage(eventName, eventOptions)与OnWSConnection(eventOptions)还支持传入事件级中间件middleware数组用于在单个事件处理前执行过滤或预处理逻辑。同时保留了几个已废弃别名OnMessage、Emit、OnDisConnection、OnConnection新代码应统一使用OnWS*系列。框架在响应时还会检查方法参数的最后一个是否为函数若是则按ack 回调处理将结果直接传给该回调否则按 emit/广播逻辑发送见 packages/ws/src/framework.ts。WebSocket Server 实例组件提供的 App 即为 WebSocket Server 实例本身可以通过App(webSocket)注入获取import { Controller, App } from midwayjs/core; import { Application } from midwayjs/ws; Controller() export class HomeController { App(webSocket) wsApp: Application; }之后即可在任意 Controller 或 Service 中操作底层 Server例如遍历所有客户端广播消息import { Controller, App } from midwayjs/core; import { Application } from midwayjs/ws; Controller() export class HomeController { App(webSocket) wsApp: Application; async invoke() { this.wsApp.clients.forEach(ws { // ws.send(something); }); } }Application类型IMidwayWSApplication是 Midway 应用接口与WebSocket.Server的交叉类型见 packages/ws/src/interface.ts因此既具备 Midway 的依赖注入、中间件能力也拥有ws原生的clients、handleUpgrade、broadcast等属性与方法。连接与事件级中间件与 HTTP 框架类似WebSocket 也支持中间件机制。通过useConnectionMiddleware注册的连接中间件会在每次新连接建立、事件处理之前执行import { Framework } from midwayjs/ws; // 在 configuration 中注入 Framework 后调用 this.wsFramework.useConnectionMiddleware(async (ctx, next) { // 连接级处理如统计连接数、打日志 await next(); });仓库测试夹具 packages/ws/test/fixtures/base-app-filter/ 演示了连接中间件的过滤效果客户端发送消息后收到的是中间件拦截返回的packet error而非正常业务结果见 packages/ws/test/index.test.ts 的should test create socket and with filter用例。此外OnWSMessage、OnWSConnection装饰器支持的事件级middleware配置会在单个事件触发时叠加执行见 packages/ws/src/framework.ts。心跳检查服务器和客户端之间的连接有时会中断而双方都无从感知。可以通过开启enableServerHeartbeatCheck配置由服务端主动探测并清理失效连接// src/config/config.default export default { // ... webSocket: { enableServerHeartbeatCheck: true, }, }默认检查间隔为30 * 1000毫秒可通过serverHeartbeatInterval修改单位毫秒// src/config/config.default export default { // ... webSocket: { serverHeartbeatInterval: 30000, }, }该配置生效后服务端会每隔serverHeartbeatInterval毫秒向所有客户端发送ping包若客户端在下一个检查周期内没有返回通过pong置位isAlive该连接将被自动terminate。对应实现位于 packages/ws/src/framework.ts 的startHeartBeat()方法遍历app.clients对isAlive false的 socket 直接terminate()其余 socket 先置isAlive false再ping()等待pong事件回调置回true。同时connection事件处理中会注册socket.on(pong, ...)回调来恢复存活标记。组件默认配置enableServerHeartbeatCheck: false、serverHeartbeatInterval: 30000见 packages/ws/src/configuration.ts。客户端如果希望感知服务端状态可以监听ping消息实现自己的心跳逻辑import WebSocket from ws; function heartbeat() { clearTimeout(this.pingTimeout); // 每次接收 ping 之后延迟等待如果下一次未拿到服务端 ping 消息则认为出现问题 this.pingTimeout setTimeout(() { // 重连或者中止 }, 30000 1000); } const client new WebSocket(wss://websocket-echo.com/); // ... client.on(ping, heartbeat);测试用例should test heartbeat timeout and terminate见 packages/ws/test/index.test.ts验证了客户端terminate后服务端的clients数量最终收敛为 0说明失效连接被正确清理。鉴权在 WebSocket 连接建立握手之前往往需要对客户端进行身份验证。从v3.20.9开始Midway 提供onWebSocketUpgrade方法用于在 WebSocket 握手前完成鉴权。设置鉴权处理器在应用启动阶段onReady注入Framework并注册鉴权处理器import { Configuration, Inject } from midwayjs/core; import { Framework } from midwayjs/ws; Configuration() export class WSConfiguration { Inject() wsFramework: Framework; async onReady() { // 设置升级前鉴权处理器 this.wsFramework.onWebSocketUpgrade(async (request, socket, head) { // 从 URL 参数中获取 token const url new URL(request.url, http://${request.headers.host}); const token url.searchParams.get(token); // 验证 token if (token valid-token) { return true; // 允许连接 } return false; // 拒绝连接 }); } }从源码实现看packages/ws/src/framework.tsrun()中监听 HTTP Server 的upgrade事件若注册了鉴权处理器会先异步执行它返回false或抛出异常时会记录告警/错误日志并直接socket.destroy()拒绝连接通过后才调用app.handleUpgrade完成 WebSocket 升级并派发connection事件。同时支持传入null来移除鉴权处理器。鉴权处理器参数鉴权处理器接收三个参数requestHTTP 请求对象http.IncomingMessage包含 URL、headers 等信息socket原始 socket 对象headWebSocket 握手的头部数据Buffer处理器需要返回一个Promisebooleantrue允许 WebSocket 连接false拒绝 WebSocket 连接对应的类型定义为UpgradeAuthHandler见 packages/ws/src/interface.ts组件还将其挂载为应用方法app.onWebSocketUpgrade(handler)便于在应用实例层面调用。获取鉴权信息可以从多个来源获取鉴权信息URL 参数this.wsFramework.onWebSocketUpgrade(async (request, socket, head) { const url new URL(request.url, http://${request.headers.host}); const token url.searchParams.get(token); const userId url.searchParams.get(userId); // 验证逻辑 return await this.validateToken(token, userId); });请求头this.wsFramework.onWebSocketUpgrade(async (request, socket, head) { const authorization request.headers.authorization; if (!authorization) { return false; } const token authorization.replace(Bearer , ); return await this.validateToken(token); });Cookiethis.wsFramework.onWebSocketUpgrade(async (request, socket, head) { const cookie request.headers.cookie; if (!cookie) { return false; } // 解析 cookie 获取 session 信息 const sessionId this.parseCookie(cookie).sessionId; return await this.validateSession(sessionId); });测试夹具 packages/ws/test/fixtures/base-app-upgrade-auth/ 与用例should test onWebSocketUpgrade authentication见 packages/ws/test/index.test.ts验证了完整链路无 token 或 token 无效的连接被拒绝测试工具testConnectionRejected通过监听open/error/close与超时判断拒绝结果携带valid-token的连接可以正常收发消息。本地测试配置测试端口ws 框架可以独立启动依附于默认的 HTTP 服务也可以与其他 Midway 框架一起启动。作为独立框架启动时需要显式指定端口// src/config/config.default export default { // ... webSocket: { port: 3000, }, }作为副框架启动时例如与 koa/http 共用由于 HTTP 框架在单测时未指定端口supertest 自动生成端口WebSocket 无法确定监听地址因此可以仅在测试环境为 WebSocket 显式指定一个端口// src/config/config.unittest export default { // ... koa: { port: null, }, webSocket: { port: 3000, }, }:::tip1、这里的端口仅为 WebSocket 服务在测试时启动的端口2、koa 中的端口为 null即意味着在测试环境下不配置端口不会启动 http 服务:::测试代码和其他 Midway 测试方法一样使用createApp启动项目import { createApp, close } from midwayjs/mock // 这里使用的 Framework 定义以主框架为准 import { Framework } from midwayjs/koa; describe(/test/index.test.ts, () { it(should create app and test webSocket, async () { const app await createAppFramework(); //... await close(app); }); });测试客户端可以直接使用ws包编写客户端测试也可以使用 Midway 基于ws封装的createWebSocketClient测试客户端其实现会在连接open后 resolve 出客户端实例见 packages/mock/src/client/ws.client.ts。Promise 回调写法import { createApp, close, createWebSocketClient } from midwayjs/mock; import { sleep } from midwayjs/core; // ... 省略 describe it(should test create websocket app, async () { // 创建一个服务 const app await createAppFramework(); // 创建一个客户端 const client await createWebSocketClient(ws://localhost:3000); const result await new Promise(resolve { client.on(message, (data) { // xxxx resolve(data); }); // 发送事件 client.send(1); }); // 判断结果 expect(JSON.parse(result)).toEqual({ name: harry, result: 6, }); await sleep(1000); // 关闭客户端 await client.close(); // 关闭服务端 await close(app); });使用 Node 自带的events模块的once方法改写后代码更简洁import { sleep } from midwayjs/core; import { once } from events; import { createApp, close, createWebSocketClient } from midwayjs/mock; // ... 省略 describe it(should test create websocket app, async () { // 创建一个服务 const app await createAppFramework(process.cwd()); // 创建一个客户端 const client await createWebSocketClient(ws://localhost:3000); // 发送事件 client.send(1); // 用事件的 promise 写法监听 let gotEvent once(client, message); // 等待返回 let [data] await gotEvent; // 判断结果 expect(JSON.parse(data)).toEqual({ name: harry, result: 6, }); await sleep(1000); // 关闭客户端 await client.close(); // 关闭服务端 await close(app); });两种写法效果相同按自己习惯选择即可。仓库测试 packages/ws/test/index.test.ts 正是采用第二种写法发送1、2分别得到{ result: 6 }、{ result: 7 }完整覆盖了消息收发、广播、心跳、鉴权与链路追踪entry/exit span等场景。配置总览默认配置midwayjs/ws的默认配置样例如下// src/config/config.default export default { // ... webSocket: { port: 7001, }, }当midwayjs/ws与midwayjs/web、midwayjs/koa、midwayjs/express同时启用时可以复用 Web 框架端口此时不要再给webSocket配置port// src/config/config.default export default { // ... koa: { port: 7001, } webSocket: { // 这里不配置即可 }, }配置属性说明属性类型描述portnumber可选如果传递了该端口ws 内部会创建一个该端口的 HTTP 服务。如果希望和 midway 其他的 web 框架配合使用请不要传递该参数。serverhttpServer可选当传递 port 时可以指定一个已经存在的 webServer从配置类型IMidwayWSConfigurationOptions见 packages/ws/src/interface.ts可以看到组件还支持更多选项属性类型默认值描述enableServerHeartbeatCheckbooleanfalse是否开启服务端心跳检查serverHeartbeatIntervalnumber30000心跳检查间隔毫秒pubClient / subClientany-发布/订阅客户端可用于跨实例广播等场景预留其余选项PartialWebSocket.ServerOptions-透传给ws原生WebSocket.Server的配置如maxPayload、perMessageDeflate等组件启动时会将noServer强制置为true配合 HTTP Server 的upgrade事件处理 WebSocket 升级因此原生ws的port/server参数由 Midway 框架统一接管见 packages/ws/src/framework.ts。更多的启动选项可以继续参考 ws 官方文档。小结通过midwayjs/ws开发者可以用完全面向 Midway 的声明式风格快速构建 WebSocket 服务WSController定义服务、OnWSConnection/OnWSMessage/WSBroadCast/OnWSDisConnection处理连接生命周期与消息收发、App(webSocket)获取底层 Server 实例、onWebSocketUpgrade实现握手前鉴权、心跳检查保证连接质量再配合midwayjs/mock的createWebSocketClient完成本地联调。若需深入源码可以继续阅读 packages/ws/src/framework.ts框架核心实现、packages/ws/src/interface.ts类型定义与 packages/core/src/decorator/ws/webSocketEvent.ts事件装饰器体系并结合 packages/ws/test/index.test.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点击查看免费下载相关推荐Midway WebSocket 框架演进全解析从 midwayjs/ws 的引入、中间件到守卫体系Midway WebSocket 框架演进全解析从 midwayjs/ws 的引入、中间件到守卫体系 本文以 packages/ws/CHANGELOG.m后端微服务云原生Midway 接入 MikroORM v7midwayjs/mikro7 独立组件设计与实践指南Midway 接入 MikroORM v7midwayjs/mikro7 独立组件设计与实践指南 midwayjs/mikro7 是 Midway 专为后端微服务云原生Midway 接入 MikroORM v7midwayjs/mikro7 独立组件实战指南Midway 接入 MikroORM v7 midwayjs/mikro7 独立组件实战指南 midwayjs/mikro7 是 Midway 为 Mik后端微服务云原生上一篇Opsweekly与Fitbit/Jawbone UP集成睡眠跟踪的完整实现指南下一篇SonarQube容器镜像安全扫描5步实现漏洞检测与修复创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考