Skip to content

Feat/websocket controller#462

Merged
akitaSummer merged 9 commits into
masterfrom
feat/websocket-controller
Jul 13, 2026
Merged

Feat/websocket controller#462
akitaSummer merged 9 commits into
masterfrom
feat/websocket-controller

Conversation

@akitaSummer

@akitaSummer akitaSummer commented Jul 10, 2026

Copy link
Copy Markdown
Contributor
Checklist
  • npm test passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)
Description of change

Summary by CodeRabbit

  • New Features
    • Added WebSocket and WebSocket Fetch controllers with route, host, middleware, priority, and timeout configuration.
    • Added lifecycle handlers for connection, open, message, error, and close events.
    • Added WebSocket parameter access for sockets, streams, request data, headers, query values, and close details.
    • Added streaming support, route matching, path parameters, and standalone service-worker WebSocket handling.
  • Bug Fixes
    • Improved handling of malformed, unmatched, timed-out, and failed WebSocket connections with appropriate errors and close codes.
  • Tests
    • Added comprehensive coverage for routing, streaming, concurrency, cleanup, and cluster behavior.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces WebSocket and WebSocket-fetch controller support across the tegg framework: new decorators, types, and metadata builders in controller-decorator, a new core/websocket-runtime package for dispatch and session handling, upgrade routing in the plugin/controller app, and standalone service-worker WebSocket support, along with extensive fixtures and integration tests.

Changes

WebSocket controller support

Layer / File(s) Summary
WebSocket contracts, decorators, and info util
core/types/controller-decorator/*, core/controller-decorator/src/decorator/websocket*/*, core/controller-decorator/src/util/WebSocketInfoUtil.ts, core/controller-decorator/index.ts
Adds ControllerType/MethodType WebSocket members, WebSocketParamType/WebSocketFetchMethodType enums, context/param interfaces, controller/method/param decorators for WebSocket and WebSocket-fetch, WebSocketInfoUtil metadata storage with HTTP compatibility fallbacks, and public re-exports.
Metadata models and builders
core/controller-decorator/src/model/WebSocket*.ts, core/controller-decorator/src/impl/websocket*/*MetaBuilder.ts, core/controller-decorator/test/websocket/WebSocketMeta.test.ts
Defines WebSocketControllerMeta/WebSocketFetchControllerMeta/WebSocketMethodMeta/WebSocketFetchMethodMeta and parameter meta classes, builds and validates controller/method metadata (paths, priority, lifecycle uniqueness, param-type compatibility), and tests validation errors and mappings.
WebSocket runtime engine
core/websocket-runtime/*
New @eggjs/tegg-websocket-runtime package providing WebSocketControllerRuntime for method dispatch/streaming, WebSocketFetchSession/WebSocketEventStream for ordered event/backpressure handling, and WebSocketRouteRegistry for route creation/matching, with unit tests and build config.
Plugin upgrade routing and dispatch
plugin/controller/app.ts, plugin/controller/lib/impl/websocket/*, plugin/controller/test/fixtures/..., plugin/controller/test/websocket/*.test.ts
Registers WebSocket controller types, adds WebSocketControllerRegister for HTTP upgrade matching/dispatch via ws, extends Egg context, wires app lifecycle hooks, adds fixture controllers/endpoints, and adds single-process and cluster end-to-end WebSocket tests.
Standalone service-worker WebSocket flow
core/test-util/StandaloneTestUtil.ts, standalone/service-worker/src/websocket/*, standalone/service-worker/test/...
Extends the standalone test server to support upgrade events, adds ServiceWorkerWebSocketContext, WebSocketControllerRegister, and WebSocketEventHandler for standalone upgrade routing/dispatch, wires app lifecycle, adds fixture controllers/SSE endpoint, and adds integration tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • eggjs/tegg#436: Introduced StandaloneTestUtil for standalone HTTP/fetch testing, which this PR directly extends to also support WebSocket upgrade listeners.

Suggested reviewers: gxkl, killagu

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding WebSocket controller support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/websocket-controller

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchController.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchMethod.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

core/controller-decorator/src/decorator/websocket/WebSocketController.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 35 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jul 10, 2026

Copy link
Copy Markdown

Dependency limit exceeded — report not shown.

This pull request scan exceeded the 10,000-dependency limit applied to this scan, so the results are incomplete and may be inaccurate. To avoid reporting false positives, Socket has not posted a report.

Upgrade your plan to raise the dependency limit and get complete reports, or view the partial scan in the dashboard.

Socket is always free for open source. If this is a non-commercial open source project, contact us to request a free Team account.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces comprehensive WebSocket and WebSocket Fetch controller support to the Egg/Tegg framework, including new decorators, metadata builders, and runtime registers for both standard Egg applications and standalone service workers. The review feedback highlights critical areas for improvement: eagerly rejecting unmatched upgrade requests with a 404 in the WebSocket register blocks other plugins and should be avoided; stream handling in both standard and standalone registers suffers from event listener leaks on stream settlement; and a missing path assertion in the fetch controller builder can lead to cryptic validation errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread standalone/service-worker/src/websocket/WebSocketControllerRegister.ts Outdated
Comment thread core/controller-decorator/src/decorator/http/HTTPParam.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (3)
plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts (1)

517-591: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚖️ Poor tradeoff

Consider sharing the websocket-fetch stream helpers
sendFetchResponseStream, destroyFetchResponseStream*, and rejectSocket are still duplicated across plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts and standalone/service-worker/src/websocket/WebSocketControllerRegister.ts. A shared helper would reduce drift; pass the logger/error formatter in so the two implementations can keep their current logging behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts` around
lines 517 - 591, Share the duplicated websocket-fetch stream logic between the
two WebSocketControllerRegister implementations by extracting
sendFetchResponseStream, destroyFetchResponseStream(s), and rejectSocket into a
common helper. Pass logger and error-formatting dependencies into the helper so
each caller preserves its existing logging behavior, then replace both local
implementations with the shared helper and update call sites.
standalone/service-worker/src/websocket/WebSocketControllerRegister.ts (2)

502-557: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Significant duplication with the plugin WebSocketControllerRegister.

sendFetchResponseStream, destroyFetchResponseStream, isReadableStream, buildMethodArgs, and compose are nearly identical to plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts. The primary difference is logging (console.error vs this.app.logger.error). Extracting shared logic into a common module would reduce maintenance burden and ensure bug fixes propagate to both implementations.

Also applies to: 559-576, 596-615

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@standalone/service-worker/src/websocket/WebSocketControllerRegister.ts`
around lines 502 - 557, Extract the duplicated helpers sendFetchResponseStream,
destroyFetchResponseStream, isReadableStream, buildMethodArgs, and compose from
both WebSocketControllerRegister implementations into a shared module, then
update each class to use it. Preserve existing behavior while allowing logging
to be injected or adapted so standalone code continues using console.error and
the plugin implementation uses this.app.logger.error.

502-557: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

sendFetchResponseStream does not handle WebSocket backpressure.

Data is read from the source stream via result.on('data', onData) in flowing mode and sent with webSocket.send() without checking bufferedAmount. If the client is slow to consume, data accumulates in ws's internal send buffer, potentially causing unbounded memory growth. In contrast, pipeResponseStream (line 596) uses pipeline() which handles backpressure correctly. Consider using pipeline() or pausing/resuming the source stream based on webSocket.bufferedAmount.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@standalone/service-worker/src/websocket/WebSocketControllerRegister.ts`
around lines 502 - 557, sendFetchResponseStream reads in flowing mode without
respecting WebSocket backpressure, allowing bufferedAmount to grow unboundedly.
Refactor the stream forwarding around sendFetchResponseStream to use
pipeline-compatible backpressure handling, or pause the readable source whenever
webSocket.bufferedAmount exceeds a defined threshold and resume it once the
buffer drains; preserve existing completion, error handling, cleanup, and
responseStreams tracking.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@core/controller-decorator/src/impl/websocket/WebSocketControllerMetaBuilder.ts`:
- Line 38: Correct the assertion message in WebSocketControllerMetaBuilder by
changing the text in the controller type assertion to “invalid controller type.”

In
`@plugin/controller/test/fixtures/apps/controller-app/app/controller/WebSocketController.ts`:
- Line 164: Replace the empty callback passed to pipeline in WebSocketController
with a callback containing a descriptive comment, satisfying the
`@typescript-eslint/no-empty-function` lint rule while preserving behavior.

In `@standalone/service-worker/package.json`:
- Line 57: Update the ws dependency in package.json from ^8.18.0 to ^8.21.0 or a
newer non-vulnerable version, leaving `@types/ws` unchanged.

In `@standalone/service-worker/src/websocket/WebSocketControllerRegister.ts`:
- Around line 154-161: Update WebSocketControllerRegister.rejectSocket to
replace the socket.write() followed by socket.destroy() sequence with
socket.end(), passing the complete HTTP error response so the data is flushed
before the connection closes.
- Around line 483-490: Ensure errors from controllerMeta.connectionMethod or
controllerMeta.openMethod do not bypass awaiting closeHandled: restructure the
try/finally flow around invokeMethod so await closeHandled always executes
before rethrowing any connection/open error, while preserving
resolveControllerReady() in the finally block and propagating the original error
afterward.

In `@standalone/service-worker/test/fixtures/websocket/WebSocketController.ts`:
- Line 145: Replace the empty callback passed to pipeline in WebSocketController
with a non-empty error handler that logs or otherwise acknowledges the received
error, preserving existing stream error propagation while satisfying ESLint’s
no-empty-function rule.

In `@standalone/service-worker/test/websocket/websocket.test.ts`:
- Around line 287-293: Remove the unused waitJSONMessages function from the
websocket tests; callers already use createJSONMessageQueue, so delete the
definition to resolve no-unused-vars failures.

---

Nitpick comments:
In `@plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts`:
- Around line 517-591: Share the duplicated websocket-fetch stream logic between
the two WebSocketControllerRegister implementations by extracting
sendFetchResponseStream, destroyFetchResponseStream(s), and rejectSocket into a
common helper. Pass logger and error-formatting dependencies into the helper so
each caller preserves its existing logging behavior, then replace both local
implementations with the shared helper and update call sites.

In `@standalone/service-worker/src/websocket/WebSocketControllerRegister.ts`:
- Around line 502-557: Extract the duplicated helpers sendFetchResponseStream,
destroyFetchResponseStream, isReadableStream, buildMethodArgs, and compose from
both WebSocketControllerRegister implementations into a shared module, then
update each class to use it. Preserve existing behavior while allowing logging
to be injected or adapted so standalone code continues using console.error and
the plugin implementation uses this.app.logger.error.
- Around line 502-557: sendFetchResponseStream reads in flowing mode without
respecting WebSocket backpressure, allowing bufferedAmount to grow unboundedly.
Refactor the stream forwarding around sendFetchResponseStream to use
pipeline-compatible backpressure handling, or pause the readable source whenever
webSocket.bufferedAmount exceeds a defined threshold and resume it once the
buffer drains; preserve existing completion, error handling, cleanup, and
responseStreams tracking.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca0177ad-3acd-4861-9b7b-1912af57b83a

📥 Commits

Reviewing files that changed from the base of the PR and between 3b9f907 and 0f644cd.

📒 Files selected for processing (52)
  • core/controller-decorator/index.ts
  • core/controller-decorator/src/decorator/http/HTTPParam.ts
  • core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchController.ts
  • core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchMethod.ts
  • core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchParam.ts
  • core/controller-decorator/src/decorator/websocket/WebSocketController.ts
  • core/controller-decorator/src/decorator/websocket/WebSocketMethod.ts
  • core/controller-decorator/src/decorator/websocket/WebSocketParam.ts
  • core/controller-decorator/src/impl/websocket-fetch/WebSocketFetchControllerMetaBuilder.ts
  • core/controller-decorator/src/impl/websocket-fetch/WebSocketFetchControllerMethodMetaBuilder.ts
  • core/controller-decorator/src/impl/websocket/WebSocketControllerMetaBuilder.ts
  • core/controller-decorator/src/impl/websocket/WebSocketControllerMethodMetaBuilder.ts
  • core/controller-decorator/src/model/WebSocketControllerMeta.ts
  • core/controller-decorator/src/model/WebSocketFetchControllerMeta.ts
  • core/controller-decorator/src/model/WebSocketFetchMethodMeta.ts
  • core/controller-decorator/src/model/WebSocketMethodMeta.ts
  • core/controller-decorator/src/model/index.ts
  • core/controller-decorator/src/util/WebSocketInfoUtil.ts
  • core/test-util/StandaloneTestUtil.ts
  • core/types/controller-decorator/MetadataKey.ts
  • core/types/controller-decorator/WebSocketContext.ts
  • core/types/controller-decorator/WebSocketController.ts
  • core/types/controller-decorator/WebSocketFetchController.ts
  • core/types/controller-decorator/WebSocketFetchMethod.ts
  • core/types/controller-decorator/WebSocketFetchParam.ts
  • core/types/controller-decorator/WebSocketMethod.ts
  • core/types/controller-decorator/WebSocketParam.ts
  • core/types/controller-decorator/index.ts
  • core/types/controller-decorator/model/types.ts
  • core/types/standalone/index.ts
  • core/types/standalone/websocket.ts
  • plugin/controller/app.ts
  • plugin/controller/lib/impl/websocket/WebSocketContext.ts
  • plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts
  • plugin/controller/package.json
  • plugin/controller/test/fixtures/apps/controller-app/app/controller/AppController.ts
  • plugin/controller/test/fixtures/apps/controller-app/app/controller/WebSocketController.ts
  • plugin/controller/test/fixtures/apps/controller-app/app/controller/WebSocketTestState.ts
  • plugin/controller/test/websocket/websocket.test.ts
  • plugin/controller/test/websocket/websocketCluster.test.ts
  • standalone/service-worker/index.ts
  • standalone/service-worker/package.json
  • standalone/service-worker/src/ServiceWorkerApp.ts
  • standalone/service-worker/src/hook/ControllerLoadUnitHook.ts
  • standalone/service-worker/src/websocket/ServiceWorkerWebSocketContext.ts
  • standalone/service-worker/src/websocket/WebSocketControllerRegister.ts
  • standalone/service-worker/src/websocket/WebSocketEventHandler.ts
  • standalone/service-worker/test/fixtures/http/GetController.ts
  • standalone/service-worker/test/fixtures/websocket/WebSocketController.ts
  • standalone/service-worker/test/fixtures/websocket/package.json
  • standalone/service-worker/test/http/response.test.ts
  • standalone/service-worker/test/websocket/websocket.test.ts

Comment thread core/controller-decorator/src/impl/websocket/WebSocketControllerMetaBuilder.ts Outdated
Comment thread standalone/service-worker/package.json Outdated
Comment thread standalone/service-worker/src/websocket/WebSocketControllerRegister.ts Outdated
Comment thread standalone/service-worker/test/fixtures/websocket/WebSocketController.ts Outdated
Comment thread standalone/service-worker/test/websocket/websocket.test.ts Outdated
Comment thread core/controller-decorator/src/decorator/websocket/WebSocketParam.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated

@killagu killagu left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

多进程下 ws socket 问题如何处理?

Comment thread core/controller-decorator/src/decorator/http/HTTPParam.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
Comment thread plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts Outdated
@killagu

killagu commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Code Review: Feat/websocket controller

总览

本 PR 为 tegg 在三层架构(types → decorator → meta builder → register)上引入了完整的 WebSocket 支持,整体设计与现有 HTTP Controller 体系保持一致:

  • @WebSocketController / @WebSocketMethod —— 底层模式:每个连接调用一次方法,可通过 @WebSocketSocket / @WebSocketStream 直接操作 socket,返回的可读流会被 pipe 回客户端。
  • @WebSocketFetchController / @WebSocketFetchMethod —— 高层 "fetch 风格" 模式:每个 Controller 一个 DATA 处理方法,外加可选的 OnConnection / OnOpen / OnError / OnClose 生命周期方法;DATA 方法返回的可读流按 chunk 逐条发送为消息。
  • 复用 @HTTPParam / @HTTPQuery / @HTTPQueries / @HTTPHeaders / @Request 做参数绑定,并新增了若干 WS 专属参数装饰器。
  • 同时实现了 Egg 插件模式(plugin/controller,基于 server.on('upgrade') + ws noServer 模式)与 standalone service-worker 运行时(新增 WebSocketEventHandler)。
  • e2e 测试覆盖扎实:单进程、双 worker cluster、standalone 三套用例,覆盖 echo、流式、生命周期错误、消息串行化、连接隔离、流清理等场景。

整体架构方向正确,运行时逻辑写得比较细致(消息串行队列、按连接清理流、settle 时移除 listener 等)。以下问题按重要性排序。

主要问题

1. 两份 Register 之间约 660 行代码重复(可维护性,高优先级)

plugin/controller/lib/impl/websocket/WebSocketControllerRegister.tsstandalone/service-worker/src/websocket/WebSocketControllerRegister.ts 几乎完全相同:buildMethodArgsinvokeControllerinvokeFetchControllersendFetchResponseStreamdestroyFetchResponseStream*sendFetchChunkpipeResponseStreamwaitWebSocketClosematchRoutematchHostcreateURLcheckDuplicate* 均为复制粘贴,仅 logger / context 有差异。按照本仓库自身的分层规范(types ← decorator ← runtime ← plugin),连接分发引擎应下沉到共享 core 包(如 core/websocket-runtime),plugin 与 standalone 只提供 egg context 包装和 logger。否则一处修 bug,另一处会静默残留。

2. Egg 插件模式下未匹配的 upgrade 请求会永久挂起(资源泄漏 / DoS 风险)

plugin/.../WebSocketControllerRegister.tshandleUpgrade 在路由不匹配时直接 return。Node 只有在完全没有 upgrade listener 时才会自动销毁 upgrade socket;一旦 tegg 注册了 listener,未匹配的 upgrade 请求(如 ws://host/anything)若没有其他 listener 处理,TCP socket 会一直挂着不释放。现有测试只覆盖了"存在其他 listener"的场景。standalone 版本则正确返回了 404。建议:在当前 tick 之后(process.nextTick)检查 socket 是否已被其他 listener 写入或销毁,否则回 404;或至少在 server.listenerCount('upgrade') === 1 时直接拒绝。

3. fetch 模式的响应流没有背压处理(内存风险)

sendFetchResponseStream 用 flowing 模式(result.on('data'))消费流,每个 chunk 直接 webSocket.send()。当客户端消费慢时 ws 会无上限缓冲(bufferedAmount 持续增长),快生产者 + 慢消费者会导致单连接内存膨胀。非 fetch 路径用 pipeline() + createWebSocketStream 正确处理了背压。建议在 webSocket.bufferedAmount 超过阈值时 pause 源流,或同样走 createWebSocketStream

4. 所有 HTTP 参数装饰器现在无条件写入 WebSocket 元数据

HTTPParam.ts@HTTPQuery@HTTPQueries@HTTPParam@HTTPHeaders@Request 现在除了 HTTPInfoUtil.set… 还都会调用 WebSocketInfoUtil.set… —— 对每个应用里的每个纯 HTTP Controller 都会写一份永远不会被读取的 WS 参数元数据 map。虽然功能上没问题(symbol 不同),但既浪费又在语义上倒置。更干净的做法:用统一的通用 symbol 共享一份参数元数据存储;或让 WebSocket meta builder 在 WS 元数据缺失时回退读取 HTTPInfoUtil 的元数据。

5. 普通 WebSocket 模式的 message 回调中访问 ContextProto 是个陷阱(需要文档)

@WebSocketController 模式下,用户通过 socket.on('message', …) 注册的回调运行在 socket I/O 的异步上下文中,而不在 ctxStorage.run(eggCtx, …) 内 —— 在回调里访问注入的 @ContextProto(或任何 ctx 绑定对象)会解析到错误的/空的 context。fetch 模式通过每次分发都包一层 ctxStorage.run 正确处理了这一点。本 PR 目前完全没有文档(checklist 未勾选),这个坑点以及两种模式的使用方式在发布前需要补文档。

次要问题

  • 安全 —— 缺少 origin 校验钩子。 浏览器对 WebSocket 不应用同源策略,CSWSH(跨站 WebSocket 劫持)是真实的攻击面。虽然可以用 middleware 做鉴权,但建议在 @WebSocketController 上提供一等的 origin / verifyClient 风格选项,或至少在文档中给出建议。
  • matchRoutedecodeURIComponent(matched[index + 1]) —— 可选路径参数(/:id?)时分组为 undefined,会得到字符串 "undefined";百分号编码非法时会抛 URIError,表现为 500 而非 400。两处都应加保护(HTTP router 已处理这些场景)。
  • plugin/controller/app.ts:154didLoadserverDidReady() 中各调用了一次 WebSocketControllerRegister.instance?.listen()。真实 Egg 环境下 didLoadapp.server 尚不存在,该调用是 no-op;如果只是为了兼容 egg-mock,建议加注释说明,否则可删除。
  • (controllerMeta as any).getMethodRealPath(...) 等四个辅助方法用 as any 绕过了类型检查且没有收益 —— 两个 meta 类都声明了这些方法,直接用 union 类型即可通过类型检查,或抽一个小的共享 interface。
  • WebSocketFetchControllerMetaBuilder 断言消息语法:"must has exactly one" → "must have exactly one"。
  • fetch 模式静默丢弃消息:readyState !== OPEN(或 controllerReady 之前)排队的消息会被无日志丢弃,建议加 debug 日志方便生产排查。
  • standalone/service-worker/test/fixtures/http/GetController.ts 新增的 SSE 接口及 response.test.ts 中的 SSE 测试与 WebSocket 无直接关系 —— 保留没问题,但建议在 PR 描述中说明。
  • Register 的静态 instance 单例与现有 HTTPControllerRegister 模式一致,无异议 —— 但注意它有同样的局限(每进程一个 register;构造函数捕获第一个 app)。

测试覆盖

e2e 层面很强:插件单进程(609 行)、双 worker cluster、standalone 三套用例,包含不少 tricky 场景(生命周期错误 → error handler → 1011 关闭、按连接销毁流、消息顺序)。缺口:

  • meta builder 的单测偏薄(只测了缺 path 的断言)。未覆盖:重复生命周期方法的拒绝、各方法类型不允许的参数类型(fetch 中用 @WebSocketStream、普通 WS 中用 @WebSocketData)、路由冲突(RouterConflictError)、优先级排序、host 匹配。
  • 未覆盖"路由不匹配且没有其他 listener"的场景(问题 2)。
  • 未覆盖 WebSocket 路由上的 controller / method middleware,虽然代码路径存在(getMethodMiddlewaresinvoked / 1008 分支)。

结论

架构方向正确、运行时代码用心。建议合并前修复问题 2(未匹配 upgrade 挂起),并对问题 1(代码重复)至少给出后续重构计划确认;问题 3–5 也强烈建议在合并前处理。文档目前缺失,而这是面向用户的 feature。ws@^8.21.0 版本有效,新导出通过 core/tegg/index.ts:3 自动透出,打包层面没有问题。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts (1)

135-143: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Misleading non-null assertion on checkRouters.get(host)

The ! assertion on this.checkRouters.get(host)! is immediately contradicted by the if (!router) check on the next line. The value can be undefined for new hosts — the assertion is incorrect. Using a separately typed local variable would be clearer and type-safe.

♻️ Proposed refactor
     hosts.forEach(host => {
-      router = this.checkRouters.get(host)!;
-      if (!router) {
-        router = new EggRouter({ sensitive: true }, {} as any);
-        this.checkRouters.set(host, router);
-      }
-      this.checkDuplicateInRouter(router, controllerMeta, methodMeta);
-      this.registerToRouter(router, controllerMeta, methodMeta);
+      let hostRouter = this.checkRouters.get(host);
+      if (!hostRouter) {
+        hostRouter = new EggRouter({ sensitive: true }, {} as any);
+        this.checkRouters.set(host, hostRouter);
+      }
+      this.checkDuplicateInRouter(hostRouter, controllerMeta, methodMeta);
+      this.registerToRouter(hostRouter, controllerMeta, methodMeta);
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts` around
lines 135 - 143, Remove the non-null assertion from the
`this.checkRouters.get(host)` assignment in the `hosts.forEach` callback,
keeping `router` typed as possibly undefined so the existing `if (!router)`
initialization and subsequent router registration remain type-safe.
🧹 Nitpick comments (2)
core/websocket-runtime/test/WebSocketRouteRegistry.test.ts (1)

12-63: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add tests for WebSocketFetchControllerMeta branch and host port matching.

The test only covers WebSocketControllerMeta. The helper functions' ControllerType.WEBSOCKET_FETCH branches (lines 82, 92, 102, 112 in WebSocketRouteRegistry.ts) and matchWebSocketHost port-stripping logic (line 139) are untested. Adding a fetch-controller test case and a host-with-port match (e.g., example.com:8080 matching example.com) would close this gap.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/websocket-runtime/test/WebSocketRouteRegistry.test.ts` around lines 12 -
63, The existing WebSocket route test covers only WebSocketControllerMeta and
omits fetch-controller and host-port behavior. Extend the test around
createWebSocketRoutes and matchWebSocketRoute with a
WebSocketFetchControllerMeta case that exercises the
ControllerType.WEBSOCKET_FETCH branches, and add a host-with-port assertion
confirming example.com:8080 matches the example.com route while preserving the
existing coverage.
core/websocket-runtime/src/WebSocketRouteRegistry.ts (1)

75-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider reducing repetitive type-narrowing boilerplate.

Four helper functions (getWebSocketMethodRealPath, getWebSocketMethodHosts, getWebSocketMethodName, getWebSocketMethodMiddlewares) share the identical if (controllerMeta.type === ControllerType.WEBSOCKET) { ... } else { ... } pattern with as casts. A single dispatch helper or moving the union-type handling into the metadata classes would reduce duplication and the risk of the branches diverging.

♻️ Optional refactor: single dispatch helper
+function dispatchMethodMeta<R>(
+  controllerMeta: WebSocketControllerMetadata,
+  methodMeta: WebSocketRouteMethodMeta,
+  wsFn: (m: WebSocketMethodMeta) => R,
+  fetchFn: (m: WebSocketFetchMethodMeta) => R,
+): R {
+  return controllerMeta.type === ControllerType.WEBSOCKET
+    ? wsFn(methodMeta as WebSocketMethodMeta)
+    : fetchFn(methodMeta as WebSocketFetchMethodMeta);
+}
+
 export function getWebSocketMethodRealPath(
   controllerMeta: WebSocketControllerMetadata,
   methodMeta: WebSocketRouteMethodMeta,
 ) {
-  if (controllerMeta.type === ControllerType.WEBSOCKET) {
-    return controllerMeta.getMethodRealPath(methodMeta as WebSocketMethodMeta);
-  }
-  return controllerMeta.getMethodRealPath(methodMeta as WebSocketFetchMethodMeta);
+  return dispatchMethodMeta(
+    controllerMeta, methodMeta,
+    m => controllerMeta.getMethodRealPath(m),
+    m => controllerMeta.getMethodRealPath(m),
+  );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/websocket-runtime/src/WebSocketRouteRegistry.ts` around lines 75 - 113,
Reduce the duplicated type-narrowing branches shared by
getWebSocketMethodRealPath, getWebSocketMethodHosts, getWebSocketMethodName, and
getWebSocketMethodMiddlewares by introducing a single dispatch helper or
equivalent metadata-level union handling. Preserve each helper’s existing
behavior and return types while centralizing the ControllerType.WEBSOCKET
distinction and its casts.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/websocket-runtime/src/WebSocketFetchSession.ts`:
- Around line 57-71: Update WebSocketFetchSession.onClose to preserve or log any
buffered error events before replacing the events queue and enqueueing the close
event. Ensure pending errors are handled consistently with onError, while
retaining the existing dropped-message tracking and connection/open event
behavior.

---

Outside diff comments:
In `@plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts`:
- Around line 135-143: Remove the non-null assertion from the
`this.checkRouters.get(host)` assignment in the `hosts.forEach` callback,
keeping `router` typed as possibly undefined so the existing `if (!router)`
initialization and subsequent router registration remain type-safe.

---

Nitpick comments:
In `@core/websocket-runtime/src/WebSocketRouteRegistry.ts`:
- Around line 75-113: Reduce the duplicated type-narrowing branches shared by
getWebSocketMethodRealPath, getWebSocketMethodHosts, getWebSocketMethodName, and
getWebSocketMethodMiddlewares by introducing a single dispatch helper or
equivalent metadata-level union handling. Preserve each helper’s existing
behavior and return types while centralizing the ControllerType.WEBSOCKET
distinction and its casts.

In `@core/websocket-runtime/test/WebSocketRouteRegistry.test.ts`:
- Around line 12-63: The existing WebSocket route test covers only
WebSocketControllerMeta and omits fetch-controller and host-port behavior.
Extend the test around createWebSocketRoutes and matchWebSocketRoute with a
WebSocketFetchControllerMeta case that exercises the
ControllerType.WEBSOCKET_FETCH branches, and add a host-with-port assertion
confirming example.com:8080 matches the example.com route while preserving the
existing coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63ada958-3098-4ef3-a07d-6d581e91235d

📥 Commits

Reviewing files that changed from the base of the PR and between 2782dd7 and 348592e.

📒 Files selected for processing (41)
  • core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchController.ts
  • core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchMethod.ts
  • core/controller-decorator/src/decorator/websocket/WebSocketController.ts
  • core/controller-decorator/src/decorator/websocket/WebSocketMethod.ts
  • core/controller-decorator/src/impl/websocket-fetch/WebSocketFetchControllerMetaBuilder.ts
  • core/controller-decorator/src/impl/websocket-fetch/WebSocketFetchControllerMethodMetaBuilder.ts
  • core/controller-decorator/src/impl/websocket/WebSocketControllerMetaBuilder.ts
  • core/controller-decorator/src/impl/websocket/WebSocketControllerMethodMetaBuilder.ts
  • core/controller-decorator/src/model/WebSocketControllerMeta.ts
  • core/controller-decorator/src/model/WebSocketFetchControllerMeta.ts
  • core/controller-decorator/src/model/WebSocketFetchMethodMeta.ts
  • core/controller-decorator/src/model/WebSocketMethodMeta.ts
  • core/controller-decorator/src/util/WebSocketInfoUtil.ts
  • core/controller-decorator/test/websocket/WebSocketMeta.test.ts
  • core/types/controller-decorator/WebSocketContext.ts
  • core/types/controller-decorator/WebSocketController.ts
  • core/types/controller-decorator/WebSocketFetchController.ts
  • core/types/controller-decorator/WebSocketFetchMethod.ts
  • core/types/controller-decorator/WebSocketMethod.ts
  • core/websocket-runtime/index.ts
  • core/websocket-runtime/package.json
  • core/websocket-runtime/src/WebSocketControllerRuntime.ts
  • core/websocket-runtime/src/WebSocketFetchSession.ts
  • core/websocket-runtime/src/WebSocketRouteRegistry.ts
  • core/websocket-runtime/test/WebSocketFetchSession.test.ts
  • core/websocket-runtime/test/WebSocketRouteRegistry.test.ts
  • core/websocket-runtime/tsconfig.json
  • core/websocket-runtime/tsconfig.pub.json
  • plugin/controller/app.ts
  • plugin/controller/lib/impl/websocket/WebSocketContext.ts
  • plugin/controller/lib/impl/websocket/WebSocketControllerRegister.ts
  • plugin/controller/package.json
  • plugin/controller/test/fixtures/apps/controller-app/app/controller/WebSocketController.ts
  • plugin/controller/test/fixtures/apps/controller-app/config/config.default.js
  • plugin/controller/test/websocket/websocket.test.ts
  • plugin/controller/test/websocket/websocketCluster.test.ts
  • standalone/service-worker/package.json
  • standalone/service-worker/src/websocket/ServiceWorkerWebSocketContext.ts
  • standalone/service-worker/src/websocket/WebSocketControllerRegister.ts
  • standalone/service-worker/test/fixtures/websocket/WebSocketController.ts
  • standalone/service-worker/test/websocket/websocket.test.ts
💤 Files with no reviewable changes (1)
  • plugin/controller/app.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • core/types/controller-decorator/WebSocketFetchController.ts
  • core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchController.ts
  • core/types/controller-decorator/WebSocketController.ts
  • core/controller-decorator/src/model/WebSocketFetchMethodMeta.ts
  • core/controller-decorator/src/decorator/websocket/WebSocketMethod.ts
  • standalone/service-worker/src/websocket/ServiceWorkerWebSocketContext.ts
  • core/types/controller-decorator/WebSocketFetchMethod.ts
  • core/controller-decorator/src/decorator/websocket/WebSocketController.ts
  • core/types/controller-decorator/WebSocketMethod.ts
  • standalone/service-worker/package.json
  • core/controller-decorator/src/impl/websocket/WebSocketControllerMetaBuilder.ts
  • core/controller-decorator/src/model/WebSocketControllerMeta.ts
  • core/controller-decorator/src/impl/websocket-fetch/WebSocketFetchControllerMetaBuilder.ts
  • core/controller-decorator/src/decorator/websocket-fetch/WebSocketFetchMethod.ts
  • core/controller-decorator/src/impl/websocket-fetch/WebSocketFetchControllerMethodMetaBuilder.ts
  • plugin/controller/test/websocket/websocketCluster.test.ts
  • core/controller-decorator/src/model/WebSocketMethodMeta.ts
  • core/controller-decorator/src/impl/websocket/WebSocketControllerMethodMetaBuilder.ts
  • plugin/controller/test/fixtures/apps/controller-app/app/controller/WebSocketController.ts

Comment thread core/websocket-runtime/src/WebSocketFetchSession.ts
@akitaSummer
akitaSummer merged commit 4e8223b into master Jul 13, 2026
12 checks passed
@akitaSummer
akitaSummer deleted the feat/websocket-controller branch July 13, 2026 03:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants