asai-nodejs-host 0.2.31 → 0.2.33

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.
Files changed (3) hide show
  1. package/README.md +289 -344
  2. package/index.js +1 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,435 +2,380 @@
2
2
 
3
3
  ## 功能概述
4
4
 
5
- `asai-nodejs-host` 是整个系统的**核心服务器引擎**,负责创建和管理 **HTTP 服务器**、**HTTPS 服务器**、**代理服务器**和 **WebSocket 服务器**。它基于 Node.js 原生 `http`/`https` 模块和 `ws` 库构建。
6
-
7
- 支持特性:
8
- - 多站点并行托管(通过 `hostdir` 区分)
9
- - HTTP/HTTPS 双模式
10
- - URL Rewrite / Fallback 机制(SPA 支持)
11
- - 反向代理服务(Proxy
12
- - WebSocket 服务(支持用户鉴权、连接管理)
13
- - CORS 跨域支持
14
- - 连接数监控与限流
15
- - 优雅关闭
5
+ `asai-nodejs-host` 是系统的**核心服务器引擎**,基于 Node.js 原生 `http`/`https` `ws` 库,负责:
6
+
7
+ - 多站点 HTTP/HTTPS 并行托管(`hosts`)
8
+ - 反向代理(`proxyhosts`)
9
+ - WebSocket 服务(鉴权、在线数限制、广播)
10
+ - 静态资源服务(SPA fallback、ETag、LRU 缓存)
11
+ - 日志、进程守护、传输层桥接(TCP/gRPC/RS485
12
+
13
+ 配置文件路径:`webserver/websys/sys/asaihost.json`(运行时挂载为 `$asai.hostconfig`)。
16
14
 
17
15
  ---
18
16
 
19
- ## 文件结构
17
+ ## 目录结构
20
18
 
21
19
  ```
22
20
  asai-nodejs-host/
23
- ├── README.md
24
- ├── AsaiHost.ts ← 入口文件
21
+ ├── AsaiHost.ts ← npm/插件入口(fn + AsaiCommon + AsaiUtils + HostApi)
25
22
  └── src/
26
- ├── index.ts 核心逻辑:主机初始化、服务器启动
27
- ├── types.ts 类型定义
28
- ├── core.ts 工具函数(IP 获取、配置合并等)
29
- ├── http-server.ts ← HTTP/HTTPS 服务器实现
30
- ├── ws-server.ts WebSocket 服务器实现
31
- ├── proxy-server.ts 反向代理服务器实现
32
- ├── logger.ts 日志系统(带缓冲区、按日轮转)
33
- ├── process.ts 进程管理(异常处理、优雅关闭)
34
- └── utils.ts 工具函数(用户信息编解码)
23
+ ├── index.ts 插件工厂:注册 workHandlers、StartAsaiHost
24
+ ├── types.ts TypeScript 类型
25
+ ├── host-api.ts 对外公开 API(禁止 deep import src/ 内部)
26
+
27
+ ├── core/ 启动与配置
28
+ ├── init.ts initAsaiHost(IP/日志/进程/AES/注册表)
29
+ ├── host-config.ts getHostCFG / getProxyHostCFG / validateHostdir
30
+ ├── network.ts getIp / getHostIP
31
+ │ ├── time.ts getNowTime
32
+ │ └── index.ts
33
+
34
+ ├── http/ ← HTTP 层
35
+ │ ├── server.ts ← 路由编排、启动、asai 守护
36
+ │ ├── security.ts ← 安全响应头、Userinfo 解析
37
+ │ ├── log.ts ← 请求日志过滤(skip / sample)
38
+ │ ├── static.ts ← 静态查找、ETag、SPA fallback
39
+ │ ├── connection.ts ← 连接数监控与限流
40
+ │ └── static-path-cache.ts
41
+
42
+ ├── ws/ ← WebSocket 层
43
+ │ ├── server.ts ← WSS 创建与 connection 分发
44
+ │ ├── auth.ts ← 鉴权、登录态、踢下线
45
+ │ ├── message.ts ← 消息序列化、sendws/broadws
46
+ │ └── route-index.ts ← WS 路由前缀索引
47
+
48
+ ├── proxy/
49
+ │ └── server.ts ← 反向代理
50
+
51
+ ├── config/
52
+ │ ├── syscom.ts ← syscom 传输配置读取与归一化
53
+ │ └── validate.ts ← asaihost.json 启动校验
54
+
55
+ ├── infra/ ← 基础设施
56
+ │ ├── logger.ts ← 缓冲日志、按日轮转
57
+ │ ├── process.ts ← 异常捕获、优雅关闭
58
+ │ ├── registry.ts ← API/WS 路由注册
59
+ │ ├── transport-bridge.ts
60
+ │ ├── safe-path.ts ← 路径白名单校验
61
+ │ └── secure-compare.ts
62
+
63
+ ├── utils/
64
+ │ ├── userinfo.ts ← Userinfo AES 解密
65
+ │ ├── ws-mock.ts ← WS Mock 工具
66
+ │ └── index.ts
67
+
68
+ └── common/ ← 路由处理器基类(Api / Sys / Ws / WsClient)
69
+ └── server/
35
70
  ```
36
71
 
37
- ---
38
-
39
- ## 入口文件 — `AsaiHost.ts`
40
-
41
- ```typescript
42
- module.exports = ($asai: any, opt: any) => {
43
- const { StartAsaiHost, asaiWs } = AsaiHost($asai, opt);
44
- return { StartAsaiHost, asaiWs };
45
- };
46
- ```
47
-
48
- **返回值**:
49
- | 名称 | 类型 | 说明 |
50
- |------|------|------|
51
- | `StartAsaiHost` | function | 启动所有 HTTP/HTTPS/WebSocket 服务器 |
52
- | `asaiWs` | object | `ws` 模块引用 |
72
+ > 源码仅保留 TypeScript(`tsx` 直接运行);`syscom-config.ts` 为对外 API 兼容入口,指向 `config/syscom.ts`。
53
73
 
54
74
  ---
55
75
 
56
- ## 核心模块分析
76
+ ## asaihost.json 完整配置说明
57
77
 
58
- ### `src/index.ts` 主机初始化入口
78
+ 配置加载后赋值给 `$asai.hostconfig`。各站点实际生效配置 = `hosts.default` `hosts.<站点名>` 浅合并;代理同理 = `proxyhosts.default` + `proxyhosts.<代理名>`。
59
79
 
60
- **功能**:
61
- 1. 将 `opt` 中的处理器(`sysWork`, `apiWork`, `wsWork`, `jsonpWork`)挂载到 `global`
62
- 2. 提供 `StartAsaiHost()` 函数
80
+ ### 顶层字段
63
81
 
64
- **`StartAsaiHost()` 启动流程**:
65
- 1. 调用 `initAsaiHost()` 初始化核心环境
66
- 2. 遍历 `$asai.hostconfig.hosts` 启动 HTTP 服务器
67
- 3. 遍历 `$asai.hostconfig.proxyhosts` 启动代理服务器
82
+ | 字段 | 类型 | 示例 | 说明 | 读取模块 |
83
+ |------|------|------|------|----------|
84
+ | `website.title` | string | `"WEB"` | 日志文件头 Software 名称 | `infra/logger` |
85
+ | `website.ver` | string | `"1.0.0.0"` | 日志文件头 Version | `infra/logger` |
86
+ | `password` | string | `"asai"` | 系统密码(**必填**,启动校验) | `config/validate` |
87
+ | `ip` | string | `"asai"` | 全局绑定 IP;`"asai"`=自动探测本机 IPv4 并 localhost 监听 | `core/network` |
88
+ | `index` | string | `"index.html"` | 默认首页文件名 | `http/server` |
89
+ | `asaisn` | string | — | Userinfo 解密盐(AsCode.asdecode) | `utils/userinfo` |
90
+ | `wssec` | string[] | `["asai","","-"]` | WS 安全相关(业务层使用) | hostweb |
91
+ | `pkg.type` | number | `1` | 打包模式标识 | 业务层 |
68
92
 
69
- ---
93
+ ### `tm` — 定时器
70
94
 
71
- ### `src/types.ts` 类型定义
95
+ | 字段 | 默认 | 说明 |
96
+ |------|------|------|
97
+ | `tm.c` | — | 通用定时(ms) |
98
+ | `tm.d` | `2000` | asai 守护重启延迟 / WS 踢下线探测超时 |
99
+ | `tm.maxtry` | `100` | 进程异常重启上限窗口内次数 |
100
+ | `tm.restartwindow` | `60000` | 异常重启统计窗口(ms) |
72
101
 
73
- ```typescript
74
- type WorkHandler = (req: any, res: any, hostdir: string) => void;
75
- type WsWorkHandler = (msg: any, ws: any, hostdir: string, req: any) => void;
102
+ ### `path` — 路径
76
103
 
77
- interface AsaiHostOptions {
78
- sysWork: WorkHandler; // /sys/* 路由处理器
79
- apiWork: WorkHandler; // /api/* 路由处理器
80
- wsWork: WsWorkHandler; // WebSocket 消息处理器
81
- jsonpWork: WorkHandler; // /jsonp/* 路由处理器
82
- }
83
- ```
84
-
85
- ---
104
+ | 字段 | 说明 |
105
+ |------|------|
106
+ | `path.mock` | Mock 数据目录 |
107
+ | `path.ip` | IP 相关资源目录 |
86
108
 
87
- ### `src/core.ts` — 核心工具函数
109
+ ### `zip`
88
110
 
89
- | 函数 | 说明 |
111
+ | 字段 | 说明 |
90
112
  |------|------|
91
- | `getIp()` | 获取本机非 127.0.0.1 的第一个 IPv4 地址 |
92
- | `getNowTime(type, date)` | 格式化时间,支持 6 种格式 |
93
- | `validateHostdir(hostdir)` | 验证主机目录名是否合法(字母数字下划线中划线) |
94
- | `getHostIP(ip, $asai)` | 获取主机 IP |
95
- | `getHostCFG(hostdir, $asai)` | 合并 host 配置(默认配置 + 站点配置) |
96
- | `getProxyHostCFG(proxyhostdir, $asai)` | 合并代理配置 |
97
- | `initAsaiHost($asai, opt)` | 初始化:挂载方法、设置端口、初始化进程/日志、配置 AES |
98
-
99
- ---
113
+ | `zip.zipFileNameEncoding` | 解压文件名编码,如 `"gbk"` |
100
114
 
101
- ### `src/logger.ts` — 日志系统
115
+ ### `logger` — 日志
102
116
 
103
- **类:`LogBuffer`**
104
- - 带缓冲区的异步写入,支持按缓冲区大小或定时刷新
105
- - `flushInterval`: 100ms
106
- - `maxBufferSize`: 100
107
- - 使用 `$asai.asaifs.append()` 异步写入
117
+ | 字段 | 类型 | 示例 | 说明 |
118
+ |------|------|------|------|
119
+ | `logger.lv.view` | number/bool | `2` | 控制台输出;truthy 时 WS message 也写日志 |
120
+ | `logger.lv.record` | number/bool | `1` | 是否写入日志文件 |
121
+ | `logger.path.server` | string | `"./webdata/.../server/"` | 服务端日志目录 |
122
+ | `logger.path.client` | string | — | 客户端日志目录 |
123
+ | `logger.path.maxsize` | number | `1048576` | 单文件上限(字节),超出自动轮转 |
124
+ | `logger.sample` | object | `{ "API": 0.15 }` | 按日志类型采样率 0~1,降低高 QPS 写盘 |
125
+ | `logger.skip` | string[] | `["/sys/info/metrics"]` | 静默路径;精确匹配或 `*` 前缀通配 |
108
126
 
109
- **函数**:
127
+ ### `guard` — 进程守护(业务层 hostweb)
110
128
 
111
- | 函数 | 说明 |
129
+ | 字段 | 说明 |
112
130
  |------|------|
113
- | `setAppend($asai, src, logstr)` | 追加日志(自动检查文件大小,超 1MB 自动轮转) |
114
- | `setLog($asai, src, logstr)` | 同步写入日志 |
115
- | `initLog($asai)` | 初始化日志系统:设置 `$asai.$log` 函数 |
131
+ | `guard.config` | guard.json 路径 |
132
+ | `guard.autoStartOnRestart` | 重启后自动拉起子进程 |
133
+ | `guard.default.*` | 子进程默认参数(app/ispath/trymax/cfg 等) |
116
134
 
117
- **日志配置**(`$asai.hostconfig.logger`):
118
- ```javascript
119
- {
120
- lv: {
121
- view: true, // 是否在控制台显示
122
- record: true // 是否记录到文件
123
- },
124
- path: {
125
- client: './logs/client/', // 客户端日志目录
126
- server: './logs/server/', // 服务器日志目录
127
- maxsize: 1048576 // 单个日志文件最大字节
128
- }
129
- }
130
- ```
135
+ ### `aes` — 加密
131
136
 
132
- **日志类型**:
133
- - 参数 `0` 或不传:服务器日志(自动添加时间戳)
134
- - 参数 `1`:客户端日志(独立格式)
135
- - 参数 `> 666`:静默日志(仅记录,控制台不显示)
137
+ | 字段 | 说明 |
138
+ |------|------|
139
+ | `aes.lv` | 加密等级;`>1` 时 API/WS 全量 AES |
140
+ | `aes.keybase64` | AES 密钥(Base64) |
141
+ | `aes.aad` | 附加认证数据 |
142
+ | `aes.keylength` | 密钥长度(bit) |
143
+ | `aes.ivlength` | IV 长度 |
144
+ | `aes.algorithm` | 如 `"aes-256-gcm"` |
136
145
 
137
- ---
146
+ 启动时若 `aes.lv` 有效且存在 `AsAES`,挂载 `$asai.$lib.aesfns`。
138
147
 
139
- ### `src/process.ts` — 进程管理
148
+ ### `httpscert` — 全局 HTTPS 证书
140
149
 
141
- | 事件 | 处理方式 |
142
- |------|----------|
143
- | `unhandledRejection` | 记录未处理的 Promise 拒绝,含调用堆栈 |
144
- | `uncaughtException` | 记录未捕获异常,60 秒内最多 10 次自动重启,超出则退出 |
145
- | `SIGTERM` / `SIGINT` | 优雅关闭所有 HTTP/WS 服务器,超时 60 秒强制退出 |
150
+ | 字段 | 说明 |
151
+ |------|------|
152
+ | `httpscert.key` | PEM 私钥(字符串或 Buffer) |
153
+ | `httpscert.cert` | PEM 证书 |
146
154
 
147
- **`gracefulShutdown($asai)` 关闭顺序**:
148
- 1. 关闭所有 HTTP 服务器
149
- 2. 关闭所有 WebSocket 服务器
150
- 3. 清除守护定时器
151
- 4. 清除 WS 心跳监控
152
- 5. 3 秒后退出进程
155
+ 站点 `httptype` 为 `https://` 时,优先用站点级 `httpscert`,否则回退全局。
153
156
 
154
- ---
157
+ ### `mimetypes` — 全局 MIME
155
158
 
156
- ### `src/http-server.ts` — HTTP/HTTPS 服务器
159
+ 扩展名 → Content-Type 映射。站点可覆盖:`hosts.<name>.mimetypes` 与全局合并。
157
160
 
158
- **核心函数**:
161
+ ### `hosts` — HTTP/WS 站点
159
162
 
160
- #### `getHTTPServer(httptype, cfg, $asai)`
161
- 根据协议类型返回 `http` 或 `https` 模块,并注入 `AsCreateServer` 方法。
163
+ #### `hosts.default`(模板,不单独启动)
162
164
 
163
- #### `startHTTPServer($asai, hostdir)`
164
- 启动一个站点的 HTTP/HTTPS 服务器。
165
+ | 字段 | 类型 | 默认 | 说明 |
166
+ |------|------|------|------|
167
+ | `close` | 0/1 | `0` | `1` 跳过启动 |
168
+ | `port` | number | `9198` | 监听端口 |
169
+ | `httptype` | string | `"http://"` | `"http://"` 或 `"https://"` |
170
+ | `wstype` | string | `"ws://"` | 控制台 WS 地址展示 |
171
+ | `ip` | string | — | 站点级绑定 IP,覆盖全局 |
172
+ | `ws` | 0/1 | — | 是否挂载 WebSocket |
173
+ | `ckhttp` | 0/1 | `0` | 启用 HTTP 连接数监控与限流 |
174
+ | `ckws` | 0/1 | `0` | 启用 WS 用户鉴权与在线表 |
175
+ | `maxhttp` | number | `100` | HTTP 最大并发连接 |
176
+ | `maxws` | number | `100` | WS 最大连接数(ckws 模式下按已登录用户计) |
177
+ | `maxonline` | number | `100` | 最大在线用户数(ckws) |
178
+ | `oncors` | 0/1 | `0` | API CORS 严格模式(0=*,1=校验 cors 列表) |
179
+ | `cors` | string[] | `[]` | 允许的 Origin 列表 |
180
+ | `hostdirs` | string[] | — | 静态文件查找目录(按序) |
181
+ | `mimetypes` | object | — | 站点级 MIME 覆盖 |
165
182
 
166
- **路由规则**:
167
- | URL 前缀 | 处理器 |
168
- |----------|--------|
169
- | `/sys/*` | `sysWork()` |
170
- | `/api/*` | `apiWork()` |
171
- | `/jsonp/*` | `jsonpWork()` |
172
- | 其他 | 静态文件服务 |
183
+ #### `hosts.<站点名>.weburls` — SPA 路由
173
184
 
174
- **静态文件服务特性**:
175
- - URL Rewrite 支持 — 如果配置了 `weburls.rewrite` 和 `keys`,匹配的 URL 重写回首页(SPA 支持)
176
- - 多目录查找 依次在 `hostdirs` 配置的目录中查找文件
177
- - 目录遍历防护 拒绝包含 `..` 的路径
178
- - MIME 类型自定义 通过 `mimetypes` 配置
179
- - 文件流缓存 — `Cache-Control: public, max-age=3600`
185
+ | 字段 | 说明 |
186
+ |------|------|
187
+ | `weburls.rewrite` | `true` 启用 SPA fallback |
188
+ | `weburls.webdir` | 前端构建目录(如 `"webclient"`) |
189
+ | `weburls.keys` | URL 前缀列表,匹配则返回 `index.html` |
180
190
 
181
- **连接管理**:
182
- - 通过 `ckhttp` 和 `maxhttp` 配置连接数限制
183
- - 当连接数达到 `80%` 时记录警告
184
- - 当达到 `95%` 时强制清理空闲超过 5 分钟的连接
185
- - 每 30 分钟检查一次
191
+ **静态查找顺序**:`hostdirs` 各目录 → 未命中且启用 rewrite → fallback 到 `webdir/index.html`。
186
192
 
187
- #### `asaiGuard($asai)`
188
- 守护机制 — 当 `asai` 端口被占用时退出进程;否则延迟 2 秒重新启动。
193
+ #### 特殊站点名
189
194
 
190
- #### `checkHttpHeader(req, $asai)`
191
- 检查 HTTP 请求头中的用户信息并解码。
195
+ | 名称 | 行为 |
196
+ |------|------|
197
+ | `default` | 仅作默认模板,**不启动** |
198
+ | `asai` | 内置守护站点;端口冲突时进程退出或延迟重启 |
199
+ | 其它 | 须匹配 `^[a-zA-Z0-9_-]+$` 且长度 ≤100 |
200
+
201
+ #### 示例(节选)
202
+
203
+ ```json
204
+ "estunweb": {
205
+ "ckhttp": 1,
206
+ "ckws": 1,
207
+ "ws": 1,
208
+ "maxhttp": 1000,
209
+ "maxws": 200,
210
+ "maxonline": 10,
211
+ "weburls": {
212
+ "rewrite": true,
213
+ "webdir": "webclient",
214
+ "keys": ["/cocontrol", "/couser"]
215
+ },
216
+ "hostdirs": ["home", "websys/webpage", "webdata/upload"]
217
+ }
218
+ ```
192
219
 
193
- ---
220
+ ### `proxyhosts` — 反向代理
194
221
 
195
- ### `src/ws-server.ts` — WebSocket 服务器
222
+ #### `proxyhosts.default`
196
223
 
197
- **核心函数**:
224
+ | 字段 | 默认 | 说明 |
225
+ |------|------|------|
226
+ | `proxyhttptype` | `"http://"` | 代理监听协议 |
227
+ | `proxyport` | — | 代理监听端口 |
228
+ | `httptype` | `"http://"` | 目标协议(用于自动拼 url) |
229
+ | `port` | — | 目标端口 |
230
+ | `redirect` | `false` | `true` 时 301 重定向而非转发 |
231
+ | `maxSockets` | `2000` | Agent 最大 socket |
232
+ | `maxFreeSockets` | `200` | 空闲 socket 池上限 |
233
+ | `keepAlive` | `true` | HTTP Agent keepAlive |
234
+ | `timeout` | `60000` | 请求超时(ms) |
235
+ | `keepAliveMsecs` | `30000` | keepAlive 间隔 |
236
+ | `rejectUnauthorized` | `false` | HTTPS 目标自签名证书 |
237
+ | `passUserInfo` | `false` | 是否向目标转发 `Userinfo` 头 |
238
+
239
+ #### `proxyhosts.<代理名>`
240
+
241
+ | 字段 | 说明 |
242
+ |------|------|
243
+ | `url` | 目标完整 URL;空则按 `httptype`+`ip`+`port` 自动拼接 |
244
+ | `proxyip` | 代理绑定 IP |
245
+ | `proxyport` | 代理端口 |
246
+ | `proxyhttptype` | 代理 HTTPS 时用 `"https://"` |
198
247
 
199
- #### `createWSHost($asai, hostdir)`
200
- 为指定 hostdir 创建 WebSocket 服务(附加到已有的 HTTP 服务器)。
248
+ ### `syscom` — 传输层默认连接(TCP / gRPC / RS485)
201
249
 
202
- **`wss.sendws(msg, type, curws)` — 发送消息**:
203
- | type | 行为 |
204
- |------|------|
205
- | 0 | 仅发送给当前连接 |
206
- | 1 | 发送给除当前连接外的所有人 |
207
- | 2 | 发送给所有人(广播) |
250
+ 推荐结构:`syscom.{tcp|grpc|rs485}.default`。启动时 `normalizeHostConfig` 会合并到顶层同名键,并兼容 legacy 键 `1tcp`/`serial485` 等。
208
251
 
209
- **`wss.broadws(msg)` — 广播消息**:发送给所有连接。
252
+ #### 通用 `default` 字段
210
253
 
211
- **WebSocket 连接鉴权**:
212
- 1. 从 `Sec-WebSocket-Protocol` 请求头获取用户信息
213
- 2. 调用 `deUserInfoWithAsai()` 解码用户信息(AES 解码)
214
- 3. 如果启用了鉴权(`hostcfg.ckws`),执行 `isOkWs()`
254
+ | 字段 | 说明 |
255
+ |------|------|
256
+ | `id` | 连接 ID(如 `testtcp`) |
257
+ | `label` | 显示名称 |
258
+ | `autoConnect` | 启动时自动连接(默认 true,显式 `false` 关闭) |
259
+ | `reconnect.enabled` | 是否自动重连 |
260
+ | `reconnect.maxRetries` | 最大重试次数(默认 60 或 `tm.maxtry`) |
261
+ | `reconnect.initialDelay` | 首次重连延迟(ms) |
262
+ | `reconnect.maxDelay` | 最大重连间隔(ms) |
263
+ | `reconnect.factor` | 退避倍数 |
264
+
265
+ #### TCP `default`
266
+
267
+ | 字段 | 说明 |
268
+ |------|------|
269
+ | `host` | 目标主机 |
270
+ | `port` | 目标端口 |
215
271
 
216
- **`isOkWs()` 鉴权检查**:
217
- 1. 用户名是否存在
218
- 2. 同一用户名是否已登录(踢下线旧连接或阻止新连接)
219
- 3. 同一 Token 是否已登录
220
- 4. 在线人数是否达到上限(`maxonline`)
272
+ #### gRPC `default`
221
273
 
222
- **连接数控制**:
223
- - 通过 `maxws` 配置限制 WS 连接数(默认 100)
224
- - 超过限制时发送错误消息并关闭连接(1013 状态码)
274
+ 同 TCP:`host` + `port`(如 `50051`)。
225
275
 
226
- ---
276
+ #### RS485 `default`
227
277
 
228
- ### `src/proxy-server.ts` 反向代理服务器
229
-
230
- **函数**:`startProxyServer($asai, proxyhostdir)`
231
-
232
- **代理配置**(`$asai.hostconfig.proxyhosts`):
233
- | 参数 | 默认值 | 说明 |
234
- |------|--------|------|
235
- | `url` | — | 目标服务器 URL |
236
- | `httptype` | `http://` | 代理协议类型 |
237
- | `port` | — | 代理端口 |
238
- | `proxyhttptype` | — | 代理服务器自身协议类型 |
239
- | `proxyport` | — | 代理服务器端口 |
240
- | `redirect` | false | 设为 true 时做 301 重定向而非代理 |
241
- | `keepAlive` | true | 是否保持连接 |
242
- | `timeout` | 60000 | 请求超时(ms) |
243
- | `maxSockets` | 1000 | 最大 socket 数 |
244
-
245
- **特性**:
246
- - 支持 HTTP→HTTPS、HTTPS→HTTP、HTTPS→HTTPS 代理
247
- - 请求头过滤(去掉 transfer-encoding、connection 等转发无关头)
248
- - 错误处理:502 Bad Gateway、504 Gateway Timeout
249
- - 用户信息透传(在 `Userinfo` 头中传递)
278
+ | 字段 | 说明 |
279
+ |------|------|
280
+ | `path` | 串口路径(Windows `COM3`,Linux `/dev/ttyUSB0`) |
281
+ | `baudRate` | 波特率 |
282
+ | `dataBits` / `stopBits` / `parity` | 串口参数 |
250
283
 
251
284
  ---
252
285
 
253
- ### `src/utils.ts` — 工具函数
286
+ ## 请求路由
254
287
 
255
- | 函数 | 说明 |
256
- |------|------|
257
- | `deUserInfoWithAsai(userinfo, $asai)` | 解码加密后的用户信息(用户名、等级、token) |
258
- | `deUserInfo(userinfo)` | 旧版兼容函数(不处理解码) |
288
+ | URL 前缀 | 处理器 | 注册来源 |
289
+ |----------|--------|----------|
290
+ | `/sys/*` | `sysWork` | hostweb 注入 |
291
+ | `/api/*` | `apiWork` | hostweb 注入 |
292
+ | `/jsonp/*` | `jsonpWork` | hostweb 注入 |
293
+ | 其它 | 静态文件 | `http/static` |
259
294
 
260
- **用户信息编码格式**:
261
- ```
262
- [decode(用户名, token), decode(等级, token), token本身]
263
- ```
264
- 使用 `$asai.$lib.AsCode.asdecode()` 进行解码。
295
+ WebSocket 消息 → `wsWork`(hostweb 注入)。
265
296
 
266
297
  ---
267
298
 
268
- ## 配置参考
269
-
270
- ### `hostconfig` 结构
271
-
272
- ```javascript
273
- {
274
- ip: '0.0.0.0', // 绑定 IP
275
- index: 'index.html', // 默认首页文件
276
- mimetypes: { '.html': 'text/html' }, // 自定义 MIME 类型
277
- tm: { c: 2000, d: 3000 }, // 定时器配置
278
- aes: { // AES 加密配置
279
- lv: 0, // 加密等级
280
- keybase64: '...', // 密钥(Base64)
281
- aad: '...', // 附加认证数据
282
- ivlength: 16, // IV 长度
283
- algorithm: 'aes-256-gcm' // 加密算法
284
- },
285
- logger: {
286
- lv: { view: true, record: true },
287
- path: { client: './logs/', server: './logs/', maxsize: 1048576 }
288
- },
289
-
290
- // 站点配置
291
- hosts: {
292
- default: { // 默认配置
293
- httptype: 'http://',
294
- port: 80,
295
- hostdirs: [], // 静态文件目录列表
296
- ws: true, // 是否启用 WebSocket
297
- ckws: true, // 是否鉴权
298
- cors: ['*'], // 允许的跨域来源
299
- maxws: 100, // 最大 WS 连接数
300
- maxonline: 100, // 最大在线人数
301
- maxhttp: 100 // 最大 HTTP 连接数
302
- },
303
- mysite: { // 具体站点
304
- httptype: 'https://',
305
- port: 443,
306
- weburls: { // SPA URL 重写
307
- rewrite: true,
308
- webdir: './dist',
309
- keys: ['/app', '/user']
310
- },
311
- httpscert: { // HTTPS 证书
312
- key: fs.readFileSync('./cert/key.pem'),
313
- cert: fs.readFileSync('./cert/cert.pem')
314
- }
315
- }
316
- },
317
-
318
- // 代理配置
319
- proxyhosts: {
320
- api: {
321
- url: 'http://localhost:3000',
322
- proxyport: 8080
323
- }
324
- }
325
- }
326
- ```
299
+ ## 模块职责速查
300
+
301
+ | 模块 | 核心导出 | 职责 |
302
+ |------|----------|------|
303
+ | `core/init` | `initAsaiHost` | 启动期挂载 $asai 方法、日志、进程、AES |
304
+ | `core/host-config` | `getHostCFG` | 合并并缓存 hosts/proxyhosts |
305
+ | `http/server` | `startHTTPServer` | HTTP 创建、路由、监听、asaiGuard |
306
+ | `http/static` | `createStaticHandler` | 静态文件 + SPA + ETag |
307
+ | `http/connection` | `setupHttpConnectionMonitor` | 80%/95% 阈值、空闲清理 |
308
+ | `ws/server` | `createWSHost` | WSS 生命周期 |
309
+ | `ws/auth` | `isOkWs`, `loginWs` | 用户名/Token/在线数鉴权 |
310
+ | `proxy/server` | `startProxyServer` | 反向代理、hop-by-hop 头过滤 |
311
+ | `config/validate` | `validateHostConfig` | 启动前 fail-fast 校验 |
312
+ | `config/syscom` | `normalizeHostConfig` | syscom 归一化 |
313
+ | `infra/logger` | `initLog` | `$asai.$log`、缓冲写盘 |
314
+ | `infra/process` | `gracefulShutdown` | SIGTERM/SIGINT 优雅关闭 |
315
+ | `infra/registry` | `registerApi/Ws` | 路由注册双写 $lib 与 hostserver* |
327
316
 
328
317
  ---
329
318
 
330
319
  ## 使用示例
331
320
 
332
321
  ```javascript
333
- const AsaiHost = require('./AsaiHost');
322
+ import AsaiHost from './AsaiHost';
334
323
 
335
- const { StartAsaiHost, asaiWs } = AsaiHost($asai, {
336
- sysWork: (req, res, hostdir) => { /* 系统路由处理 */ },
337
- apiWork: (req, res, hostdir) => { /* API 路由处理 */ },
338
- wsWork: (msg, ws, hostdir, req) => { /* WS 消息处理 */ },
339
- jsonpWork: (req, res, hostdir) => { /* JSONP 处理 */ }
324
+ const { StartAsaiHost } = AsaiHost.fn($asai, {
325
+ sysWork: (req, res, hostdir) => { /* /sys */ },
326
+ apiWork: (req, res, hostdir) => { /* /api */ },
327
+ wsWork: (msg, ws, hostdir, req) => { /* WS */ },
328
+ jsonpWork: (req, res, hostdir) => { /* /jsonp */ },
340
329
  });
341
330
 
342
- StartAsaiHost(); // 启动所有服务器
331
+ StartAsaiHost();
343
332
  ```
344
333
 
345
- ---
346
-
347
- ## 注意事项
348
-
349
- 1. 所有处理器(`sysWork`/`apiWork`/`wsWork`/`jsonpWork`)在 `index.ts` 中被挂载到 `global`
350
- 2. `validateHostdir()` 拒绝名称为 `default` 和 `asai` 的站点
351
- 3. `http-server.ts` 中的 `checkFileExists()` 同时检查文件权限
352
- 4. 代理服务器中包含 SSL 错误处理(`rejectUnauthorized: false` 用于自签名证书)
353
- 5. 日志系统使用缓冲区异步写入,不会阻塞主线程
354
-
355
- ---
356
-
357
- ## 代码分析与优化建议
358
-
359
- ### ✅ 已优化项目
360
-
361
- #### 1. 全局变量污染 ➔ 模块级 Map ✅
334
+ 启动校验:
362
335
 
363
- **优化内容**:`index.ts` 中改用模块级 `workHandlers` Map 存储处理器,通过 `getWorkHandler()` 导出,`http-server.ts` 和 `ws-server.ts` 通过该函数获取,不再污染 `global`。
336
+ ```javascript
337
+ import { validateHostConfig, normalizeHostConfig } from './src/host-api';
364
338
 
365
- ```typescript
366
- // 优化后:模块级 Map,可多实例并行
367
- const workHandlers = new Map<string, Function>();
368
- export function getWorkHandler(name: string): Function | undefined {
369
- return workHandlers.get(name);
370
- }
339
+ const cfg = normalizeHostConfig(JSON.parse(fs.readFileSync('asaihost.json', 'utf8')));
340
+ const errors = validateHostConfig(cfg);
341
+ if (errors.length) throw new Error(errors.join('\n'));
371
342
  ```
372
343
 
373
- **优化收益**:避免全局命名冲突,支持多实例并行运行,单元测试可独立 mock。
374
-
375
- ---
376
-
377
- #### 2. 静态文件服务增加 ETag 和 Last-Modified ✅
378
-
379
- **优化内容**:新增 `serveStaticFile()` 函数,增加 `ETag` 和 `Last-Modified` 响应头,支持客户端条件请求(`If-None-Match` / `If-Modified-Since`),返回 304 Not Modified 减少不必要的文件传输。
380
-
381
- **优化收益**:节省带宽约 30-50%。
382
-
383
- ---
384
-
385
- #### 3. URL 解析增加编码保护 ✅
386
-
387
- **优化内容**:`parseUrlPath()` 在解析前对 URL 进行 `encodeURI()` 编码,避免中文等特殊字符导致 `new URL()` 抛出异常。
388
-
389
- ---
390
-
391
- #### 4. 日志缓冲区增加进程退出刷新 ✅
392
-
393
- **优化内容**:`LogBuffer` 构造函数和 `initLog()` 中注册 `process.once('beforeExit')` 钩子,确保进程退出前刷新所有日志缓冲区,防止日志数据丢失。
394
-
395
344
  ---
396
345
 
397
- #### 5. WebSocket 鉴权 `ws.send` + `close` 竞争修复 ✅
398
-
399
- **优化内容**:`isOkWs()` 中所有 `ws.send(msg)` 改为在回调中执行 `ws.close()`,确保消息发送完毕后再关闭连接。
400
-
401
- ---
402
-
403
- #### 6. 代理服务器 Userinfo 透传增加开关 ✅
404
-
405
- **优化内容**:在 `startProxyServer()` 中增加 `passUserInfo` 配置开关(默认关闭),避免用户信息泄露给第三方后端。
406
-
407
- ---
408
-
409
- #### 7. HTTP 连接监控间隔缩短 ✅
410
-
411
- **优化内容**:连接监控间隔从 30 分钟缩短为 5 分钟,更及时地处理连接数激增。
412
-
413
- ---
414
-
415
- #### 8. 代码可维护性改进 ✅
346
+ ## 注意事项
416
347
 
417
- **优化内容**:将静态文件服务逻辑拆分为独立的 `serveStaticFile()` 函数,包含完整的 ETag 校验和流式文件服务逻辑。
348
+ 1. 处理器通过模块级 `workHandlers` Map 注册,**不污染 global**。
349
+ 2. `validateHostdir()` 拒绝 `default` / `asai` 作为普通站点名启动。
350
+ 3. 静态路径经 `resolvePathUnderRoots` 白名单校验,拒绝目录穿越;代理默认**不透传** Userinfo。
351
+ 4. HTTP 连接监控间隔 **5 分钟**;超 95% 清理空闲 5 分钟以上连接。
352
+ 5. 日志异步缓冲写入,进程退出时 `beforeExit` 刷盘。
418
353
 
419
354
  ---
420
355
 
421
- ### 🔜 待优化项目
356
+ ## 配置 → 代码映射图
422
357
 
423
- | 优先级 | 问题 | 状态 |
424
- |--------|------|------|
425
- | 🟡 P1 | 代码拆分完成(http-server.ts 仍需进一步细化) | 待处理 |
358
+ ```
359
+ asaihost.json
360
+ ├── website/password/ip/index/asaisn/aes/httpscert/mimetypes → 全局
361
+ ├── logger.* → infra/logger, http/log
362
+ ├── tm.* → core/init, ws/auth, infra/process
363
+ ├── hosts.default + hosts.<site> → core/host-config → http/*, ws/*
364
+ │ ├── weburls.* → http/static (SPA)
365
+ │ ├── ckhttp/maxhttp → http/connection
366
+ │ └── ckws/maxws/maxonline → ws/auth
367
+ ├── proxyhosts.default + proxyhosts.<name> → proxy/server
368
+ └── syscom.{tcp|grpc|rs485} → config/syscom → infra/transport-bridge
369
+ ```
426
370
 
427
371
  ---
428
372
 
429
- ### 优化优先级总结
373
+ ## 版本与优化记录
430
374
 
431
- | 优先级 | 问题 | 影响 |
432
- |--------|------|------|
433
- |已修复 | 全局变量污染 | 可维护性/多实例兼容 |
434
- |已修复 | 缺少 ETag、日志丢失、鉴权竞争 | 性能/可靠性 |
435
- |已修复 | 代理信息泄露、URL 解析异常 | 安全/稳定性 |
436
- |已修复 | 代码拆分、监控粒度 | 可维护性 |
375
+ - 模块级 workHandlers(替代 global)
376
+ - ✅ 静态 ETag / 304、LRU 路径缓存
377
+ -URL encodeURI 保护、日志 exit 刷盘
378
+ -WS send→close 竞争修复、代理 passUserInfo 开关
379
+ -HTTP 模块职责拆分(core/http/ws/proxy/config/infra/utils)
380
+ -删除冗余 .js 编译产物与 re-export shim;LRU O(1);日志 skip 启动期编译
381
+ - ✅ 静态资源 safe-path 防穿越;HTTPS 站点 HSTS 响应头