asai-nodejs-host 0.1.0

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/AsaiHost.ts ADDED
@@ -0,0 +1,522 @@
1
+ module.exports = ($asai: any, opt: any) => {
2
+ const { sysWork, apiWork, wsWork } = opt;
3
+ const fs: any = require("fs");
4
+ const path: any = require("path");
5
+ const url: any = require("url");
6
+
7
+ // 读取配置文件
8
+ if (!$asai.hostconfig?.ip) {
9
+ $asai.hostconfig.ip = getIp();
10
+ }
11
+ // 通过环境变量控制日志输出
12
+ // if (process.env.NODE_ENV === 'production') {
13
+ // console.log = () => {}; // 关闭调试日志
14
+ // }
15
+ console.log(`※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※`);
16
+ // 获取http或https的服务
17
+ // const http: any = require('http2');
18
+ const http: any = require("http");
19
+ const https: any = require("https");
20
+ // 获取当前时间
21
+ function getNowTime() {
22
+ const datanow: any = new Date();
23
+ return (
24
+ datanow.getMonth() +
25
+ 1 +
26
+ "-" +
27
+ datanow.getDate() +
28
+ " " +
29
+ datanow.getHours() +
30
+ ":" +
31
+ datanow.getMinutes() +
32
+ ":" +
33
+ datanow.getSeconds() +
34
+ "." +
35
+ datanow.getMilliseconds()
36
+ );
37
+ }
38
+ // 获取本机IP地址
39
+ function getIp() {
40
+ const ifaces: any = require("os").networkInterfaces();
41
+ for (const el in ifaces) {
42
+ const iface: any = ifaces[el];
43
+ if (iface && iface.length) {
44
+ for (let i: number = 0; i < iface.length; i++) {
45
+ const elc: any = iface[i];
46
+ if (
47
+ elc.family === "IPv4" &&
48
+ elc.address !== "127.0.0.1" &&
49
+ !elc.internal
50
+ ) {
51
+ return elc.address;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ return "";
57
+ }
58
+
59
+ function getHTTPServer() {
60
+ try {
61
+ if ($asai.hostconfig.httptype.includes("https")) {
62
+ https.AsCreateServer = (callback: any) => {
63
+ return https.createServer($asai.hostconfig.httpscert, callback);
64
+ };
65
+ return https;
66
+ } else {
67
+ http.AsCreateServer = http.createServer;
68
+ return http;
69
+ }
70
+ } catch (err: any) {
71
+ console.log(666.907, err);
72
+ http.AsCreateServer = http.createServer;
73
+ return http;
74
+ }
75
+ }
76
+ function getWsUserinfo(req: any) {
77
+ try {
78
+ const headers = Object.fromEntries(
79
+ Array.from({ length: req.rawHeaders.length / 2 }, (_, i) => [
80
+ req.rawHeaders[i * 2].toLowerCase(),
81
+ req.rawHeaders[i * 2 + 1],
82
+ ])
83
+ );
84
+ const userinfo = headers["sec-websocket-protocol"]?.split("##");
85
+ return deUserInfo(userinfo);
86
+ } catch (err) {}
87
+ return null;
88
+ }
89
+ // Ws请求头校验
90
+ function checkWsHeader(req: any) {
91
+ req.Userinfo = getWsUserinfo(req);
92
+ console.log(666.2001, "WS", req.Userinfo[0]);
93
+ return true;
94
+ }
95
+ function getHttpUserinfo(req: any) {
96
+ try {
97
+ const headers = Object.fromEntries(
98
+ Array.from({ length: req.rawHeaders.length / 2 }, (_, i) => [
99
+ req.rawHeaders[i * 2].toLowerCase(),
100
+ req.rawHeaders[i * 2 + 1],
101
+ ])
102
+ );
103
+ const userinfo = headers["userinfo"]?.split("##");
104
+ return deUserInfo(userinfo);
105
+ } catch (err) {}
106
+ return null;
107
+ }
108
+ // tcp请求头校验
109
+ function checkHttpHeader(req: any) {
110
+ req.Userinfo = getHttpUserinfo(req);
111
+ return true;
112
+ }
113
+ function deUserInfo(userinfo: any) {
114
+ if (userinfo[2]) {
115
+ userinfo[2] = $asai.$lib.AsCode.asdecode(
116
+ userinfo[2],
117
+ $asai.hostconfig.asaisn
118
+ );
119
+ userinfo[0] = $asai.$lib.AsCode.asdecode(userinfo[0], userinfo[2]);
120
+ userinfo[1] = $asai.$lib.AsCode.asdecode(userinfo[1], userinfo[2]);
121
+ }
122
+ return userinfo;
123
+ }
124
+ // 代理服务器
125
+ if ($asai.hostconfig?.proxyhosts) {
126
+ // 循环启动配置中的服务
127
+ Object.keys($asai.hostconfig.proxyhosts).forEach((el) => {
128
+ startProxyServer(el);
129
+ });
130
+ // 启动代理服务器
131
+ function startProxyServer(proxyhost: string) {
132
+ const proxycfg = $asai.hostconfig.proxyhosts[proxyhost];
133
+ if (!proxycfg.url && proxycfg.port) {
134
+ proxycfg.url =
135
+ $asai.hostconfig.httptype + $asai.hostconfig.ip + ":" + proxycfg.port;
136
+ }
137
+ if (proxycfg.url) {
138
+ const http = getHTTPServer();
139
+ const proxyServer = http.AsCreateServer((req: any, res: any) => {
140
+ // 启用Keep-Alive复用连接
141
+ const agent = new http.Agent({ keepAlive: true });
142
+ const forwardRequest = http.request(
143
+ {
144
+ agent: agent, // 复用连接池
145
+ method: req.method,
146
+ ...url.parse(proxycfg.url + req.url),
147
+ },
148
+ (forwardRes: any) => {
149
+ // 设置响应头(可以选择性地转发原始响应头)
150
+ Object.keys(forwardRes.headers).forEach((key) => {
151
+ res.setHeader(key, forwardRes.headers[key]);
152
+ });
153
+ res.statusCode = forwardRes.statusCode;
154
+ console.log(666.001, getHttpUserinfo(req));
155
+ res.setHeader("Userinfo", getHttpUserinfo(req));
156
+ // 转发响应数据
157
+ req.pipe(forwardRequest); // 自动处理背压
158
+ forwardRes.pipe(res);
159
+ // forwardRes.pipe(res, { end: true }); // 手动数据传递
160
+ }
161
+ );
162
+
163
+ // 处理转发请求的错误
164
+ forwardRequest.on("error", (err: any) => {
165
+ console.error("Error during forward request:", err);
166
+ res.writeHead(502); // Bad Gateway
167
+ res.end("Bad Gateway");
168
+ });
169
+ // 转发请求数据
170
+ req.pipe(forwardRequest, { end: true });
171
+
172
+ // 请求超时
173
+ forwardRequest.setTimeout(50000, () => {
174
+ forwardRequest.destroy();
175
+ res.writeHead(504);
176
+ res.end("Gateway Timeout");
177
+ });
178
+ });
179
+ if (proxycfg.proxyip) {
180
+ proxyServer.listen(
181
+ proxycfg.proxyport || $asai.hostconfig.proxyport,
182
+ proxycfg.proxyip,
183
+ () => {
184
+ console.log(`[${proxyhost}] Start At ${getNowTime()}.`);
185
+ console.log(
186
+ `\x1B[45;28mProxy Server ${$asai.hostconfig.httptype}${
187
+ proxycfg.proxyip
188
+ }:${proxycfg.proxyport || $asai.hostconfig.proxyport}\x1B[0m`
189
+ );
190
+ console.log(`================================================`);
191
+ }
192
+ );
193
+ } else {
194
+ proxyServer.listen(
195
+ proxycfg.proxyport || $asai.hostconfig.proxyport,
196
+ () => {
197
+ console.log(`[${proxyhost}] Start At ${getNowTime()}.`);
198
+ console.log(
199
+ `\x1B[45;28mProxy server ${
200
+ $asai.hostconfig.httptype
201
+ }localhost:${
202
+ proxycfg.proxyport || $asai.hostconfig.proxyport
203
+ }\x1B[0m`
204
+ );
205
+ console.log(`================================================`);
206
+ }
207
+ );
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ // 网页与文件服务器
214
+ if ($asai.hostconfig?.hosts) {
215
+ $asai.startHTTPServer = startHTTPServer;
216
+ // 循环启动配置中的服务
217
+ Object.keys($asai.hostconfig.hosts).forEach((el) => {
218
+ startHTTPServer(el);
219
+ });
220
+
221
+ // 创建WS服务
222
+ function createWSHost(hostdir: string | number) {
223
+ try {
224
+ if (!$asai.serversws[hostdir]) {
225
+ const { WebSocket, WebSocketServer } = require("ws");
226
+ const wsopt = {};
227
+ // ws的配置项
228
+ $asai.serversws[hostdir] = new WebSocketServer({
229
+ server: $asai.servershttp[hostdir],
230
+ // 附加配置信息
231
+ perMessageDeflate: false, // 关闭压缩减少CPU开销
232
+ // perMessageDeflate: {
233
+ // zlibDeflateOptions: {
234
+ // // See zlib defaults.
235
+ // chunkSize: 1024,
236
+ // memLevel: 7,
237
+ // level: 3,
238
+ // },
239
+ // zlibInflateOptions: {
240
+ // chunkSize: 10 * 1024,
241
+ // },
242
+ // // Other options settable:
243
+ // clientNoContextTakeover: true, // Defaults to negotiated value.
244
+ // serverNoContextTakeover: true, // Defaults to negotiated value.
245
+ // serverMaxWindowBits: 10, // Defaults to negotiated value.
246
+ // // Below options specified as default values.
247
+ // concurrencyLimit: 10, // Limits zlib concurrency for perf.
248
+ // threshold: 1024, // Size (in bytes) below which messages
249
+ // // should not be compressed if context takeover is disabled.
250
+ // },
251
+ });
252
+ // 服务器ws服务关闭操作,清空定时器
253
+ function wsServerClose() {
254
+ if (
255
+ $asai.tasksws[hostdir] &&
256
+ typeof $asai.tasksws[hostdir] === "object"
257
+ ) {
258
+ Object.values($asai.tasksws[hostdir]).forEach((el: any) => {
259
+ el && clearInterval(el);
260
+ });
261
+ $asai.tasksws[hostdir] = {};
262
+ }
263
+ }
264
+
265
+ // 服务端异常错误关闭
266
+ $asai.serversws[hostdir].on("error", () => {
267
+ wsServerClose();
268
+ $asai.hostconfig.log && console.log(666.303, "WS Server error!");
269
+ });
270
+ // 服务端异常关闭
271
+ $asai.serversws[hostdir].on("close", () => {
272
+ wsServerClose();
273
+ $asai.hostconfig.log && console.log(666.301, "WS Server close!");
274
+ });
275
+
276
+ if ($asai.hostconfig.status.connectionsws) {
277
+ // 初始化记录ws服务连接
278
+ if (!$asai.connectionsws[hostdir]) {
279
+ $asai.connectionsws[hostdir] = new WeakMap();
280
+ }
281
+ }
282
+ // 服务被连接后...
283
+ $asai.serversws[hostdir].on("connection", (ws: any, req: any) => {
284
+ if (!$asai.tasksws[hostdir]) {
285
+ $asai.tasksws[hostdir] = {};
286
+ }
287
+ if (!ws?.tasksws) {
288
+ ws.tasksws = {};
289
+ }
290
+ if (checkWsHeader(req)) {
291
+ $asai.hostconfig.log &&
292
+ console.log(666.2002, req.Userinfo, "wsClient connection!");
293
+ function closeWs() {
294
+ ws.close();
295
+ }
296
+ if (
297
+ $asai.hostconfig.status.connectionsws &&
298
+ (!req.Userinfo[0] ||
299
+ !req.Userinfo[2] ||
300
+ $asai.connectionsws[hostdir][req.Userinfo[0]])
301
+ ) {
302
+ // 连接用户信息不符合条件,已存在同账户的登录
303
+ closeWs();
304
+ } else {
305
+ $asai.hostconfig.log && console.log(666.2006, "ok!");
306
+ // 正常登录
307
+ const [us, lv, tk] = req.Userinfo || [];
308
+ ws.Userinfo = { us, lv, tk }; // 赋值给ws
309
+ if ($asai.hostconfig.status.connectionsws) {
310
+ $asai.connectionsws[hostdir][req.Userinfo[0]] = ws;
311
+ }
312
+ // 发送消息
313
+ $asai.serversws[hostdir].sendws = (
314
+ msg: any,
315
+ type: number,
316
+ curws: any
317
+ ) => {
318
+ if (type) {
319
+ // 广播消息
320
+ // wsopt.ulen = 0;
321
+ $asai.serversws[hostdir].clients.forEach((client: any) => {
322
+ // wsopt.ulen++;
323
+ if (
324
+ (type == 2 || client !== curws) &&
325
+ client.readyState == WebSocket.OPEN
326
+ ) {
327
+ client.send(msg);
328
+ }
329
+ console.info(666.801, client.Userinfo);
330
+ });
331
+ // $asai.hostconfig.log &&
332
+ // console.log(666.808, "wsClient Public!", wsopt.ulen);
333
+ } else {
334
+ // api方式回复消息
335
+ curws.send(msg);
336
+ }
337
+ };
338
+
339
+ //监听客户端发来的消息
340
+ ws.on("message", (msg: any) => {
341
+ wsWork(msg, ws, hostdir, req);
342
+ });
343
+ // 客户端关闭
344
+ ws.on("close", (err: any) => {
345
+ $asai.hostconfig.log &&
346
+ console.log(
347
+ 666.103,
348
+ req.Userinfo[0] + " wsClient close!",
349
+ err
350
+ );
351
+ if ($asai.hostconfig.status.connectionsws) {
352
+ // 关闭点对点客户端的任务
353
+ if (ws.tasksws && typeof ws.tasksws === "object") {
354
+ Object.values(ws.tasksws).forEach((el: any) => {
355
+ el && clearInterval(el);
356
+ });
357
+ ws.tasksws = null;
358
+ }
359
+ if (
360
+ req.Userinfo[0] &&
361
+ $asai.connectionsws[hostdir][req.Userinfo[0]]
362
+ ) {
363
+ delete $asai.connectionsws[hostdir][req.Userinfo[0]];
364
+ }
365
+ }
366
+ });
367
+ }
368
+ }
369
+ });
370
+ }
371
+ } catch (err) {}
372
+ return $asai.serversws[hostdir];
373
+ }
374
+
375
+ // 请求的文件相对路径
376
+ function getFilePath(hostdir: string, requrl: string) {
377
+ let filePath = "" + requrl;
378
+ if (filePath == "/" || filePath == "") {
379
+ filePath = "/index.html";
380
+ }
381
+ if (hostdir == "home") {
382
+ filePath = "." + filePath;
383
+ } else {
384
+ filePath = "./" + hostdir + filePath;
385
+ }
386
+ return filePath;
387
+ }
388
+ // 读取服务器上文件
389
+ function fsReadFile(hostdirs: any, requrl: any) {
390
+ return new Promise((resolve, reject) => {
391
+ // 尝试顺序读取多个路径
392
+ const tryRead = async (index = 0) => {
393
+ if (index >= hostdirs.length) return reject(404);
394
+ const filePath = getFilePath(hostdirs[index], requrl);
395
+ fs.readFile(filePath, (err: any, data: any) => {
396
+ if (err) tryRead(index + 1);
397
+ else resolve({ resdata: data, filepath: filePath });
398
+ });
399
+ };
400
+ tryRead();
401
+ });
402
+ }
403
+ // 启动HTTP服务
404
+ function startHTTPServer(hostdir: string) {
405
+ // 初始化服务配置
406
+ const hostcfg = $asai.hostconfig.hosts[hostdir];
407
+ try {
408
+ if (!$asai.servershttp[hostdir]) {
409
+ if (hostcfg.mimetypes) {
410
+ Object.assign(hostcfg.mimetypes, $asai.hostconfig.mimetypes);
411
+ } else {
412
+ hostcfg.mimetypes = $asai.hostconfig.mimetypes;
413
+ }
414
+ // 创建服务
415
+ const http = getHTTPServer();
416
+ $asai.servershttp[hostdir] = http.AsCreateServer(
417
+ (req: any, res: any) => {
418
+ if (checkHttpHeader(req)) {
419
+ if (req.url.startsWith("/sys")) {
420
+ sysWork(req, res, hostdir);
421
+ } else if (req.url.startsWith("/api")) {
422
+ apiWork(req, res, hostdir);
423
+ } else if (hostcfg.hostdirs) {
424
+ fsReadFile(hostcfg.hostdirs, req.url)
425
+ .then((resfile: any) => {
426
+ res.writeHead(200, {
427
+ "Content-Encoding": "gzip",
428
+ "Content-Type":
429
+ hostcfg.mimetypes[path.extname(resfile.filepath)] ||
430
+ "application/octet-stream",
431
+ "Cache-Control": "public, max-age=3600", // 客户端缓存1小时
432
+ });
433
+ // 提前处理压缩数据为.gz
434
+ fs.createReadStream("file.gz").pipe(res);
435
+ res.end(resfile.resdata);
436
+ })
437
+ .catch((err) => {
438
+ if (err === 404) {
439
+ res.writeHead(404, {
440
+ "Content-Type": "text/plain; charset=utf-8",
441
+ });
442
+ res.end(`File not found!`);
443
+ } else {
444
+ res.writeHead(500, {
445
+ "Content-Type": "text/plain; charset=utf-8",
446
+ });
447
+ res.end(`${err} error!`);
448
+ }
449
+ });
450
+ } else {
451
+ res.writeHead(404, {
452
+ "Content-Type": "text/plain; charset=utf-8",
453
+ });
454
+ res.end(`404`);
455
+ }
456
+ } else {
457
+ res.writeHead(502, {
458
+ "Content-Type": "text/plain; charset=utf-8",
459
+ });
460
+ res.end(`502`);
461
+ }
462
+ }
463
+ );
464
+ hostcfg.ws && createWSHost(hostdir); // 创建ws服务
465
+ // 启动服务
466
+ if (hostcfg.ip || $asai.hostconfig.ip) {
467
+ $asai.servershttp[hostdir].listen(
468
+ hostcfg.port,
469
+ hostcfg.ip || $asai.hostconfig.ip,
470
+ () => {
471
+ console.log(`[${hostdir}] Start At ${getNowTime()}.`);
472
+ hostcfg.ws &&
473
+ console.log(
474
+ `\x1B[44;28mWS Server ${
475
+ $asai.hostconfig.wstype +
476
+ (hostcfg.ip || $asai.hostconfig.ip)
477
+ }:${hostcfg.port}\x1B[0m`
478
+ );
479
+ console.log(
480
+ `\x1B[42;28mHTTP Server ${
481
+ $asai.hostconfig.httptype +
482
+ (hostcfg.ip || $asai.hostconfig.ip)
483
+ }:${hostcfg.port}\x1B[0m`
484
+ );
485
+ console.log(`================================================`);
486
+ }
487
+ );
488
+ } else {
489
+ $asai.servershttp[hostdir].listen(hostcfg.port, () => {
490
+ console.log(`[${hostdir}] Start At ${getNowTime()}.`);
491
+ hostcfg.ws &&
492
+ console.log(
493
+ `\x1B[41;28mWS Server${$asai.hostconfig.wstype}localhost:${hostcfg.port}\x1B[0m`
494
+ );
495
+ console.log(
496
+ `\x1B[42;28mHTTP Server ${$asai.hostconfig.httptype}localhost:${hostcfg.port}\x1B[0m`
497
+ );
498
+ console.log(`================================================`);
499
+ });
500
+ }
501
+ }
502
+ } catch (err) {
503
+ console.log(666.303, err);
504
+ }
505
+
506
+ if ($asai.hostconfig.status.connectionshttp) {
507
+ // 记录http服务连接(先删除连接用户,再操作node服务,成功几率更大,但记录用户会造成变量变大)
508
+ $asai.servershttp[hostdir].on("connection", (connection: any) => {
509
+ if (!$asai.connectionshttp[hostdir]) {
510
+ $asai.connectionshttp[hostdir] = new Set();
511
+ }
512
+ $asai.connectionshttp[hostdir].add(connection);
513
+ // 处理关闭服务
514
+ connection.on("close", () => {
515
+ $asai.connectionshttp[hostdir].delete(connection);
516
+ });
517
+ });
518
+ }
519
+ return $asai.servershttp[hostdir];
520
+ }
521
+ }
522
+ };