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