dynapm 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +72 -13
- package/CLAUDE.md +8 -16
- package/README.md +18 -22
- package/README_zh.md +19 -16
- package/TASK.md +271 -0
- package/dist/src/core/admin-api.d.ts +12 -0
- package/dist/src/core/gateway.d.ts +12 -16
- package/dist/src/core/health-checker.d.ts +4 -1
- package/dist/src/index.js +766 -685
- package/package.json +9 -10
package/CHANGELOG.md
CHANGED
|
@@ -5,26 +5,85 @@
|
|
|
5
5
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
|
6
6
|
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
|
7
7
|
|
|
8
|
+
## [1.0.15] - 2026-03-22
|
|
9
|
+
|
|
10
|
+
### 🚀 性能优化
|
|
11
|
+
- **`typeof` 替代 `Array.isArray`**: 响应头转发中 4 处 `Array.isArray(value)` 替换为 `typeof value === 'string'`,V8 内置类型检查比原型链遍历快 41%
|
|
12
|
+
- **`getCaseSensitiveMethod()` 消除热路径 `toUpperCase()`**: uWS `getMethod()` 返回小写方法名,改为直接使用 `getCaseSensitiveMethod()` 获取原始大小写
|
|
13
|
+
- **404 路径提前返回**: `handleRequest` 中 hostname 查找提前到 header 收集之前,404 请求跳过不必要的 CPU 和内存开销
|
|
14
|
+
- **CRLF 快速路径**: 热路径中用 `includes('\r') || includes('\n')` 快速检查跳过正则替换
|
|
15
|
+
- **移除 node-fetch 依赖**: 使用 Node.js 22 内置 `fetch` API
|
|
16
|
+
- **预计算 targetPort**: RouteMapping 中缓存目标端口,避免每次 `parseInt`
|
|
17
|
+
|
|
18
|
+
### 🔴 Bug 修复
|
|
19
|
+
- **activeConnections 双重递减**: cleanup() 添加 `cleaned` 守卫防止多次触发
|
|
20
|
+
- **代理请求 timeout 未处理**: proxyReq 监听 `timeout` 事件并 `destroy()`
|
|
21
|
+
- **502 vs 504 区分**: 代理请求超时返回 504 Gateway Timeout,连接错误返回 502
|
|
22
|
+
- **startService fire-and-forget 竞态**: `serviceManager.start()` 改为 `await`,避免端口短暂可用时错误标记为 online
|
|
23
|
+
- **WebSocket handler 无条件日志修复**: `open` 回调中的 JSON.stringify 日志添加 `enableWebSocketLog` 守卫
|
|
24
|
+
- **test-startup-recovery 闲置超时测试修复**: 等待时间从 14s 增加到 16s,修复时序敏感测试偶发失败
|
|
25
|
+
|
|
26
|
+
### ✅ 代码质量
|
|
27
|
+
- **消除所有 `any` 类型**: `test-all.ts`、`test-proxy-comprehensive.ts`、`test-gateway-robustness.ts`、`test-pilot.ts`、`command-executor.ts` 中的 `any` 替换为 `unknown` + `instanceof` 类型守卫
|
|
28
|
+
- **Admin API `method.toLowerCase()` 移除**: uWS `getMethod()` 返回小写方法名,5 处冗余 `toLowerCase()` 已移除
|
|
29
|
+
- **Admin API `findServiceMapping` O(n) → O(1)**: 懒初始化 `serviceName → RouteMapping` 索引 Map
|
|
30
|
+
- **`getServicesList` 消除重复遍历**: 使用预构建的服务名称索引 Map
|
|
31
|
+
- **health-checker.ts 消除循环内 `new URL()`**: 预解析 URL 对象,传入 `host`/`port` 参数
|
|
32
|
+
- **`Buffer.alloc` → `Buffer.allocUnsafe`**: `collectRequestBody` 和 `handleDirectProxy` 中使用 `allocUnsafe` 跳过清零
|
|
33
|
+
|
|
34
|
+
### 🧪 测试(224 个用例全部通过,22 个测试套件)
|
|
35
|
+
- 新增 test-gateway-resilience.ts: 8 个网关韧性与边界深度测试
|
|
36
|
+
- 新增 test-crlf-fastpath.ts: 11 个 CRLF 安全性验证测试
|
|
37
|
+
- 新增 test-gzip-passthrough.ts: 5 个 Gzip 压缩响应透传测试
|
|
38
|
+
- 新增 test-startservice-race.ts: 6 个 startService 竞态条件测试
|
|
39
|
+
- 新增 test-port-ws-proxy.ts: 10 个端口绑定 WebSocket 代理测试
|
|
40
|
+
- 新增 test-admin-api-deep.ts: 10 个管理 API 深度测试
|
|
41
|
+
- 新增 test-gateway-boundary.ts: 10 个网关边界与安全测试
|
|
42
|
+
- 新增 test-proxy-deep.ts: 10 个代理深度与资源管理测试
|
|
43
|
+
- 新增 test-proxy-edge-paths.ts: 10 个代理边缘路径测试
|
|
44
|
+
- 新增 test-concurrent-post-body.ts: 10 个并发与竞争条件测试
|
|
45
|
+
- 新增 test-proxy-supplementary.ts: 10 个代理场景补充测试
|
|
46
|
+
- 新增 test-ws-concurrent.ts: 10 个 WebSocket 并发与稳定性测试
|
|
47
|
+
|
|
48
|
+
## [1.0.14] - 2026-03-20
|
|
49
|
+
|
|
50
|
+
### 🔴 Bug 修复
|
|
51
|
+
- **按需启动 POST 请求体丢失(严重)**: uWS 的 onData 回调中 ArrayBuffer 是借用语义,`Buffer.from(ab)` 底层数据被后续回调覆盖。改用 `Buffer.alloc + copy` 确保数据复制
|
|
52
|
+
- **并发按需启动返回 502**: 多个请求同时到达离线服务时,只有第一个触发启动,其他请求等待启动完成后再代理
|
|
53
|
+
- **transfer-encoding 头冲突**: forwardProxyRequest 中过滤 transfer-encoding 头,避免与 content-length 冲突导致后端 400
|
|
54
|
+
- **后端崩溃自动恢复**: 检测到后端不可达(ECONNREFUSED)时,自动将非 proxyOnly 服务状态重置为 offline
|
|
55
|
+
- **端口路由并发启动**: handlePortBindingRequest 补全 starting/stopping 状态处理,与 hostname 路由保持一致
|
|
56
|
+
|
|
57
|
+
### 🔒 安全加固
|
|
58
|
+
- 请求体大小限制 10MB,防止 DoS 攻击
|
|
59
|
+
- WebSocket 消息队列限制 1000 条,防止内存泄漏
|
|
60
|
+
- CRLF 注入防护:请求头值中的 `\r\n` 被清理
|
|
61
|
+
- 端口路由 WebSocket close handler 补充后端连接清理
|
|
62
|
+
|
|
63
|
+
### 🚀 性能优化
|
|
64
|
+
- **去除 undici,恢复原生 http 模块**: 吞吐量从 4,523 提升至 **5,942 req/s(+31%)**,延迟从 10.6ms 降至 **10.3ms**
|
|
65
|
+
- 预编译 CRLF 正则,避免热路径重复创建
|
|
66
|
+
- Set 替代内联条件判断,优化请求头跳过逻辑
|
|
67
|
+
|
|
68
|
+
### 🧪 测试(81 个用例全部通过)
|
|
69
|
+
- test-proxy-comprehensive.ts: 23 个综合代理测试
|
|
70
|
+
- test-edge-cases.ts: 15 个极端场景测试
|
|
71
|
+
- test-gateway-robustness.ts: 13 个健壮性测试
|
|
72
|
+
- test-admin-api-lifecycle.ts: 12 个管理 API 生命周期测试
|
|
73
|
+
- test-port-route-start.ts: 9 个端口路由按需启动测试
|
|
74
|
+
- test-security-stability.ts: 9 个安全与稳定性深度测试
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
8
78
|
## [1.0.13] - 2026-02-10
|
|
9
79
|
|
|
10
80
|
### 🚀 性能优化
|
|
11
|
-
-
|
|
12
|
-
- 吞吐量提升 **28.2%**:4345 → 5571 req/s
|
|
13
|
-
- 延迟保持 11.4ms(无退化)
|
|
14
|
-
- 使用 `undici.request()` API 实现流式转发
|
|
15
|
-
- 避免了 `undici.stream()` + Writable stream 的包装开销
|
|
81
|
+
- 使用 undici 替代原生 http 模块(已在 v1.0.14 回退,原生 http 性能更优)
|
|
16
82
|
|
|
17
83
|
### 🔧 改进
|
|
18
|
-
-
|
|
19
|
-
- 清理 RouteMapping 中的冗余字段(isHttps、httpModule、httpAgent)
|
|
84
|
+
- 清理 RouteMapping 中的冗余字段
|
|
20
85
|
- 代码更简洁,维护性更好
|
|
21
86
|
|
|
22
|
-
### 📚 技术细节
|
|
23
|
-
- undici 的 `request()` API 比 `stream()` API 更适合代理场景
|
|
24
|
-
- `stream()` 适用于消费响应(写入文件、解析 JSON)
|
|
25
|
-
- `request()` 返回 Readable stream,可以手动控制流式转发
|
|
26
|
-
- 完全保持流式处理,客户端延迟无增加
|
|
27
|
-
|
|
28
87
|
---
|
|
29
88
|
|
|
30
89
|
## [1.0.12] - 2026-02-10
|
package/CLAUDE.md
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
# DynaPM - Claude Code 项目配置
|
|
2
2
|
|
|
3
|
-
DynaPM
|
|
3
|
+
DynaPM 是一个智能网关(类似nginx)/进程管理工具(类似pm2),通过按需启动(请求到达时通过查找配置判断请求属于哪个程序,然后网关转发请求,当程序还未启动会执行启动命令等待就绪后再实际转发)和闲置自动停止的方式,帮助用户在资源受限的服务器上运行成千上万个低频访问+少量高频访问的服务。
|
|
4
4
|
|
|
5
5
|
**核心特性:**
|
|
6
|
-
- ⚡
|
|
7
|
-
- 🚀
|
|
6
|
+
- ⚡ 极速冷启动
|
|
7
|
+
- 🚀 双向流式转发代理实现极低的请求延迟:将请求体流式给代理服务,将代理服务的响应流式给请求者 所以不需要缓冲请求或者响应,唯一的需要缓冲的时机就是请求到达网关,但是需要被网关唤起的程序还没有启动成功的时候
|
|
8
8
|
- 🌐 支持 SSE 和 WebSocket
|
|
9
|
-
- 🎛️
|
|
10
|
-
- 🔄
|
|
9
|
+
- 🎛️ 基于bash的通用服务管理(可用于管理PM2、Docker、systemd 等任意服务)
|
|
10
|
+
- 🔄 闲置自动回收资源
|
|
11
11
|
|
|
12
12
|
## 技术栈
|
|
13
13
|
|
|
14
14
|
- **运行时**: Node.js 22+
|
|
15
|
-
- **Web 框架**: uWebSockets.js
|
|
16
|
-
- **日志**: Pino
|
|
15
|
+
- **Web 框架**: uWebSockets.js(最佳性能)
|
|
16
|
+
- **日志**: Pino
|
|
17
17
|
- **配置**: c12(支持 TypeScript)
|
|
18
|
-
- **构建**: rslib
|
|
19
18
|
- **包管理**: pnpm
|
|
20
19
|
- **测试**: tsx + 自定义测试套件
|
|
21
20
|
|
|
@@ -37,8 +36,6 @@ DynaPM/
|
|
|
37
36
|
│ ├── test-all.ts # 完整测试套件(12个测试)
|
|
38
37
|
│ ├── server-*.ts # 测试服务器
|
|
39
38
|
│ └── benchmark.js # 性能测试
|
|
40
|
-
├── docs/
|
|
41
|
-
│ └── NPM_OIDC_SETUP.md # npm OIDC 发布配置指南
|
|
42
39
|
├── .github/workflows/
|
|
43
40
|
│ └── release.yml # 自动发布到 npm
|
|
44
41
|
├── CHANGELOG.md # 版本更新日志
|
|
@@ -49,12 +46,7 @@ DynaPM/
|
|
|
49
46
|
|
|
50
47
|
## 开发指南
|
|
51
48
|
|
|
52
|
-
### 快速开始
|
|
53
|
-
|
|
54
49
|
```bash
|
|
55
|
-
# 安装依赖
|
|
56
|
-
pnpm install
|
|
57
|
-
|
|
58
50
|
# 运行测试
|
|
59
51
|
pnpm test
|
|
60
52
|
|
|
@@ -67,4 +59,4 @@ pnpm benchmark
|
|
|
67
59
|
|
|
68
60
|
### 发布新版本
|
|
69
61
|
|
|
70
|
-
流程 : CHANGELOG → commit
|
|
62
|
+
流程 : CHANGELOG → commit →更新 npm version (git add tag) →git push (github actions 会通过 oicd 发包 npm )
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
> **Dynamic Process Manager** - A lightweight, universal service management system with serverless-like features.
|
|
6
6
|
|
|
7
|
-
[](https://www.npmjs.com/package/dynapm) ](https://www.npmjs.com/package/dynapm)  
|
|
8
8
|
|
|
9
9
|
DynaPM is a **lightweight alternative** to complex container orchestration platforms (like Knative, Sablier) for private deployments. It helps you manage hundreds of low-frequency services on resource-constrained servers by starting them on-demand and stopping them when idle.
|
|
10
10
|
|
|
@@ -57,7 +57,7 @@ You have many side projects or internal tools that:
|
|
|
57
57
|
|
|
58
58
|
- **DynaPM overhead**: Only **25ms** (startup command: 8ms + port wait: 17ms)
|
|
59
59
|
- **Instant retry**: Zero-delay polling, forward immediately when port is ready
|
|
60
|
-
- **Total cold start**: ~
|
|
60
|
+
- **Total cold start**: ~185ms (including service boot time, e.g., ~160ms for Node.js apps)
|
|
61
61
|
|
|
62
62
|
### 🚀 **Stream Proxying**
|
|
63
63
|
|
|
@@ -147,9 +147,9 @@ Configure ANY service using bash commands - no limits:
|
|
|
147
147
|
```
|
|
148
148
|
Test Environment: Node.js HTTP Server (autocannon benchmark)
|
|
149
149
|
|
|
150
|
-
✅ Cold start: ~
|
|
151
|
-
✅ Stream proxy: Avg
|
|
152
|
-
✅ Throughput:
|
|
150
|
+
✅ Cold start: ~185ms (DynaPM: 25ms + service boot: 160ms)
|
|
151
|
+
✅ Stream proxy: Avg 10.3ms (range: 9-12ms)
|
|
152
|
+
✅ Throughput: 5,942 req/s (multi-service, 150 concurrent)
|
|
153
153
|
✅ Load test: Low latency even under high concurrency
|
|
154
154
|
✅ Memory overhead: ~50MB (Node.js runtime)
|
|
155
155
|
✅ Bundle size: 21.7KB (minified)
|
|
@@ -245,7 +245,7 @@ pnpm test
|
|
|
245
245
|
|
|
246
246
|
### Test Coverage
|
|
247
247
|
|
|
248
|
-
The automated tests validate
|
|
248
|
+
The automated tests validate 81 test cases across 6 test suites:
|
|
249
249
|
|
|
250
250
|
1. ✅ **On-demand start** - Services auto-start when offline
|
|
251
251
|
2. ✅ **Hot start** - Direct proxy when service is running
|
|
@@ -259,6 +259,9 @@ The automated tests validate 12 core functionalities:
|
|
|
259
259
|
10. ✅ **SSE streaming** - Server-Sent Events proxy support
|
|
260
260
|
11. ✅ **WebSocket** - WebSocket bidirectional communication support
|
|
261
261
|
12. ✅ **Long connections** - Active connections prevent premature shutdown
|
|
262
|
+
13. ✅ **Concurrent startup** - Multiple requests to offline service
|
|
263
|
+
14. ✅ **Security hardening** - Body size limits, CRLF injection protection
|
|
264
|
+
15. ✅ **Port routing** - Direct port-based proxy and on-demand start
|
|
262
265
|
|
|
263
266
|
### Test Output Example
|
|
264
267
|
|
|
@@ -426,8 +429,8 @@ Test: Total time from offline to first accessible request
|
|
|
426
429
|
|
|
427
430
|
Results:
|
|
428
431
|
├─ DynaPM overhead: 25ms (startup command: 8ms + TCP port wait: 17ms)
|
|
429
|
-
├─ Service boot:
|
|
430
|
-
└─ Total cold start:
|
|
432
|
+
├─ Service boot: 160ms (Node.js application)
|
|
433
|
+
└─ Total cold start: 185ms
|
|
431
434
|
```
|
|
432
435
|
|
|
433
436
|
### Stream Proxy Performance
|
|
@@ -436,30 +439,23 @@ Results:
|
|
|
436
439
|
Test: Single request latency when service is running
|
|
437
440
|
|
|
438
441
|
Results:
|
|
439
|
-
├─ Average latency:
|
|
440
|
-
├─ Min latency:
|
|
442
|
+
├─ Average latency: 10.3ms
|
|
443
|
+
├─ Min latency: 9ms
|
|
441
444
|
├─ Max latency: 12ms
|
|
442
|
-
└─ Latency range:
|
|
445
|
+
└─ Latency range: 9-12ms
|
|
443
446
|
```
|
|
444
447
|
|
|
445
448
|
### Throughput Performance
|
|
446
449
|
|
|
447
450
|
```
|
|
448
|
-
Test: Multi-service benchmark (3 services ×
|
|
451
|
+
Test: Multi-service benchmark (3 services × 50 concurrent, 5 seconds)
|
|
449
452
|
|
|
450
453
|
Results:
|
|
451
|
-
├─ Total requests:
|
|
452
|
-
├─ Average throughput:
|
|
453
|
-
├─ Per-service:
|
|
454
|
+
├─ Total requests: 29,710 requests
|
|
455
|
+
├─ Average throughput: 5,942 req/s
|
|
456
|
+
├─ Per-service: ~1,980 req/s
|
|
454
457
|
├─ Errors: 0
|
|
455
458
|
└─ Test duration: 5 seconds
|
|
456
|
-
|
|
457
|
-
Test: Single service benchmark (50 concurrent, 5 seconds)
|
|
458
|
-
|
|
459
|
-
Results:
|
|
460
|
-
├─ Requests/sec: 4,225+ req/s
|
|
461
|
-
├─ Average latency: ~23ms
|
|
462
|
-
└─ Total requests: 21k requests
|
|
463
459
|
```
|
|
464
460
|
|
|
465
461
|
### Resource Usage
|
package/README_zh.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
> **动态进程管理器** - 具有类 serverless 特性的轻量级通用服务管理系统
|
|
6
6
|
|
|
7
|
-
[](https://www.npmjs.com/package/dynapm) ](https://www.npmjs.com/package/dynapm)  
|
|
8
8
|
|
|
9
9
|
DynaPM 是**复杂容器编排平台(如 Knative、Sablier)的轻量级替代方案**,专为私有化部署设计。它通过按需启动和闲置自动停止的方式,帮助你在资源受限的服务器上管理数百个低频访问的服务。
|
|
10
10
|
|
|
@@ -57,7 +57,7 @@ DynaPM 是**复杂容器编排平台(如 Knative、Sablier)的轻量级替
|
|
|
57
57
|
|
|
58
58
|
- **DynaPM 开销**:仅 **25ms**(启动命令 8ms + 端口等待 17ms)
|
|
59
59
|
- **失败立即重试**:无延迟轮询,端口可用即刻转发
|
|
60
|
-
- **总冷启动**:~
|
|
60
|
+
- **总冷启动**:~185ms(包括服务本身启动时间,如 Node.js 应用 ~160ms)
|
|
61
61
|
|
|
62
62
|
### 🚀 **流式代理**
|
|
63
63
|
|
|
@@ -146,9 +146,9 @@ DynaPM 原生支持现代实时通信协议:
|
|
|
146
146
|
```
|
|
147
147
|
测试环境:Node.js HTTP 服务器 (autocannon 压测)
|
|
148
148
|
|
|
149
|
-
✅ 冷启动时间: ~
|
|
150
|
-
✅ 流式代理延迟: 平均
|
|
151
|
-
✅ 吞吐量:
|
|
149
|
+
✅ 冷启动时间: ~185ms (DynaPM: 25ms + 服务启动: 160ms)
|
|
150
|
+
✅ 流式代理延迟: 平均 10.3ms (范围: 9-12ms)
|
|
151
|
+
✅ 吞吐量: 5,942 req/s (多服务, 150 并发)
|
|
152
152
|
✅ 压测延迟: 高并发下保持低延迟
|
|
153
153
|
✅ 内存开销: ~50MB (Node.js 运行时)
|
|
154
154
|
✅ 代码体积: 21.7KB (压缩后)
|
|
@@ -244,7 +244,7 @@ pnpm test
|
|
|
244
244
|
|
|
245
245
|
### 测试覆盖场景
|
|
246
246
|
|
|
247
|
-
自动化测试会验证以下
|
|
247
|
+
自动化测试会验证以下 81 个测试用例(6 个测试套件):
|
|
248
248
|
|
|
249
249
|
1. ✅ **按需启动** - 服务离线时自动启动
|
|
250
250
|
2. ✅ **热启动** - 服务运行时直接代理,无需重新启动
|
|
@@ -258,6 +258,9 @@ pnpm test
|
|
|
258
258
|
10. ✅ **SSE 流式传输** - Server-Sent Events 代理支持
|
|
259
259
|
11. ✅ **WebSocket** - WebSocket 双向通信支持
|
|
260
260
|
12. ✅ **长连接代理** - 活跃连接阻止服务被提前关闭
|
|
261
|
+
13. ✅ **并发启动** - 多个请求同时到达离线服务
|
|
262
|
+
14. ✅ **安全加固** - 请求体大小限制、CRLF 注入防护
|
|
263
|
+
15. ✅ **端口路由** - 端口直连代理和按需启动
|
|
261
264
|
|
|
262
265
|
### 测试输出示例
|
|
263
266
|
|
|
@@ -424,8 +427,8 @@ npm install -g autocannon
|
|
|
424
427
|
|
|
425
428
|
结果:
|
|
426
429
|
├─ DynaPM 开销: 25ms (启动命令 8ms + TCP 端口等待 17ms)
|
|
427
|
-
├─ 服务启动时间:
|
|
428
|
-
└─ 总冷启动时间:
|
|
430
|
+
├─ 服务启动时间: 160ms (Node.js 应用)
|
|
431
|
+
└─ 总冷启动时间: 185ms
|
|
429
432
|
```
|
|
430
433
|
|
|
431
434
|
### 流式代理性能
|
|
@@ -434,22 +437,22 @@ npm install -g autocannon
|
|
|
434
437
|
测试:服务运行时的单次请求延迟
|
|
435
438
|
|
|
436
439
|
结果:
|
|
437
|
-
├─ 平均延迟:
|
|
438
|
-
├─ 最小延迟:
|
|
440
|
+
├─ 平均延迟: 10.3ms
|
|
441
|
+
├─ 最小延迟: 9ms
|
|
439
442
|
├─ 最大延迟: 12ms
|
|
440
|
-
└─ 延迟范围:
|
|
443
|
+
└─ 延迟范围: 9-12ms
|
|
441
444
|
```
|
|
442
445
|
|
|
443
446
|
### 吞吐量性能
|
|
444
447
|
|
|
445
448
|
```
|
|
446
|
-
|
|
449
|
+
测试:多服务混合压测 (3 服务 × 50 并发, 5 秒)
|
|
447
450
|
|
|
448
451
|
结果:
|
|
449
|
-
├─ 请求数/秒:
|
|
450
|
-
├─ 平均延迟:
|
|
451
|
-
├─ 总请求数:
|
|
452
|
-
└─ 测试时长:
|
|
452
|
+
├─ 请求数/秒: 5,942 req/s
|
|
453
|
+
├─ 平均延迟: ~10ms
|
|
454
|
+
├─ 总请求数: 29,710 requests
|
|
455
|
+
└─ 测试时长: 5 秒
|
|
453
456
|
```
|
|
454
457
|
|
|
455
458
|
### 资源占用
|
package/TASK.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
具有类 serverless 特性的轻量级通用服务管理系统:dynapm
|
|
2
|
+
## dynapm 开发
|
|
3
|
+
|
|
4
|
+
/loop 先检查 TASK.md 中是否有未完成的任务请逐项完成并在充分test验证再继续下一项,如果没有则请请完善当前项目:监测并优化程序性能,修改完毕后需要进行实际运行测试,请自我完善,不要询问我任何事情,也不要切换其他模式(例如 plan mode)
|
|
5
|
+
作为网关的测试一定要非常严谨,测试各种可能的情况以及极端情况。
|
|
6
|
+
所有文件使用 ts,需要临时运行的使用 node --experimental-strip-types -e xxx.ts 来执行
|
|
7
|
+
这一次主要考虑性能,要在不破坏所有功能的前提下优化性能,但是不要为了优化而优化,必须经过仔细的评估,不要无脑上缓存,缓存很容易出bug,否则可读性更强。然后咱们的网关可能已经到达了一般情况下的上限,所以你可能需要去互联网上查找node的一些大神使用的技巧
|
|
8
|
+
|
|
9
|
+
## TASKS
|
|
10
|
+
|
|
11
|
+
[x] 请在各个层面完善一下 serveless host 这个功能,尤其是前端,太low了,至少也得有 https://www.typescriptlang.org/play/ 这种水准的编辑和运行体验吧
|
|
12
|
+
[x] 充分测试当前的代理功能是否正确
|
|
13
|
+
[x] 创建一个实用的dynam能力演示程序:实现一个运行ts/js的 serveless host(并不属于 dynapm,但是可以被 dynapm 运行,然后请求又可以被这个 serveless host 路由到 对应的 ts文件去执行):支持用户通过网站访问并编写 ts 上传执行和测试执行
|
|
14
|
+
|
|
15
|
+
## 已完成的工作
|
|
16
|
+
|
|
17
|
+
### 2026-03-20
|
|
18
|
+
|
|
19
|
+
#### 网关 Bug 修复
|
|
20
|
+
- **并发按需启动修复**: 多个请求同时到达离线服务时,只有第一个触发启动,其他请求等待启动完成后再代理(之前返回 502)
|
|
21
|
+
- **后端崩溃自动恢复**: 当 handleDirectProxy 检测到后端不可达(ECONNREFUSED),自动将非 proxyOnly 服务状态重置为 offline,后续请求可重新触发按需启动
|
|
22
|
+
- **端口路由并发启动修复**: handlePortBindingRequest 补全 starting/stopping 状态处理,与 hostname 路由保持一致
|
|
23
|
+
- **echo-server 支持命令行端口参数**: `parseInt(process.argv[2] || '3099')`,允许不同测试配置使用不同端口
|
|
24
|
+
- **按需启动 POST 请求体丢失修复(严重)**: uWS 的 onData 回调中 ArrayBuffer 是借用语义,`Buffer.from(ab)` 底层数据被后续回调覆盖。改用 `Buffer.alloc + copy` 确保数据复制
|
|
25
|
+
- **transfer-encoding 头冲突修复**: forwardProxyRequest 中过滤 transfer-encoding 头,避免与 content-length 冲突导致后端 400
|
|
26
|
+
- **请求体大小限制(10MB)**: collectRequestBody 超过限制时截断,防止 DoS 攻击
|
|
27
|
+
- **WebSocket 消息队列限制(1000)**: 防止后端未连接时消息无限堆积导致内存泄漏
|
|
28
|
+
- **端口路由 WebSocket close 清理**: 端口路由的 close handler 补充后端 WebSocket 关闭逻辑
|
|
29
|
+
|
|
30
|
+
#### 性能优化
|
|
31
|
+
- **预编译 CRLF 正则**: `GatewayConstants.CRLF_REGEX` 避免热路径中重复创建正则对象
|
|
32
|
+
- **Set 替代内联条件**: `GatewayConstants.SKIP_REQUEST_HEADERS` 使用 Set.has() 替代重复 toLowerCase + 条件判断
|
|
33
|
+
- **预计算 targetPort**: RouteMapping 中缓存目标端口,避免热路径中 parseInt 解析
|
|
34
|
+
|
|
35
|
+
#### 网关稳定性修复
|
|
36
|
+
- **activeConnections 双重递减修复**: handleDirectProxy 和 forwardProxyRequest 中 cleanup() 添加 `cleaned` 守卫,防止 onAborted/proxyReq error/proxyRes end 多次触发导致 activeConnections 变为负数,进而导致闲置超时永远不触发
|
|
37
|
+
- **代理请求超时处理**: proxyReq 添加 `timeout` 事件监听,超时后调用 `destroy()` 触发 error 事件正确返回 502。之前 timeout 事件未被处理,导致后端慢响应时客户端无限等待
|
|
38
|
+
|
|
39
|
+
#### 测试覆盖(224 个测试全部通过)
|
|
40
|
+
- **test-proxy-comprehensive.ts**: 23 个综合代理测试
|
|
41
|
+
- **test-advanced-proxy.ts**: 12 个高级代理场景测试(PUT/PATCH/DELETE 请求体转发、HEAD 无响应体、OPTIONS CORS、空 POST、根路径、查询参数特殊字符、30 个自定义头、Host 头覆盖、流式响应、快速连续请求、活跃服务闲置测试、WS+HTTP 并发)
|
|
42
|
+
- **test-edge-cases.ts**: 15 个极端场景测试
|
|
43
|
+
- **test-gateway-robustness.ts**: 13 个健壮性测试
|
|
44
|
+
- **test-admin-api-lifecycle.ts**: 12 个管理 API 生命周期测试
|
|
45
|
+
- **test-admin-api-deep.ts**: 10 个管理 API 深度与网关边界测试(新增)
|
|
46
|
+
- 管理 API 事件流 (SSE)
|
|
47
|
+
- 管理 API 路由边界(PUT/DELETE 404、路径遍历、不存在 API)
|
|
48
|
+
- 请求体超过 10MB 截断(按需启动路径)
|
|
49
|
+
- 3xx 重定向 Location 头透传
|
|
50
|
+
- 50 个并发请求同时断开
|
|
51
|
+
- 服务按需启动超时行为
|
|
52
|
+
- 非 JSON Content-Type POST
|
|
53
|
+
- 管理 API 并发请求 (40个)
|
|
54
|
+
- OPTIONS 预检请求
|
|
55
|
+
- 网关直接访问返回 404
|
|
56
|
+
- **test-gateway-boundary.ts**: 10 个网关边界与安全深度测试(新增)
|
|
57
|
+
- CRLF 注入防护验证
|
|
58
|
+
- 并发按需启动竞争 (20个)
|
|
59
|
+
- 大响应体流式转发 (1MB)
|
|
60
|
+
- URL 特殊字符透传(中文、编码)
|
|
61
|
+
- 超长请求头值 (16KB)
|
|
62
|
+
- 响应头大小写兼容
|
|
63
|
+
- 重复请求头处理
|
|
64
|
+
- 快速连续请求到不同路径
|
|
65
|
+
- 连接超时后网关稳定性
|
|
66
|
+
- 多服务并发代理 (20个)
|
|
67
|
+
- **test-proxy-deep.ts**: 10 个代理深度与资源管理测试(新增)
|
|
68
|
+
- 慢响应时客户端断开 activeConnections 准确性(含闲置超时验证)
|
|
69
|
+
- stopping 状态下收到请求(等待停止完成后启动)
|
|
70
|
+
- WebSocket 消息队列溢出 (1200条)
|
|
71
|
+
- PATCH/PUT 请求体转发完整性
|
|
72
|
+
- 分块传输响应体转发
|
|
73
|
+
- 带查询参数的 POST 请求
|
|
74
|
+
- 多次快速启停状态一致性 (5轮)
|
|
75
|
+
- 长连接 keep-alive 稳定性 (100个)
|
|
76
|
+
- 后端 500 错误网关不崩溃
|
|
77
|
+
- 50 个错误请求后网关稳定性
|
|
78
|
+
- **test-proxy-edge-paths.ts**: 10 个代理边缘路径与错误恢复测试(新增)
|
|
79
|
+
- 后端响应超时处理
|
|
80
|
+
- 服务启动失败后重试
|
|
81
|
+
- 后端立即关闭连接
|
|
82
|
+
- 大量 502 后网关恢复 (30个)
|
|
83
|
+
- 服务正在启动时收到请求 (5个)
|
|
84
|
+
- 二进制请求体传输
|
|
85
|
+
- 根路径请求
|
|
86
|
+
- 特殊编码 URL 路径
|
|
87
|
+
- 服务详情字段完整性
|
|
88
|
+
- 网关端口扫描防护 (100个)
|
|
89
|
+
- **test-concurrent-post-body.ts**: 10 个并发与竞争条件测试
|
|
90
|
+
- **test-port-route-start.ts**: 9 个端口路由按需启动测试
|
|
91
|
+
- **test-security-stability.ts**: 9 个安全与稳定性深度测试
|
|
92
|
+
- **test-startup-recovery.ts**: 7 个服务启动失败恢复测试
|
|
93
|
+
- **test-proxy-supplementary.ts**: 10 个代理场景补充测试
|
|
94
|
+
- Set-Cookie 响应头转发
|
|
95
|
+
- 自定义响应头透传(X-Custom-Response、X-Rate-Limit、Cache-Control)
|
|
96
|
+
- 204 No Content 响应
|
|
97
|
+
- 分块传输响应
|
|
98
|
+
- GET 请求不应有 body
|
|
99
|
+
- Content-Type 多样性(json/plain/octet-stream)
|
|
100
|
+
- 并发连接后网关不崩溃
|
|
101
|
+
- 非 ASCII 响应体
|
|
102
|
+
- 快速连续启停后代理正常
|
|
103
|
+
- **test-pilot.ts**: 16 个 Pilot 实际运行测试(使用 dynapm.config.ts 生产配置)
|
|
104
|
+
- **test-ws-concurrent.ts**: 10 个 WebSocket 并发与稳定性测试(新增)
|
|
105
|
+
- **test-gateway-resilience.ts**: 8 个网关韧性与边界深度测试(新增)
|
|
106
|
+
- 20 个并发 WebSocket 连接
|
|
107
|
+
- 10 个并发 WebSocket ping/pong
|
|
108
|
+
- WebSocket 较大消息传输 (10KB)
|
|
109
|
+
- WebSocket 二进制消息传输
|
|
110
|
+
- 多连接消息顺序保证 (10条)
|
|
111
|
+
- 快速连接/断开循环 (30次)
|
|
112
|
+
- 服务停止后连接清理
|
|
113
|
+
- WebSocket + HTTP 混合并发
|
|
114
|
+
- WebSocket 活跃连接阻止闲置停止
|
|
115
|
+
- WebSocket 按需启动
|
|
116
|
+
- **test-post-body-fix.ts**: 12 个 POST 请求体完整性测试(已整合到 test-concurrent-post-body.ts)
|
|
117
|
+
|
|
118
|
+
#### echo-server 修复
|
|
119
|
+
- **HEAD 请求不返回 body**: 包装 res.end 使 HEAD 请求忽略 data 参数,修复 node:http 客户端 HTTP 解析错误
|
|
120
|
+
- **3xx 重定向 Location 头**: handleStatus 端点在 3xx 状态码时返回 Location 头
|
|
121
|
+
|
|
122
|
+
#### server-ws.ts 修复
|
|
123
|
+
- **WebSocket isBinary 参数错误**: `ws.send(JSON.stringify(...), true, false)` 中 `isBinary=true` 传入 string 导致连接状态异常。移除多余的 `true, false` 参数,使用 uWS 默认值
|
|
124
|
+
|
|
125
|
+
#### 代码质量优化(第十三轮 2026-03-22)
|
|
126
|
+
- **`Array.isArray` → `typeof` 优化**: 网关响应头转发中 4 处 `Array.isArray(value) ? value.join(', ') : value` 替换为 `typeof value === 'string' ? value : value.join(', ')`。微基准验证 `typeof` 比 `Array.isArray` 快 41.2%(10ms vs 17ms,500 万次迭代),因为 `typeof` 是 V8 内置类型检查,无需遍历原型链
|
|
127
|
+
- **V8 微基准全面验证**: 系统性测试了 ProxyState 对象创建(1ns/op)、headers 迭代(for...in 最优,比 Object.keys 快 43.8%)、hostname 提取(substring vs slice 差异 0.1ns/req)、http.request options 创建(1.5ns/op)。结论:当前所有热路径写法已是 V8 最优
|
|
128
|
+
- **test-startup-recovery.ts 闲置超时等待修复**: 等待时间从 14s 增加到 16s(10s idleTimeout + 3s 检查间隔 + 3s 停止执行 buffer),修复时序敏感测试偶发失败
|
|
129
|
+
- **224 个测试全部通过**(22 个测试套件),基准测试 5,368 req/s(wrk -t4 -c50 -d10s),性能无退化
|
|
130
|
+
|
|
131
|
+
#### 代码质量优化(第十二轮 2026-03-22)
|
|
132
|
+
- **消除 `any` 类型**: 修复 `test-all.ts`、`test-proxy-comprehensive.ts`、`test-gateway-robustness.ts`、`test-pilot.ts`、`command-executor.ts` 中的 `any` 类型,替换为 `unknown` + `instanceof` 类型守卫或具体接口类型(`{ name?: string }`)
|
|
133
|
+
- **`for...in` vs `Object.keys()` 微基准验证**: 100 万次迭代测试显示 `Object.keys()` 比 `for...in` 慢 43.8%(391ms vs 272ms),确认当前 `for...in` 写法是最优选择,不做替换
|
|
134
|
+
- **互联网调研 Node.js 性能技巧**: 研究了 Node.js 22+ HTTP Agent 调优(`agentKeepAliveTimeoutBuffer`、`maxSockets`、`scheduling`)、DNS 缓存(`cacheable-lookup`)、V8 Maglev 编译器友好代码模式、`Buffer.allocUnsafeSlow` 等。结论:自定义 HTTP Agent 已在第五轮测试中验证(QPS 下降 14.6%),DNS 直连对 `127.0.0.1` 无意义(已是 IP),`for...in` 已是最优
|
|
135
|
+
- **224 个测试全部通过**(22 个测试套件),基准测试 5,193 req/s(wrk -t4 -c50 -d10s),性能无退化
|
|
136
|
+
|
|
137
|
+
#### 代码质量与性能优化(第十一轮 2026-03-22)
|
|
138
|
+
- **404 路径提前返回优化**: `handleRequest` 中将 `hostnameRoutes.get(hostname)` 检查提前到 `req.forEach` header 收集之前。对 404 请求(未知 hostname)跳过 header 遍历和 `Record<string, string>` 对象分配,减少不必要的 CPU 和内存开销
|
|
139
|
+
- **212 个测试全部通过**(21 个测试套件),基准测试 5,132 req/s(wrk -t4 -c50 -d10s),性能无退化
|
|
140
|
+
|
|
141
|
+
#### 代码质量与性能优化(第十轮 2026-03-22)
|
|
142
|
+
- **`getMethod()` → `getCaseSensitiveMethod()` 消除热路径 `toUpperCase()`**: uWS 的 `getMethod()` 返回小写方法名(如 `get`),传给 `http.request()` 前需要 `toUpperCase()` 转为大写。改为直接使用 `getCaseSensitiveMethod()` 获取原始大小写方法名(如 `GET`),消除 `handleRequest`、`handlePortBindingRequest` 入口处和 `handleDirectProxy`、`forwardProxyRequest` 中共 2 处 `toUpperCase()` 调用
|
|
143
|
+
- **test-crlf-fastpath.ts `rawTcpRequestBytes` EPIPE 修复**: 原实现在连接回调中同步写入所有 buffer 后才注册 error handler。当 uWS 拒绝畸形请求并关闭连接时,后续 `socket.write()` 触发 `EPIPE`。改为先注册 error handler,将 EPIPE 视为正常响应(服务端关闭连接),并在写入前检查 `socket.destroyed`
|
|
144
|
+
- **212 个测试全部通过**(21 个测试套件),基准测试 5,132 req/s(wrk -t4 -c50 -d10s),性能无退化
|
|
145
|
+
|
|
146
|
+
#### 代码质量与性能优化(第九轮 2026-03-22)
|
|
147
|
+
- **test-startup-recovery.ts `ensureEchoOffline` 测试 bug 修复**: 当服务已经是 offline 时,原代码发请求尝试重置状态,但反而触发了按需启动。改为先通过 admin API 查询服务状态,仅在 online/stopping 时才发请求触发 ECONNREFUSED 重置
|
|
148
|
+
- **移除未使用的 `getTargetHostPort` 辅助函数**: gateway.ts 中的 `getTargetHostPort` 已被内联为直接属性访问 `mapping.targetUrl!.hostname` 和 `mapping.targetPort`,删除未使用的函数定义
|
|
149
|
+
- **全量回归测试 212 个用例全部通过**(21 个测试套件顺序运行)
|
|
150
|
+
- **基准测试验证**: 5,347 req/s(wrk -t4 -c50 -d10s),微基准 P50 纯代理开销 0.286ms,与之前基准一致无退化
|
|
151
|
+
- **性能优化调研结论**: 经过互联网调研(uWS 最佳实践、Node.js 22+ 网络优化、V8 引擎优化)和代码审查,确认所有已知的 JS 层优化已实施,网关已到达 HTTP 协议双重解析的理论极限。剩余可能的优化(highWaterMark 调优、Buffer 预分配、V8 hidden classes 一致性)均为微优化,收益 < 1%
|
|
152
|
+
|
|
153
|
+
#### 代码质量与性能优化(第七轮 2026-03-22)
|
|
154
|
+
- **admin-api.ts `startService` fire-and-forget 竞态条件修复(正确性 bug)**: `serviceManager.start()` 原来是 fire-and-forget(不 await),如果启动命令失败但端口短暂可用,TCP 就绪循环会将状态错误地标记为 online。改为先 `await start()` 完成后再做 TCP 就绪检查
|
|
155
|
+
- **WebSocket handler 无条件 JSON.stringify 日志修复**: hostname 路由的 WebSocket `open` 回调中有一行 `JSON.stringify(backendHeaders, null, 2)` 日志没有 `enableWebSocketLog` 守卫,每次 WebSocket 连接都执行。改为仅在 `enableWebSocketLog` 开启时记录
|
|
156
|
+
- **CRLF 替换快速路径**: 热路径 `handleRequest` 和 `handlePortBindingRequest` 的 headers 收集中,先用 `value.includes('\r') || value.includes('\n')` 快速检查是否需要正则替换。正常请求(99.99%+)直接跳过 `replace()` 调用,节省 ~400-1800ns/req
|
|
157
|
+
- **fullUrl 拼接方式统一**: `handlePortBindingRequest` 中的模板字符串 `${url}?${queryString}` 改为字符串拼接 `url + '?' + queryString`,与 `handleRequest` 保持一致
|
|
158
|
+
- **移除 node-fetch 依赖**: `health-checker.ts` 中 `import fetch from 'node-fetch'` 改为使用 Node.js 22 内置的全局 `fetch` API。`pnpm remove node-fetch` 移除生产依赖,减小安装体积
|
|
159
|
+
- **212 个扩展测试全部通过**,基准测试 5,347 req/s(wrk -t4 -c50 -d10s),冷启动 201ms
|
|
160
|
+
|
|
161
|
+
#### 新增测试套件(第八轮 2026-03-22)
|
|
162
|
+
- **test-crlf-fastpath.ts**: 11 个 CRLF 安全性验证测试(新增)
|
|
163
|
+
- 正常原始 TCP 请求(验证 chunked 响应解析正确性)
|
|
164
|
+
- CRLF 快速路径正常请求不受影响
|
|
165
|
+
- URL 路径特殊字符安全
|
|
166
|
+
- uWS 层安全:裸 \n 被 uWS 拒绝 (400)
|
|
167
|
+
- uWS 层安全:裸 \r 被 uWS 拒绝或忽略
|
|
168
|
+
- uWS 层安全:\r\n+非法行被 uWS 拒绝 (400)
|
|
169
|
+
- uWS 层安全:\r\n+合法头被 uWS 解析为独立头(标准 HTTP 行为)
|
|
170
|
+
- uWS 层安全:\r\n+多个注入头解析
|
|
171
|
+
- CRLF 不产生额外响应头(响应头注入防护)
|
|
172
|
+
- 响应头注入防护
|
|
173
|
+
- 20 个并发 CRLF 请求不崩溃
|
|
174
|
+
- **test-gzip-passthrough.ts**: 5 个 Gzip 压缩响应透传测试(新增)
|
|
175
|
+
- gzip Content-Encoding 头透传(hostname 路由)
|
|
176
|
+
- gzip 响应体可解压验证
|
|
177
|
+
- 无 Accept-Encoding 时 gzip 透传
|
|
178
|
+
- 多 Accept-Encoding 时 gzip 透传
|
|
179
|
+
- 端口路由 gzip 响应透传
|
|
180
|
+
- **test-startservice-race.ts**: 6 个 startService 竞态条件修复验证测试(新增)
|
|
181
|
+
- startService 正常启动
|
|
182
|
+
- startService 后代理功能正常
|
|
183
|
+
- starting 状态重复调用返回 400
|
|
184
|
+
- online 状态调用返回 400
|
|
185
|
+
- startCount 正确递增
|
|
186
|
+
- 启动超时机制验证
|
|
187
|
+
- **test-port-ws-proxy.ts**: 10 个端口绑定 WebSocket 代理测试(新增)
|
|
188
|
+
- 端口路由 WS 基本连接与消息收发
|
|
189
|
+
- 端口路由 WS 二进制消息
|
|
190
|
+
- 端口路由 WS 较大消息 (10KB)
|
|
191
|
+
- 端口路由 WS 并发连接 (10个)
|
|
192
|
+
- 端口路由 WS 快速连接/断开循环 (20次)
|
|
193
|
+
- 端口路由 WS + HTTP 混合并发
|
|
194
|
+
- 端口路由 WS 按需启动
|
|
195
|
+
- 端口路由 WS 消息队列
|
|
196
|
+
- 端口路由 WS 长连接稳定性 (5s)
|
|
197
|
+
- 端口路由 WS 后端崩溃后连接清理
|
|
198
|
+
|
|
199
|
+
#### CRLF 安全架构分析结论
|
|
200
|
+
- **双层安全模型**: uWS HTTP 解析器(第一层)+ 网关 CRLF 清理(第二层)
|
|
201
|
+
- **uWS 解析器**: 裸 \n / \r 违反 HTTP 规范,uWS 直接返回 400 Bad Request
|
|
202
|
+
- **\r\n 行为**: uWS 将 \r\n 解析为 HTTP 头分隔符(标准行为),\r\n+合法头成为独立头,\r\n+非法行导致 400
|
|
203
|
+
- **网关 CRLF 清理**: 对 `req.forEach` 迭代的每个 header value 做防御性 `[\r\n]` 替换,保护通过程序化路径(如中间件)传入的脏数据
|
|
204
|
+
- **快速路径优化**: `value.includes('\r') || value.includes('\n')` 先检查,正常请求(99.99%+)跳过正则替换
|
|
205
|
+
|
|
206
|
+
#### 代码质量与性能优化(第六轮 2026-03-22)
|
|
207
|
+
- **admin-api.ts 删除重复的 `checkTcpPort` 函数**: 与 gateway.ts 中的实现功能完全相同,且每次调用都 `new URL()` 解析。改为在 `startService` 循环外预解析 URL,内联 TCP 检查逻辑
|
|
208
|
+
- **health-checker.ts 消除循环内 `new URL()` 冗余解析**: `wait` 方法在循环外预解析 `service.base` 为 `targetHost` 和 `targetPort`,传入 `checkTcp` 方法。`checkTcp` 签名改为直接接收 host/port 参数
|
|
209
|
+
- **admin-api.ts `method.toLowerCase()` 全部移除**: uWS `getMethod()` 返回小写方法名,5 处 `method.toLowerCase()` 是冗余操作。直接比较 `method === 'get'` / `method === 'post'`
|
|
210
|
+
- **168 个扩展测试全部通过**,基准测试 4,680 req/s(正常波动范围)
|
|
211
|
+
|
|
212
|
+
#### 代码质量与性能优化(第五轮 2026-03-22)
|
|
213
|
+
- **`Buffer.alloc` → `Buffer.allocUnsafe`**: `collectRequestBody` 和 `handleDirectProxy` 的 `onData` 回调中,`Buffer.alloc` 会先 memset 清零再被 `copy()` 覆盖。改用 `allocUnsafe` 跳过清零,减少每次回调的 CPU 开销。安全性分析:`Buffer.from(ab).copy(chunk)` 立即覆盖所有字节,不存在数据泄露风险
|
|
214
|
+
- **WebSocket headers 构建优化**: 两处 WebSocket `open` 回调中的 `Object.entries(clientHeaders)` + `key.toLowerCase()` 改为 `for...in` + 直接 `has(key)`。uWS `req.forEach` 的 key 已是小写,`WS_SKIP_HEADERS` 的 key 也是小写
|
|
215
|
+
- **复用 `startTime` 替代第二次 `Date.now()`**: `handleRequest` 和 `handlePortBindingRequest` 中 `service._state!.lastAccessTime` 直接使用 `startTime`,省掉一次系统调用(idle checker 精度 3 秒,差异可忽略)
|
|
216
|
+
- **代理请求超时返回 504 而非 502(功能性修复)**: `proxyReq.on('timeout')` 设置 `state.timedOut` 标志,`error` handler 据此区分返回 504 Gateway Timeout(超时)和 502 Bad Gateway(连接错误)。`handleDirectProxy` 和 `forwardProxyRequest` 两处修复
|
|
217
|
+
- **专用 HTTP Agent 评估**: 创建 `PROXY_AGENT`(maxSockets:256, maxFreeSockets:32)后基准测试显示 QPS 从 4,936 降到 4,214。原因:DynaPM 后端全在 localhost,TCP 握手 ~50us 极快,`maxSockets:Infinity` 不是问题;而 `maxFreeSockets:32`(默认 256)导致空闲连接频繁回收重建。已回退使用 `globalAgent`
|
|
218
|
+
- **TCP_NODELAY 确认**: Node.js v18+ 的 `http.ClientRequest` 默认已启用 TCP_NODELAY,无需额外设置
|
|
219
|
+
- **168 个扩展测试全部通过**,基准测试 4,954 req/s(3 服务×50 并发),P50 延迟 ~31ms
|
|
220
|
+
|
|
221
|
+
#### 代码质量与性能优化(第四轮 2026-03-22)
|
|
222
|
+
|
|
223
|
+
#### 代码质量与性能优化(第三轮 2026-03-22)
|
|
224
|
+
- **checkTcpPort 消除每次调用的 `new URL()` 开销**: 改为直接接收 `host` 和 `port` 参数,利用已缓存的 `RouteMapping.targetUrl`/`targetPort`;WebSocket 启动等待中也提前创建 `targetUrl` 避免重复解析
|
|
225
|
+
- **Admin API `findServiceMapping` O(n) → O(1)**: 构建懒初始化的 `serviceName → RouteMapping` 索引 Map,替代每次请求的线性遍历
|
|
226
|
+
- **Admin API `getServicesList` 消除重复遍历**: 使用预构建的服务名称索引 Map,替代每次请求时遍历所有路由表构建去重列表
|
|
227
|
+
- **178 个测试全部通过**(含新增的 8 个网关韧性测试)
|
|
228
|
+
|
|
229
|
+
#### 性能优化(第二轮 2026-03-21)
|
|
230
|
+
- **消除响应头过滤冗余 toLowerCase**: `proxyRes.headers` 的 key 已经是小写的,4 处 `.toLowerCase()` 调用是冗余的,移除后代码更正确
|
|
231
|
+
- **微基准验证**: CRLF 替换 includes 优化 13.5x(正常值)、一次遍历合并节省 22.6%——但综合影响 < 1% 的总延迟
|
|
232
|
+
- **确认 node:http 22 默认 globalAgent 已最优**: `keepAlive: true, maxSockets: Infinity`
|
|
233
|
+
- **架构瓶颈确认**: 所有 JS 层面微优化合计节省 ~300ns/req,在 34ms 延迟中占比 < 1%
|
|
234
|
+
- **最终结论**: 网关已接近 node:http 出站连接的理论极限,瓶颈在 HTTP 协议双重解析(客户端→uWS + node:http→后端)
|
|
235
|
+
- 基准测试:4,936 req/s(3 服务×50 并发),P50 延迟 ~31ms,170 个测试全部通过
|
|
236
|
+
|
|
237
|
+
#### 性能评估结论
|
|
238
|
+
- 网关纯代理开销 P50=0.006ms(6μs),在测量误差范围内,远低于 node:http 协议解析开销
|
|
239
|
+
- 1000 请求延迟剖析:TTFB P50=0.336ms,Total P50=0.360ms,Body overhead 仅 0.024ms
|
|
240
|
+
- 微基准测试:所有热路径操作均在亚微秒级别(Map.get 27ns、Set.has 28ns、CRLF replace 73ns、Buffer.alloc+copy 787ns)
|
|
241
|
+
- Buffer.alloc+copy 是最昂贵操作(787ns/op),但这是 uWS ArrayBuffer 借用语义的必要成本,无法优化
|
|
242
|
+
- 基准测试:冷启动 255ms、单请求延迟 9.9ms、3 服务×50 并发吞吐量 5,260 req/s
|
|
243
|
+
- **无数量级优化空间**: 瓶颈在 node:http 的 HTTP 协议双重解析(客户端→网关 + 网关→后端),不是网关代码
|
|
244
|
+
- node:http keep-alive 出站 0.097ms/req,新建连接 0.252ms/req,网关已利用 keep-alive
|
|
245
|
+
- **net.Socket 替代方案不可行**: 测试显示 net.Socket 0.508ms/req(无 keep-alive),反而更慢;uWS 未暴露 `us_socket_context_connect`
|
|
246
|
+
- undici 与 uWS 流式模型不兼容,不可用作替代方案
|
|
247
|
+
- Pilot 实际运行测试:16/16 全部通过(使用 dynapm.config.ts 生产配置)
|
|
248
|
+
- WebSocket 并发测试:10/10 全部通过(20 并发连接、混合 WS+HTTP、按需启动、闲置保护)
|
|
249
|
+
|
|
250
|
+
#### Serverless Host 演示
|
|
251
|
+
- test/services/serverless-host/: 独立目录结构
|
|
252
|
+
- index.ts: 后端服务(Worker 线程隔离执行 TS 函数)
|
|
253
|
+
- public/: 前端静态文件(CodeMirror 6 IDE 界面)
|
|
254
|
+
- 前端升级到 IDE 级别体验:
|
|
255
|
+
- CodeMirror 6 编辑器(语法高亮、自动缩进、行号)
|
|
256
|
+
- 可拖拽调整大小的侧边栏和输出面板
|
|
257
|
+
- 自定义请求体编辑面板(POST/PUT/PATCH)
|
|
258
|
+
- HTTP 方法选择、请求路径输入、模板下拉菜单
|
|
259
|
+
- JSON 语法高亮输出、快捷键面板(Ctrl+Enter 运行、Ctrl+S 保存)
|
|
260
|
+
- 脏状态标记、Toast 通知、函数删除确认
|
|
261
|
+
- 后端完善:
|
|
262
|
+
- 静态文件服务(pipe 流式传输、Cache-Control)
|
|
263
|
+
- GET /_fn/:name 读取函数源码端点
|
|
264
|
+
- 请求体大小限制(64KB)、请求日志
|
|
265
|
+
- 支持子路径执行(/:fnName/sub/path)
|
|
266
|
+
|
|
267
|
+
#### echo-server 新增端点
|
|
268
|
+
- `/cookie` — 返回 Set-Cookie 响应头
|
|
269
|
+
- `/custom-response` — 返回自定义响应头(X-Custom-Response、X-Rate-Limit、Cache-Control)和二进制响应体
|
|
270
|
+
- `/no-content` — 返回 204 No Content
|
|
271
|
+
- `/gzip` — 返回 gzip 压缩的 JSON 响应(Content-Encoding: gzip)
|
|
@@ -21,7 +21,15 @@ export declare class AdminApiHandler {
|
|
|
21
21
|
private hostnameRoutes;
|
|
22
22
|
private portRoutes;
|
|
23
23
|
private serviceManager;
|
|
24
|
+
/** 按服务名称索引的服务配置(懒初始化,因为构造时路由表可能尚未填充) */
|
|
25
|
+
private _serviceMap;
|
|
26
|
+
/** 按服务名称索引的路由映射(懒初始化) */
|
|
27
|
+
private _routeMap;
|
|
24
28
|
constructor(config: DynaPMConfig, logger: Logger, hostnameRoutes: Map<string, RouteMapping>, portRoutes: Map<number, RouteMapping>, serviceManager: ServiceManager);
|
|
29
|
+
/** 获取(或首次构建)服务名称索引 */
|
|
30
|
+
private getServiceMap;
|
|
31
|
+
/** 获取(或首次构建)路由映射索引 */
|
|
32
|
+
private getRouteMap;
|
|
25
33
|
/**
|
|
26
34
|
* 检查客户端 IP 是否在允许列表中
|
|
27
35
|
*/
|
|
@@ -34,6 +42,10 @@ export declare class AdminApiHandler {
|
|
|
34
42
|
* 获取服务运行时长
|
|
35
43
|
*/
|
|
36
44
|
private getServiceUptime;
|
|
45
|
+
/**
|
|
46
|
+
* 在所有路由表中查找服务
|
|
47
|
+
*/
|
|
48
|
+
private findServiceMapping;
|
|
37
49
|
/**
|
|
38
50
|
* 处理管理 API 请求
|
|
39
51
|
*/
|