你这个判断是对的:**Undertow 不应感知路由/中间件,Context 也不应有 `ws()`**。
下面给你一版更干净、可落地的**完整重构方案(分层清晰版)**。
---
## 0. 设计目标(先定原则)
1. **分层职责单一**
- `core`:路由、匹配、中间件、DI、事件、抽象连接对象
- `server-undertow`:仅实现网络协议适配(HTTP + WS I/O),不包含业务路由逻辑
2. **HTTP 与 WS 是并列协议,不是“响应体特例”**
- 不走 `Context.ws()` / `ResponseBody.WebSocket`
3. **统一开发体验**
- `app.ws(...)`
- `app.wsUse(...)`
- `@Ws(...)`
- 连接内可访问升级时上下文快照(path/query/state/service)
---
## 1. 分层架构(修正版)
- `colleen-core`
- `Router`(HTTP routes + WS routes)
- `MiddlewarePipeline`(HTTP/WS 两条链)
- `WsRouteDefinition / WsRouteMatch`
- `WebSocketConnection`(框架抽象)
- `WebSocketSessionContext`(升级时上下文快照)
- `WsEvents`
- `colleen-server-api`
- `WebServer` 抽象
- `WebSocketAdapter` SPI(服务器实现 WS 读写)
- `colleen-server-undertow`
- Undertow 启动与 handler 桥接
- `UndertowWsChannelAdapter : WebSocketAdapter`
- 只接收 core 下发的“已匹配 WS endpoint 执行计划”,不自己匹配路由
---
## 2. 关键修正点(你提到的问题逐条落地)
### A) Undertow 不感知路由/中间件
**做法**:
路由匹配和中间件执行全部在 core 完成。Undertow 只做:
- 识别请求(HTTP or WS upgrade)
- 把请求交给 core dispatcher
- 若 dispatcher 返回 `UpgradePlan.WebSocket`,则执行握手并绑定 channel adapter
### B) Context 不提供 `ws()`
**做法**:
WS endpoint 由专门注册 API 进入路由表,不通过 HTTP handler 中 `ctx.ws()` 触发升级。
### C) WS 不作为 ResponseBody
**做法**:
引入专门返回类型:
- `DispatchResult.Http(Response)`
- `DispatchResult.WebSocketUpgrade(UpgradePlan.WebSocket)`
- `DispatchResult.NotFound`
---
## 3. API 设计(对用户)
```kotlin
val app = Colleen()
app.use(RequestLogger()) // HTTP 全局中间件
app.wsUse("/ws/*", AuthWsMiddleware()) // WS 中间件链
app.ws("/ws/echo") { conn -> ... }
@Controller("/ws")
class WsController {
@Ws("/chat/{room}")
fun chat(conn: WebSocketConnection) { ... }
}
app.listen(8000)
```
### `wsUse` 语义
- 仅作用于 WS upgrade 请求
- 接口可复用 Middleware 签名,但执行环境是“upgrade 上下文”
- 明确禁止在其中写 HTTP body(若框架允许,也会被忽略/报错)
---
## 4. Core 数据结构(推荐)
### 4.1 Ws 路由定义
- `WsRouteDefinition(pathPattern, handler, metadata)`
- 支持 path param 提取
### 4.2 Upgrade 计划
- `UpgradePlan.WebSocket`
- `sessionContext`(不可变快照)
- `endpointHandler`
- `runtimeConfig`(idle timeout / max msg size / subprotocols)
### 4.3 会话上下文(替代直接持有 Context)
- `WebSocketSessionContext`
- `pathParams`
- `queryParams`
- `stateSnapshot`
- `serviceResolver`
- `requestMeta`(ip, headers, uri, userAgent...)
> 这样避免长期持有可变 `Context` 导致线程安全与生命周期混乱。
---
## 5. WebSocketConnection 设计(修正版)
能力:
- lifecycle: `onOpen/onMessage/onClose/onError`
- send: `sendText/sendBinary/sendJson`(可选)
- close: `close(code, reason)`
- context access:
- `pathParam(name)`
- `query(name)`
- `state<T>(key)`
- `getService<T>()`
约束:
- handler 注册阶段与事件触发阶段分离(先注册后触发 open)
- 默认多监听器 or 单监听器策略要明确(建议单监听器,重复注册报错)
---
## 6. Dispatcher 流程(核心)
1. 收到请求
2. 判断是否 WS upgrade 请求
3. 若是:
- 路由匹配 WS route
- 执行 `wsUse` + 全局 WS 中间件
- 产出 `UpgradePlan.WebSocket`
4. server adapter 执行握手并绑定 channel
5. 创建 `WebSocketConnectionImpl`
6. 调用用户 endpoint(注册回调)
7. 触发 `onOpen` + `WebSocketConnected` 事件
8. 接收消息并分发 `onMessage`
9. 关闭时触发 `onClose` + `WebSocketDisconnected`
---
## 7. Undertow 适配层职责(严格边界)
Undertow 模块只包含:
- `UndertowRequestAdapter`(请求映射)
- `UndertowResponseWriter`(HTTP 写回)
- `UndertowWsChannelAdapter`(send/receive/close 映射)
- `UndertowRuntime`(线程池、配置)
**不包含**:
- 路由表
- 中间件列表
- 注解扫描
- controller 注册
---
## 8. 中间件模型(HTTP/WS 分离)
建议定义两类 pipeline:
- `HttpMiddleware`
- `WsMiddleware`
即使签名一致,也在类型上分离,防止误用。
你可以提供桥接器,把现有通用中间件显式转换到 WS 侧。
---
## 9. 配置模型(完整)
`server { ... }` 增加:
- `wsIdleTimeoutMs`
- `wsMaxMessageSizeBytes`
- `wsMaxFrameSizeBytes`(可选)
- `wsEnableCompression`(默认 false)
- `wsAllowedOrigins`(默认空=不限制或仅同源,需明确)
- `wsHandshakeTimeoutMs`
- `wsDispatchExecutor`(io/business)
---
## 10. 事件模型
新增:
- `WebSocketConnected(sessionId, path, timestamp, attrs)`
- `WebSocketDisconnected(sessionId, code, reason, durationMs)`
- `WebSocketError(sessionId, throwable)`
事件触发线程建议统一投递到 EventBus executor,避免阻塞 IO。
---
## 11. 关键非功能设计
1. **线程模型**
- IO 线程只收发/解码
- 用户回调默认在业务线程池执行(可配置)
2. **背压策略**
- 每连接发送队列上限(例如 1~10MB)
- 溢出策略:`CLOSE | DROP_OLDEST | REJECT_SEND`
3. **大包与分片**
- 支持聚合上限控制
- 超限关闭(1009)
4. **错误语义**
- 协议错误、业务异常、IO 异常区分处理与 close code 映射
---
## 12. 注解 `@Ws` 设计
- 仅在 controller 扫描阶段注册为 `WsRouteDefinition`
- 方法签名建议支持:
- `fun(WebSocketConnection)`
- (可选)`fun(WebSocketConnection, WsSessionContext)`
- 不建议直接注入 HTTP Context(避免语义泄漏)
---
## 13. 文件改动建议(最终版)
### 新增(core)
- `websocket/WebSocketMessage.kt`
- `websocket/WsCloseReason.kt`
- `websocket/WebSocketConnection.kt`
- `websocket/WebSocketSessionContext.kt`
- `websocket/WsRouteDefinition.kt`
- `websocket/WsMiddleware.kt`
- `websocket/WsEvents.kt`
- `dispatch/UpgradePlan.kt`
- `dispatch/DispatchResult.kt`
- `annotation/Ws.kt`
### 修改(core)
- `Router.kt`(加入 ws route table + matcher)
- `Colleen.kt`(`ws/wsUse` 注册)
- `Dispatcher.kt`(upgrade 分支)
- `Scanner.kt`(扫描 `@Ws`)
### 新增(undertow)
- `UndertowWsChannelAdapter.kt`
- `UndertowWebSocketHandshakeHandler.kt`(仅协议桥接)
### 修改(undertow)
- `UndertowServer.kt`(根据 `DispatchResult` 执行 HTTP 或 WS)
---
## 14. 分阶段实施(建议)
- **M1**:`ws + wsUse + functional handler + text/binary + close`
- **M2**:`@Ws + path params + session context + events`
- **M3**:背压、异步发送、origin/subprotocol/compression、监控指标
---
## 15. 你之前方案中“类似问题”再补几条
- `Consumer<WebSocketConnection>` 偏 Java 风格,Kotlin DSL 可直接 `(WebSocketConnection) -> Unit`
- `attribute()`/`state()` 命名不统一
- `Context` 长生命周期引用有内存泄漏风险
- WS 与 HTTP 共用异常映射器需单独规则(握手前 vs 握手后)
---
如果你认可,我下一步可以直接给你输出两份可执行文档草案:
1. **`docs/design/websocket-architecture.md`**(架构与时序)
2. **`docs/design/websocket-api-contract.md`**(API 契约 + 错误码 + 线程模型)
你确认后我按这版继续细化到“可直接开工”的接口级别。