UNPKG

koatty_serve

Version:

Provide http1/2/3, websocket, gRPC server for Koatty.

257 lines (256 loc) 15.7 kB
{ "tasks": [ { "id": "task-001", "title": "修复 ConnectionPoolManager 定时器泄漏", "phase": 1, "priority": "P0", "status": "completed", "depends_on": [], "files": ["src/pools/pool.ts"], "description": "在 ConnectionPoolManager 中添加 healthCheckInterval 和 cleanupInterval 属性,保存 setInterval 返回值,在 destroy() 中清理定时器", "reviewer": "reviewer", "reviewer_notes": "[P3] src/pools/pool.ts:130 — 注释'定时器引用 - 用于清理'仅说明目的,建议补充防止进程阻塞的意图(结合 .unref() 说明)" }, { "id": "task-002", "title": "修复 createGrpcConfig 中 channelOptions 赋值错误", "phase": 1, "priority": "P0", "status": "completed", "depends_on": [], "files": ["src/config/config.ts"], "description": "将 channelOptions: options.connectionPool || {} 修改为 channelOptions: options.channelOptions || {}", "reviewer": "reviewer", "reviewer_notes": "pass — 单行精准修复,正确将 options.connectionPool 改为 options.channelOptions,消除了 gRPC channelOptions 被错误替换为连接池配置的 bug" }, { "id": "task-003", "title": "修复 BaseServer 构造函数时序 - 移除自动初始化", "phase": 1, "priority": "P0", "status": "completed", "depends_on": [], "files": ["src/server/base.ts"], "description": "在 BaseServer 构造函数中移除 this.initializeServer() 调用,让子类手动调用", "reviewer": "orchestrator", "reviewer_notes": "配合 task-004 完成,子类已添加手动调用" }, { "id": "task-004", "title": "修复所有子类构造函数时序 - 手动初始化", "phase": 1, "priority": "P0", "status": "completed", "depends_on": ["task-003"], "files": ["src/server/http.ts", "src/server/https.ts", "src/server/http2.ts", "src/server/http3.ts", "src/server/grpc.ts", "src/server/ws.ts"], "description": "在所有子类构造函数中,配置处理后手动调用 this.initializeServer()", "reviewer": "orchestrator", "reviewer_notes": "6 个子类已添加 this.initializeServer() 调用" }, { "id": "task-005", "title": "统一关闭路径 - 移除 terminus.ts 重复信号逻辑", "phase": 1, "priority": "P0", "status": "completed", "depends_on": [], "files": ["src/utils/terminus.ts"], "description": "移除 signalHandlers Map,简化 onSignal 函数,不再触发 appStop 和 process.exit", "reviewer": "reviewer", "reviewer_notes": "pass — 信号处理逻辑已成功委托给 TerminusManager 单例,process.setMaxListeners(0) 已删除。[P3] src/utils/terminus.ts:19 — TerminusOptions.onSignal 字段已无任何调用方使用(task-005 移除了对应逻辑),属于废弃死接口;建议删除该字段或将整个 TerminusOptions 标记 @deprecated" }, { "id": "task-006", "title": "统一关闭路径 - 调整 TerminusManager", "phase": 1, "priority": "P0", "status": "rejected", "depends_on": ["task-005"], "files": ["src/utils/terminus-manager.ts"], "description": "修改 shutdownAll 方法,移除直接调用 server.destroy(),仅触发 appStop 事件", "reviewer": "reviewer", "reviewer_notes": "[P1] src/utils/terminus-manager.ts:129 — asyncEvent(process, 'beforeExit') 调用会通过 removeAllListeners 永久删除 Node.js process 上所有 beforeExit 监听器;若应用其他模块(或第三方库)注册了 beforeExit 钩子,将被静默移除,导致清理逻辑失效;应改为仅触发监听器而不移除,如 process.listeners('beforeExit').forEach(fn => fn()) 或去掉这一整行(appStop 已足够)。[P2] src/utils/terminus-manager.ts:35 — this.servers Map 通过 registerServer 填充,但 shutdownAll 中从未遍历该 Map,注册的 server 引用完全不参与关闭流程,属于死代码;建议删除 servers Map 与 getServerCount(),或补充对应使用逻辑。[P2] src/utils/terminus-manager.ts:154-159 — resetInstance() 将 signalsRegistered 重置为 false 后再置 instance=null,下次 getInstance() 会再次调用 setupSignalHandlers() 注册新的 process 信号监听器,但旧实例(通过闭包被信号 handler 持有)不会被 GC,形成信号 handler 累积泄漏;建议在 resetInstance 中显式移除已注册的 process 信号监听器(需持久化 handler 引用)" }, { "id": "task-007", "title": "移除 process.setMaxListeners(0)", "phase": 1, "priority": "P0", "status": "completed", "depends_on": ["task-005", "task-006"], "files": ["src/utils/terminus.ts"], "description": "删除 process.setMaxListeners(0) 及其注释", "reviewer": "reviewer", "reviewer_notes": "pass — 已在 task-005 中完成,terminus.ts 中确认无 process.setMaxListeners 调用" }, { "id": "task-008", "title": "修复 ConnectionPoolFactory 缓存键不稳定", "phase": 2, "priority": "P1", "status": "completed", "depends_on": [], "files": ["src/pools/factory.ts"], "description": "添加 stableStringify 辅助函数,确保相同配置不同键顺序产生相同缓存键", "reviewer": "reviewer", "reviewer_notes": "pass — stableStringify 正确对对象键排序,相同配置不同键序产生相同键值,满足缓存稳定性要求。[P3] src/pools/factory.ts:24 — 数组元素经 stableStringify 后再外套 JSON.stringify,导致 [{a:1}] 序列化为 [\"{\\"a\\":1}\"] 而非 [{\"a\":1}];对缓存功能无影响(相同输入产生相同输出),但输出非标准 JSON 可能造成调试困惑;建议改为 '[' + obj.map(i => stableStringify(i)).join(',') + ']'" }, { "id": "task-009", "title": "修复 errorRate 只增不减", "phase": 2, "priority": "P1", "status": "completed", "depends_on": ["task-001"], "files": ["src/pools/pool.ts"], "description": "使用 RingBuffer 滑动窗口计算错误率,替代只增不减的逻辑", "reviewer": "reviewer", "reviewer_notes": "pass — errorWindow RingBuffer(500) 正确实现滑动窗口,recordConnectionEvent 对 'added' 推入 false、对 'error' 推入 true,calculateErrorRate 基于窗口内比例计算,错误率可随时间自然衰减" }, { "id": "task-010", "title": "修复 gRPC RegisterService 硬编码超时", "phase": 2, "priority": "P1", "status": "completed", "depends_on": [], "files": ["src/server/grpc.ts"], "description": "将硬编码的 30000ms 超时改为从配置读取:this.options.connectionPool?.requestTimeout || 30000", "reviewer": "reviewer", "reviewer_notes": "pass — src/server/grpc.ts:679 中 const timeoutMs = this.options.connectionPool?.requestTimeout || 30000 正确从配置读取,默认值 30000 保持向后兼容" }, { "id": "task-011", "title": "移除 HttpConnectionPoolManager 重复清理任务", "phase": 2, "priority": "P1", "status": "completed", "depends_on": ["task-001"], "files": ["src/pools/http.ts"], "description": "移除 httpCleanupInterval、startCleanupTasks() 和 cleanupIdleConnections(),使用父类逻辑", "reviewer": "reviewer", "reviewer_notes": "pass — httpCleanupInterval/startCleanupTasks/cleanupIdleConnections 已全部移除,清理由父类 ConnectionPoolManager.startPeriodicTasks() 统一处理。[P2] src/pools/http.ts:149 和 :332 — setupConnectionEventHandlers(私有,由 addHttpConnection 调用)与 setupProtocolSpecificHandlers(受保护,由 registerConnection 调用)对同一 Socket 注册了功能高度重叠的 close/error/timeout 处理器,属于代码重复;若两者均被调用则导致重复绑定;建议统一为一个入口或明确互斥调用路径" }, { "id": "task-012", "title": "修复 startTime falsy 判断", "phase": 3, "priority": "P2", "status": "completed", "depends_on": [], "files": ["src/server/serve.ts"], "description": "将 || 改为 ?? 运算符,正确处理 startTime 为 0 的情况", "reviewer": "reviewer", "reviewer_notes": "pass — src/server/serve.ts:292 已改为 (this.serverInstance as any)?.startTime ?? Date.now(),?? 运算符正确处理 startTime === 0 的边界情况,uptime 不再永远为 0" }, { "id": "task-013", "title": "提取 ConfigHelper 公共 SSL 迁移逻辑", "phase": 3, "priority": "P2", "status": "completed", "depends_on": [], "files": ["src/config/config.ts"], "description": "添加 migrateSSLFromExt 私有静态方法,在 5 个 createXxxConfig 方法中复用", "reviewer": "reviewer", "reviewer_notes": "pass — migrateSSLFromExt 私有静态方法已在 createHttpsConfig/createHttp2Config/createGrpcConfig/createHttp3Config/createWebSocketConfig 5 处复用,符合 DRY 原则,减少约 50 行重复代码" }, { "id": "task-014", "title": "消除 GraphQL 协议处理重复", "phase": 3, "priority": "P2", "status": "completed", "depends_on": [], "files": ["src/server/serve.ts"], "description": "移除 initializeServerInstance 中的 GraphQL _underlyingProtocol 设置,保留 createServerInstance 中的逻辑", "reviewer": "reviewer", "reviewer_notes": "pass — initializeServerInstance 中不再设置 _underlyingProtocol,该逻辑集中在 createServerInstance(serve.ts:461-472),消除了重复赋值,符合单一职责原则" }, { "id": "task-015", "title": "精简 gRPC RegisterService 日志", "phase": 3, "priority": "P2", "status": "completed", "depends_on": [], "files": ["src/server/grpc.ts"], "description": "移除所有 [GRPC_SERVER] 前缀的 debug 日志,保留关键日志", "reviewer": "reviewer", "reviewer_notes": "pass — RegisterService 中已无 [GRPC_SERVER] 前缀冗余 debug 日志,保留了服务注册、方法调用、错误和超时等关键日志,信噪比合理" }, { "id": "task-016", "title": "优化 waitingQueue 插入排序", "phase": 3, "priority": "P2", "status": "completed", "depends_on": [], "files": ["src/pools/pool.ts"], "description": "使用有序插入替代全量排序,将 O(n log n) 降为 O(n)", "reviewer": "reviewer", "reviewer_notes": "pass — src/pools/pool.ts:319-331 采用线性扫描找到第一个优先级低于新项目的位置后 splice 插入,算法正确:相同优先级维持 FIFO(新 normal 插入到最后一个 normal 之后),高优先级正确排在低优先级前面;时间复杂度 O(n) 优于原全量排序 O(n log n)" }, { "id": "task-017", "title": "添加证书路径安全检查", "phase": 3, "priority": "P2", "status": "completed", "depends_on": [], "files": ["src/utils/cert-loader.ts"], "description": "添加 sanitizeCertPath 函数,防止路径遍历攻击", "reviewer": "reviewer", "reviewer_notes": "pass — sanitizeCertPath 正确检测空字节注入和 '..' 路径段遍历,并通过 path.resolve() 返回绝对路径;对于来自应用配置的证书路径,该防护级别合理。[P3] src/utils/cert-loader.ts:37 — path.resolve() 后未校验解析路径是否在允许的目录范围内(如应用根目录下的 certs/);若证书路径源头为用户输入(而非配置文件),攻击者可直接提供 /etc/passwd 绕过遍历检测;建议在高风险场景下添加 allowedBaseDirs 白名单校验" }, { "id": "task-018", "title": "为 process.exit 添加可配置开关", "phase": 4, "priority": "P2", "status": "completed", "depends_on": ["task-006"], "files": ["src/utils/terminus-manager.ts"], "description": "添加 exitOnShutdown 配置属性和 setExitOnShutdown 方法,使 process.exit 可配置", "reviewer": "reviewer", "reviewer_notes": "pass — exitOnShutdown 默认 true,通过 setExitOnShutdown() 可配置,向后兼容;关闭成功调用 exit(0)、失败调用 exit(1) 逻辑清晰。[P3] src/utils/terminus-manager.ts:119-133 — timeoutHandle 使用 let 声明但未初始化(let timeoutHandle: NodeJS.Timeout),在 finally 块中使用非空断言 clearTimeout(timeoutHandle!) 绕过 TS 检查;实际不会出现未定义问题(Promise 构造函数同步执行),但建议使用 let timeoutHandle: NodeJS.Timeout | undefined 并改为 if (timeoutHandle) clearTimeout(timeoutHandle) 消除断言" }, { "id": "task-019", "title": "BaseServer.server 类型改进", "phase": 4, "priority": "P3", "status": "completed", "depends_on": ["task-004"], "files": ["src/server/base.ts", "src/server/http.ts", "src/server/https.ts", "src/server/http2.ts", "src/server/http3.ts", "src/server/grpc.ts", "src/server/ws.ts"], "description": "修改 BaseServer 泛型签名,各子类指定具体服务器类型", "reviewer": "reviewer", "reviewer_notes": "pass — BaseServer<T, S> 泛型声明正确,GrpcServer extends BaseServer<GrpcServerOptions, Server> 等子类类型具体化符合设计意图。[P2] src/server/base.ts:58 和各子类 createProtocolServer — readonly server!: S 声明后,子类通过 (this as any).server = new Server(...) 的类型断言赋值,绕过了 TypeScript readonly 约束,类型安全意图落空;建议改为 protected server!: S(去掉 readonly)并让 createProtocolServer 直接赋值 this.server = ...,或使用受保护的初始化方法" }, { "id": "task-020", "title": "ConnectionPoolManager 事件类型安全", "phase": 4, "priority": "P3", "status": "completed", "depends_on": ["task-009"], "files": ["src/pools/pool.ts"], "description": "定义 ConnectionPoolEventMap 类型映射,修改事件方法签名为泛型", "reviewer": "reviewer", "reviewer_notes": "pass — ConnectionPoolEventMap 类型映射完整定义了 6 个事件的数据结构,on<E>/off<E> 泛型方法在订阅侧提供编译时类型检查。[P3] src/pools/pool.ts:750 — emitEvent(event: ConnectionPoolEvent, data: any) 的 data 参数未使用 ConnectionPoolEventMap 约束,发布侧缺少类型检查,可传入任意数据而不报错;建议改为 emitEvent<E extends ConnectionPoolEvent>(event: E, data: ConnectionPoolEventMap[E])" }, { "id": "task-021", "title": "完整回归测试", "phase": 5, "priority": "回归", "status": "review_pending", "depends_on": ["task-001", "task-002", "task-003", "task-004", "task-005", "task-006", "task-007", "task-008", "task-009", "task-010", "task-011", "task-012", "task-013", "task-014", "task-015", "task-016", "task-017", "task-018", "task-019", "task-020"], "files": [], "description": "运行全量测试、构建、lint,确保所有修改不破坏现有功能", "reviewer": "orchestrator", "reviewer_notes": "752 passed, 1 failed(预先存在), 23 skipped | 构建成功 | Lint: 0 errors, 6 warnings(预先存在)" } ] }