dynapm 1.0.12 → 1.0.14
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 +41 -0
- package/CLAUDE.md +8 -16
- package/README.md +25 -24
- package/README_zh.md +26 -18
- package/TASK.md +53 -0
- package/dist/src/config/types.d.ts +50 -1
- package/dist/src/core/admin-api.d.ts +76 -0
- package/dist/src/core/gateway.d.ts +48 -17
- package/dist/src/index.js +1314 -521
- package/dist/src/utils/format.d.ts +6 -0
- package/package.json +9 -9
package/dist/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_modules__ = {
|
|
3
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/buffer-util.js"
|
|
3
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/buffer-util.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
4
4
|
const { EMPTY_BUFFER } = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js");
|
|
5
5
|
const FastBuffer = Buffer[Symbol.species];
|
|
6
6
|
function concat(list, totalLength) {
|
|
@@ -61,13 +61,13 @@ var __webpack_modules__ = {
|
|
|
61
61
|
};
|
|
62
62
|
} catch (e) {}
|
|
63
63
|
},
|
|
64
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js"
|
|
64
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js" (module) {
|
|
65
65
|
const BINARY_TYPES = [
|
|
66
66
|
'nodebuffer',
|
|
67
67
|
'arraybuffer',
|
|
68
68
|
'fragments'
|
|
69
69
|
];
|
|
70
|
-
const hasBlob =
|
|
70
|
+
const hasBlob = "u" > typeof Blob;
|
|
71
71
|
if (hasBlob) BINARY_TYPES.push('blob');
|
|
72
72
|
module.exports = {
|
|
73
73
|
BINARY_TYPES,
|
|
@@ -82,7 +82,7 @@ var __webpack_modules__ = {
|
|
|
82
82
|
NOOP: ()=>{}
|
|
83
83
|
};
|
|
84
84
|
},
|
|
85
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/event-target.js"
|
|
85
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/event-target.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
86
86
|
const { kForOnEventAttribute, kListener } = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js");
|
|
87
87
|
const kCode = Symbol('kCode');
|
|
88
88
|
const kData = Symbol('kData');
|
|
@@ -227,7 +227,7 @@ var __webpack_modules__ = {
|
|
|
227
227
|
else listener.call(thisArg, event);
|
|
228
228
|
}
|
|
229
229
|
},
|
|
230
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/extension.js"
|
|
230
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/extension.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
231
231
|
const { tokenChars } = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js");
|
|
232
232
|
function push(dest, name, elem) {
|
|
233
233
|
if (void 0 === dest[name]) dest[name] = [
|
|
@@ -249,54 +249,51 @@ var __webpack_modules__ = {
|
|
|
249
249
|
let i = 0;
|
|
250
250
|
for(; i < header.length; i++){
|
|
251
251
|
code = header.charCodeAt(i);
|
|
252
|
-
if (void 0 === extensionName) {
|
|
253
|
-
if (-1 ===
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
push(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
} else throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
286
|
-
} else if (isEscaping) {
|
|
252
|
+
if (void 0 === extensionName) if (-1 === end && 1 === tokenChars[code]) {
|
|
253
|
+
if (-1 === start) start = i;
|
|
254
|
+
} else if (0 !== i && (0x20 === code || 0x09 === code)) {
|
|
255
|
+
if (-1 === end && -1 !== start) end = i;
|
|
256
|
+
} else if (0x3b === code || 0x2c === code) {
|
|
257
|
+
if (-1 === start) throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
258
|
+
if (-1 === end) end = i;
|
|
259
|
+
const name = header.slice(start, end);
|
|
260
|
+
if (0x2c === code) {
|
|
261
|
+
push(offers, name, params);
|
|
262
|
+
params = Object.create(null);
|
|
263
|
+
} else extensionName = name;
|
|
264
|
+
start = end = -1;
|
|
265
|
+
} else throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
266
|
+
else if (void 0 === paramName) if (-1 === end && 1 === tokenChars[code]) {
|
|
267
|
+
if (-1 === start) start = i;
|
|
268
|
+
} else if (0x20 === code || 0x09 === code) {
|
|
269
|
+
if (-1 === end && -1 !== start) end = i;
|
|
270
|
+
} else if (0x3b === code || 0x2c === code) {
|
|
271
|
+
if (-1 === start) throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
272
|
+
if (-1 === end) end = i;
|
|
273
|
+
push(params, header.slice(start, end), true);
|
|
274
|
+
if (0x2c === code) {
|
|
275
|
+
push(offers, extensionName, params);
|
|
276
|
+
params = Object.create(null);
|
|
277
|
+
extensionName = void 0;
|
|
278
|
+
}
|
|
279
|
+
start = end = -1;
|
|
280
|
+
} else if (0x3d === code && -1 !== start && -1 === end) {
|
|
281
|
+
paramName = header.slice(start, i);
|
|
282
|
+
start = end = -1;
|
|
283
|
+
} else throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
284
|
+
else if (isEscaping) {
|
|
287
285
|
if (1 !== tokenChars[code]) throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
288
286
|
if (-1 === start) start = i;
|
|
289
287
|
else if (!mustUnescape) mustUnescape = true;
|
|
290
288
|
isEscaping = false;
|
|
291
|
-
} else if (inQuotes) {
|
|
292
|
-
if (1 ===
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
} else if (0x22 === code && 0x3d === header.charCodeAt(i - 1)) inQuotes = true;
|
|
289
|
+
} else if (inQuotes) if (1 === tokenChars[code]) {
|
|
290
|
+
if (-1 === start) start = i;
|
|
291
|
+
} else if (0x22 === code && -1 !== start) {
|
|
292
|
+
inQuotes = false;
|
|
293
|
+
end = i;
|
|
294
|
+
} else if (0x5c === code) isEscaping = true;
|
|
295
|
+
else throw new SyntaxError(`Unexpected character at index ${i}`);
|
|
296
|
+
else if (0x22 === code && 0x3d === header.charCodeAt(i - 1)) inQuotes = true;
|
|
300
297
|
else if (-1 === end && 1 === tokenChars[code]) {
|
|
301
298
|
if (-1 === start) start = i;
|
|
302
299
|
} else if (-1 !== start && (0x20 === code || 0x09 === code)) {
|
|
@@ -352,7 +349,7 @@ var __webpack_modules__ = {
|
|
|
352
349
|
parse
|
|
353
350
|
};
|
|
354
351
|
},
|
|
355
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/limiter.js"
|
|
352
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/limiter.js" (module) {
|
|
356
353
|
const kDone = Symbol('kDone');
|
|
357
354
|
const kRun = Symbol('kRun');
|
|
358
355
|
class Limiter {
|
|
@@ -380,7 +377,7 @@ var __webpack_modules__ = {
|
|
|
380
377
|
}
|
|
381
378
|
module.exports = Limiter;
|
|
382
379
|
},
|
|
383
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js"
|
|
380
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
384
381
|
const zlib = __webpack_require__("zlib");
|
|
385
382
|
const bufferUtil = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/buffer-util.js");
|
|
386
383
|
const Limiter = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/limiter.js");
|
|
@@ -575,10 +572,7 @@ var __webpack_modules__ = {
|
|
|
575
572
|
}
|
|
576
573
|
function inflateOnData(chunk) {
|
|
577
574
|
this[kTotalLength] += chunk.length;
|
|
578
|
-
if (this[kPerMessageDeflate]._maxPayload < 1 || this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload)
|
|
579
|
-
this[kBuffers].push(chunk);
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
575
|
+
if (this[kPerMessageDeflate]._maxPayload < 1 || this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload) return void this[kBuffers].push(chunk);
|
|
582
576
|
this[kError] = new RangeError('Max payload size exceeded');
|
|
583
577
|
this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
|
|
584
578
|
this[kError][kStatusCode] = 1009;
|
|
@@ -587,15 +581,12 @@ var __webpack_modules__ = {
|
|
|
587
581
|
}
|
|
588
582
|
function inflateOnError(err) {
|
|
589
583
|
this[kPerMessageDeflate]._inflate = null;
|
|
590
|
-
if (this[kError])
|
|
591
|
-
this[kCallback](this[kError]);
|
|
592
|
-
return;
|
|
593
|
-
}
|
|
584
|
+
if (this[kError]) return void this[kCallback](this[kError]);
|
|
594
585
|
err[kStatusCode] = 1007;
|
|
595
586
|
this[kCallback](err);
|
|
596
587
|
}
|
|
597
588
|
},
|
|
598
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/receiver.js"
|
|
589
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/receiver.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
599
590
|
const { Writable } = __webpack_require__("stream");
|
|
600
591
|
const PerMessageDeflate = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js");
|
|
601
592
|
const { BINARY_TYPES, EMPTY_BUFFER, kStatusCode, kWebSocket } = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js");
|
|
@@ -818,10 +809,7 @@ var __webpack_modules__ = {
|
|
|
818
809
|
data = this.consume(this._payloadLength);
|
|
819
810
|
if (this._masked && (this._mask[0] | this._mask[1] | this._mask[2] | this._mask[3]) !== 0) unmask(data, this._mask);
|
|
820
811
|
}
|
|
821
|
-
if (this._opcode > 0x07)
|
|
822
|
-
this.controlMessage(data, cb);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
812
|
+
if (this._opcode > 0x07) return void this.controlMessage(data, cb);
|
|
825
813
|
if (this._compressed) {
|
|
826
814
|
this._state = INFLATING;
|
|
827
815
|
this.decompress(data, cb);
|
|
@@ -945,7 +933,7 @@ var __webpack_modules__ = {
|
|
|
945
933
|
}
|
|
946
934
|
module.exports = Receiver;
|
|
947
935
|
},
|
|
948
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/sender.js"
|
|
936
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/sender.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
949
937
|
const { Duplex } = __webpack_require__("stream");
|
|
950
938
|
const { randomFillSync } = __webpack_require__("crypto");
|
|
951
939
|
const PerMessageDeflate = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/permessage-deflate.js");
|
|
@@ -999,13 +987,12 @@ var __webpack_modules__ = {
|
|
|
999
987
|
offset = 6;
|
|
1000
988
|
}
|
|
1001
989
|
let dataLength;
|
|
1002
|
-
if ('string' == typeof data)
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
} else {
|
|
990
|
+
if ('string' == typeof data) if ((!options.mask || skipMasking) && void 0 !== options[kByteLength]) dataLength = options[kByteLength];
|
|
991
|
+
else {
|
|
992
|
+
data = Buffer.from(data);
|
|
993
|
+
dataLength = data.length;
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
1009
996
|
dataLength = data.length;
|
|
1010
997
|
merge = options.mask && options.readOnly && !skipMasking;
|
|
1011
998
|
}
|
|
@@ -1054,19 +1041,18 @@ var __webpack_modules__ = {
|
|
|
1054
1041
|
close(code, data, mask, cb) {
|
|
1055
1042
|
let buf;
|
|
1056
1043
|
if (void 0 === code) buf = EMPTY_BUFFER;
|
|
1057
|
-
else if ('number' == typeof code && isValidStatusCode(code)) {
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
} else throw new TypeError('First argument must be a valid error code number');
|
|
1044
|
+
else if ('number' == typeof code && isValidStatusCode(code)) if (void 0 !== data && data.length) {
|
|
1045
|
+
const length = Buffer.byteLength(data);
|
|
1046
|
+
if (length > 123) throw new RangeError('The message must not be greater than 123 bytes');
|
|
1047
|
+
buf = Buffer.allocUnsafe(2 + length);
|
|
1048
|
+
buf.writeUInt16BE(code, 0);
|
|
1049
|
+
if ('string' == typeof data) buf.write(data, 2);
|
|
1050
|
+
else buf.set(data, 2);
|
|
1051
|
+
} else {
|
|
1052
|
+
buf = Buffer.allocUnsafe(2);
|
|
1053
|
+
buf.writeUInt16BE(code, 0);
|
|
1054
|
+
}
|
|
1055
|
+
else throw new TypeError('First argument must be a valid error code number');
|
|
1070
1056
|
const options = {
|
|
1071
1057
|
[kByteLength]: buf.length,
|
|
1072
1058
|
fin: true,
|
|
@@ -1111,16 +1097,15 @@ var __webpack_modules__ = {
|
|
|
1111
1097
|
readOnly,
|
|
1112
1098
|
rsv1: false
|
|
1113
1099
|
};
|
|
1114
|
-
if (isBlob(data))
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
} else if (this._state !== DEFAULT) this.enqueue([
|
|
1100
|
+
if (isBlob(data)) if (this._state !== DEFAULT) this.enqueue([
|
|
1101
|
+
this.getBlobData,
|
|
1102
|
+
data,
|
|
1103
|
+
false,
|
|
1104
|
+
options,
|
|
1105
|
+
cb
|
|
1106
|
+
]);
|
|
1107
|
+
else this.getBlobData(data, false, options, cb);
|
|
1108
|
+
else if (this._state !== DEFAULT) this.enqueue([
|
|
1124
1109
|
this.dispatch,
|
|
1125
1110
|
data,
|
|
1126
1111
|
false,
|
|
@@ -1154,16 +1139,15 @@ var __webpack_modules__ = {
|
|
|
1154
1139
|
readOnly,
|
|
1155
1140
|
rsv1: false
|
|
1156
1141
|
};
|
|
1157
|
-
if (isBlob(data))
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
} else if (this._state !== DEFAULT) this.enqueue([
|
|
1142
|
+
if (isBlob(data)) if (this._state !== DEFAULT) this.enqueue([
|
|
1143
|
+
this.getBlobData,
|
|
1144
|
+
data,
|
|
1145
|
+
false,
|
|
1146
|
+
options,
|
|
1147
|
+
cb
|
|
1148
|
+
]);
|
|
1149
|
+
else this.getBlobData(data, false, options, cb);
|
|
1150
|
+
else if (this._state !== DEFAULT) this.enqueue([
|
|
1167
1151
|
this.dispatch,
|
|
1168
1152
|
data,
|
|
1169
1153
|
false,
|
|
@@ -1208,16 +1192,15 @@ var __webpack_modules__ = {
|
|
|
1208
1192
|
readOnly,
|
|
1209
1193
|
rsv1
|
|
1210
1194
|
};
|
|
1211
|
-
if (isBlob(data))
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
} else if (this._state !== DEFAULT) this.enqueue([
|
|
1195
|
+
if (isBlob(data)) if (this._state !== DEFAULT) this.enqueue([
|
|
1196
|
+
this.getBlobData,
|
|
1197
|
+
data,
|
|
1198
|
+
this._compress,
|
|
1199
|
+
opts,
|
|
1200
|
+
cb
|
|
1201
|
+
]);
|
|
1202
|
+
else this.getBlobData(data, this._compress, opts, cb);
|
|
1203
|
+
else if (this._state !== DEFAULT) this.enqueue([
|
|
1221
1204
|
this.dispatch,
|
|
1222
1205
|
data,
|
|
1223
1206
|
this._compress,
|
|
@@ -1248,10 +1231,7 @@ var __webpack_modules__ = {
|
|
|
1248
1231
|
});
|
|
1249
1232
|
}
|
|
1250
1233
|
dispatch(data, compress, options, cb) {
|
|
1251
|
-
if (!compress)
|
|
1252
|
-
this.sendFrame(Sender.frame(data, options), cb);
|
|
1253
|
-
return;
|
|
1254
|
-
}
|
|
1234
|
+
if (!compress) return void this.sendFrame(Sender.frame(data, options), cb);
|
|
1255
1235
|
const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
|
|
1256
1236
|
this._bufferedBytes += options[kByteLength];
|
|
1257
1237
|
this._state = DEFLATING;
|
|
@@ -1302,7 +1282,7 @@ var __webpack_modules__ = {
|
|
|
1302
1282
|
sender.onerror(err);
|
|
1303
1283
|
}
|
|
1304
1284
|
},
|
|
1305
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/stream.js"
|
|
1285
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/stream.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
1306
1286
|
__webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js");
|
|
1307
1287
|
const { Duplex } = __webpack_require__("stream");
|
|
1308
1288
|
function emitClose(stream) {
|
|
@@ -1356,12 +1336,9 @@ var __webpack_modules__ = {
|
|
|
1356
1336
|
if (terminateOnDestroy) ws.terminate();
|
|
1357
1337
|
};
|
|
1358
1338
|
duplex._final = function(callback) {
|
|
1359
|
-
if (ws.readyState === ws.CONNECTING) {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
});
|
|
1363
|
-
return;
|
|
1364
|
-
}
|
|
1339
|
+
if (ws.readyState === ws.CONNECTING) return void ws.once('open', function() {
|
|
1340
|
+
duplex._final(callback);
|
|
1341
|
+
});
|
|
1365
1342
|
if (null === ws._socket) return;
|
|
1366
1343
|
if (ws._socket._writableState.finished) {
|
|
1367
1344
|
callback();
|
|
@@ -1377,12 +1354,9 @@ var __webpack_modules__ = {
|
|
|
1377
1354
|
if (ws.isPaused) ws.resume();
|
|
1378
1355
|
};
|
|
1379
1356
|
duplex._write = function(chunk, encoding, callback) {
|
|
1380
|
-
if (ws.readyState === ws.CONNECTING) {
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
});
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1357
|
+
if (ws.readyState === ws.CONNECTING) return void ws.once('open', function() {
|
|
1358
|
+
duplex._write(chunk, encoding, callback);
|
|
1359
|
+
});
|
|
1386
1360
|
ws.send(chunk, callback);
|
|
1387
1361
|
};
|
|
1388
1362
|
duplex.on('end', duplexOnEnd);
|
|
@@ -1391,7 +1365,7 @@ var __webpack_modules__ = {
|
|
|
1391
1365
|
}
|
|
1392
1366
|
module.exports = createWebSocketStream;
|
|
1393
1367
|
},
|
|
1394
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/subprotocol.js"
|
|
1368
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/subprotocol.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
1395
1369
|
const { tokenChars } = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js");
|
|
1396
1370
|
function parse(header) {
|
|
1397
1371
|
const protocols = new Set();
|
|
@@ -1423,7 +1397,7 @@ var __webpack_modules__ = {
|
|
|
1423
1397
|
parse
|
|
1424
1398
|
};
|
|
1425
1399
|
},
|
|
1426
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js"
|
|
1400
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/validation.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
1427
1401
|
const { isUtf8 } = __webpack_require__("buffer");
|
|
1428
1402
|
const { hasBlob } = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/constants.js");
|
|
1429
1403
|
const tokenChars = [
|
|
@@ -1599,7 +1573,7 @@ var __webpack_modules__ = {
|
|
|
1599
1573
|
};
|
|
1600
1574
|
} catch (e) {}
|
|
1601
1575
|
},
|
|
1602
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js"
|
|
1576
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
1603
1577
|
const EventEmitter = __webpack_require__("events");
|
|
1604
1578
|
const http = __webpack_require__("http");
|
|
1605
1579
|
const { Duplex } = __webpack_require__("stream");
|
|
@@ -1686,10 +1660,9 @@ var __webpack_modules__ = {
|
|
|
1686
1660
|
this._removeListeners();
|
|
1687
1661
|
this._removeListeners = this._server = null;
|
|
1688
1662
|
}
|
|
1689
|
-
if (this.clients)
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
} else process.nextTick(emitClose, this);
|
|
1663
|
+
if (this.clients) if (this.clients.size) this._shouldEmitClose = true;
|
|
1664
|
+
else process.nextTick(emitClose, this);
|
|
1665
|
+
else process.nextTick(emitClose, this);
|
|
1693
1666
|
} else {
|
|
1694
1667
|
const server = this._server;
|
|
1695
1668
|
this._removeListeners();
|
|
@@ -1734,10 +1707,7 @@ var __webpack_modules__ = {
|
|
|
1734
1707
|
});
|
|
1735
1708
|
return;
|
|
1736
1709
|
}
|
|
1737
|
-
if (!this.shouldHandle(req))
|
|
1738
|
-
abortHandshake(socket, 400);
|
|
1739
|
-
return;
|
|
1740
|
-
}
|
|
1710
|
+
if (!this.shouldHandle(req)) return void abortHandshake(socket, 400);
|
|
1741
1711
|
const secWebSocketProtocol = req.headers['sec-websocket-protocol'];
|
|
1742
1712
|
let protocols = new Set();
|
|
1743
1713
|
if (void 0 !== secWebSocketProtocol) try {
|
|
@@ -1769,13 +1739,10 @@ var __webpack_modules__ = {
|
|
|
1769
1739
|
secure: !!(req.socket.authorized || req.socket.encrypted),
|
|
1770
1740
|
req
|
|
1771
1741
|
};
|
|
1772
|
-
if (2 === this.options.verifyClient.length) {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
});
|
|
1777
|
-
return;
|
|
1778
|
-
}
|
|
1742
|
+
if (2 === this.options.verifyClient.length) return void this.options.verifyClient(info, (verified, code, message, headers)=>{
|
|
1743
|
+
if (!verified) return abortHandshake(socket, code || 401, message, headers);
|
|
1744
|
+
this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
|
|
1745
|
+
});
|
|
1779
1746
|
if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
|
|
1780
1747
|
}
|
|
1781
1748
|
this.completeUpgrade(extensions, key, protocols, req, socket, head, cb);
|
|
@@ -1860,7 +1827,7 @@ var __webpack_modules__ = {
|
|
|
1860
1827
|
} else abortHandshake(socket, code, message, headers);
|
|
1861
1828
|
}
|
|
1862
1829
|
},
|
|
1863
|
-
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js"
|
|
1830
|
+
"./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js" (module, __unused_rspack_exports, __webpack_require__) {
|
|
1864
1831
|
const EventEmitter = __webpack_require__("events");
|
|
1865
1832
|
const https = __webpack_require__("https");
|
|
1866
1833
|
const http = __webpack_require__("http");
|
|
@@ -1911,14 +1878,12 @@ var __webpack_modules__ = {
|
|
|
1911
1878
|
this._isServer = false;
|
|
1912
1879
|
this._redirects = 0;
|
|
1913
1880
|
if (void 0 === protocols) protocols = [];
|
|
1914
|
-
else if (!Array.isArray(protocols)) {
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
];
|
|
1921
|
-
}
|
|
1881
|
+
else if (!Array.isArray(protocols)) if ('object' == typeof protocols && null !== protocols) {
|
|
1882
|
+
options = protocols;
|
|
1883
|
+
protocols = [];
|
|
1884
|
+
} else protocols = [
|
|
1885
|
+
protocols
|
|
1886
|
+
];
|
|
1922
1887
|
initAsClient(this, address, protocols, options);
|
|
1923
1888
|
} else {
|
|
1924
1889
|
this._autoPong = options.autoPong;
|
|
@@ -2043,10 +2008,7 @@ var __webpack_modules__ = {
|
|
|
2043
2008
|
mask = void 0;
|
|
2044
2009
|
}
|
|
2045
2010
|
if ('number' == typeof data) data = data.toString();
|
|
2046
|
-
if (this.readyState !== WebSocket.OPEN)
|
|
2047
|
-
sendAfterClose(this, data, cb);
|
|
2048
|
-
return;
|
|
2049
|
-
}
|
|
2011
|
+
if (this.readyState !== WebSocket.OPEN) return void sendAfterClose(this, data, cb);
|
|
2050
2012
|
if (void 0 === mask) mask = !this._isServer;
|
|
2051
2013
|
this._sender.ping(data || EMPTY_BUFFER, mask, cb);
|
|
2052
2014
|
}
|
|
@@ -2060,10 +2022,7 @@ var __webpack_modules__ = {
|
|
|
2060
2022
|
mask = void 0;
|
|
2061
2023
|
}
|
|
2062
2024
|
if ('number' == typeof data) data = data.toString();
|
|
2063
|
-
if (this.readyState !== WebSocket.OPEN)
|
|
2064
|
-
sendAfterClose(this, data, cb);
|
|
2065
|
-
return;
|
|
2066
|
-
}
|
|
2025
|
+
if (this.readyState !== WebSocket.OPEN) return void sendAfterClose(this, data, cb);
|
|
2067
2026
|
if (void 0 === mask) mask = !this._isServer;
|
|
2068
2027
|
this._sender.pong(data || EMPTY_BUFFER, mask, cb);
|
|
2069
2028
|
}
|
|
@@ -2079,10 +2038,7 @@ var __webpack_modules__ = {
|
|
|
2079
2038
|
options = {};
|
|
2080
2039
|
}
|
|
2081
2040
|
if ('number' == typeof data) data = data.toString();
|
|
2082
|
-
if (this.readyState !== WebSocket.OPEN)
|
|
2083
|
-
sendAfterClose(this, data, cb);
|
|
2084
|
-
return;
|
|
2085
|
-
}
|
|
2041
|
+
if (this.readyState !== WebSocket.OPEN) return void sendAfterClose(this, data, cb);
|
|
2086
2042
|
const opts = {
|
|
2087
2043
|
binary: 'string' != typeof data,
|
|
2088
2044
|
mask: !this._isServer,
|
|
@@ -2221,9 +2177,8 @@ var __webpack_modules__ = {
|
|
|
2221
2177
|
} else invalidUrlMessage = 'The URL\'s protocol must be one of "ws:", "wss:", "http:", "https:", or "ws+unix:"';
|
|
2222
2178
|
if (invalidUrlMessage) {
|
|
2223
2179
|
const err = new SyntaxError(invalidUrlMessage);
|
|
2224
|
-
if (0
|
|
2225
|
-
|
|
2226
|
-
return;
|
|
2180
|
+
if (0 !== websocket._redirects) return void emitErrorAndClose(websocket, err);
|
|
2181
|
+
throw err;
|
|
2227
2182
|
}
|
|
2228
2183
|
const defaultPort = isSecure ? 443 : 80;
|
|
2229
2184
|
const key = randomBytes(16).toString('base64');
|
|
@@ -2256,10 +2211,8 @@ var __webpack_modules__ = {
|
|
|
2256
2211
|
}
|
|
2257
2212
|
opts.headers['Sec-WebSocket-Protocol'] = protocols.join(',');
|
|
2258
2213
|
}
|
|
2259
|
-
if (opts.origin)
|
|
2260
|
-
|
|
2261
|
-
else opts.headers.Origin = opts.origin;
|
|
2262
|
-
}
|
|
2214
|
+
if (opts.origin) if (opts.protocolVersion < 13) opts.headers['Sec-WebSocket-Origin'] = opts.origin;
|
|
2215
|
+
else opts.headers.Origin = opts.origin;
|
|
2263
2216
|
if (parsedUrl.username || parsedUrl.password) opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;
|
|
2264
2217
|
if (isIpcUrl) {
|
|
2265
2218
|
const parts = opts.path.split(':');
|
|
@@ -2303,10 +2256,7 @@ var __webpack_modules__ = {
|
|
|
2303
2256
|
const location = res.headers.location;
|
|
2304
2257
|
const statusCode = res.statusCode;
|
|
2305
2258
|
if (location && opts.followRedirects && statusCode >= 300 && statusCode < 400) {
|
|
2306
|
-
if (++websocket._redirects > opts.maxRedirects)
|
|
2307
|
-
abortHandshake(websocket, req, 'Maximum redirects exceeded');
|
|
2308
|
-
return;
|
|
2309
|
-
}
|
|
2259
|
+
if (++websocket._redirects > opts.maxRedirects) return void abortHandshake(websocket, req, 'Maximum redirects exceeded');
|
|
2310
2260
|
req.abort();
|
|
2311
2261
|
let addr;
|
|
2312
2262
|
try {
|
|
@@ -2324,26 +2274,16 @@ var __webpack_modules__ = {
|
|
|
2324
2274
|
if (websocket.readyState !== WebSocket.CONNECTING) return;
|
|
2325
2275
|
req = websocket._req = null;
|
|
2326
2276
|
const upgrade = res.headers.upgrade;
|
|
2327
|
-
if (void 0 === upgrade || 'websocket' !== upgrade.toLowerCase())
|
|
2328
|
-
abortHandshake(websocket, socket, 'Invalid Upgrade header');
|
|
2329
|
-
return;
|
|
2330
|
-
}
|
|
2277
|
+
if (void 0 === upgrade || 'websocket' !== upgrade.toLowerCase()) return void abortHandshake(websocket, socket, 'Invalid Upgrade header');
|
|
2331
2278
|
const digest = createHash('sha1').update(key + GUID).digest('base64');
|
|
2332
|
-
if (res.headers['sec-websocket-accept'] !== digest)
|
|
2333
|
-
abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');
|
|
2334
|
-
return;
|
|
2335
|
-
}
|
|
2279
|
+
if (res.headers['sec-websocket-accept'] !== digest) return void abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');
|
|
2336
2280
|
const serverProt = res.headers['sec-websocket-protocol'];
|
|
2337
2281
|
let protError;
|
|
2338
|
-
if (void 0 !== serverProt) {
|
|
2339
|
-
if (protocolSet.
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
if (protError) {
|
|
2344
|
-
abortHandshake(websocket, socket, protError);
|
|
2345
|
-
return;
|
|
2346
|
-
}
|
|
2282
|
+
if (void 0 !== serverProt) if (protocolSet.size) {
|
|
2283
|
+
if (!protocolSet.has(serverProt)) protError = 'Server sent an invalid subprotocol';
|
|
2284
|
+
} else protError = 'Server sent a subprotocol but none was requested';
|
|
2285
|
+
else if (protocolSet.size) protError = 'Server sent no subprotocol';
|
|
2286
|
+
if (protError) return void abortHandshake(websocket, socket, protError);
|
|
2347
2287
|
if (serverProt) websocket._protocol = serverProt;
|
|
2348
2288
|
const secWebSocketExtensions = res.headers['sec-websocket-extensions'];
|
|
2349
2289
|
if (void 0 !== secWebSocketExtensions) {
|
|
@@ -2524,38 +2464,35 @@ var __webpack_modules__ = {
|
|
|
2524
2464
|
}
|
|
2525
2465
|
}
|
|
2526
2466
|
},
|
|
2527
|
-
buffer
|
|
2467
|
+
buffer (module) {
|
|
2528
2468
|
module.exports = require("buffer");
|
|
2529
2469
|
},
|
|
2530
|
-
crypto
|
|
2470
|
+
crypto (module) {
|
|
2531
2471
|
module.exports = require("crypto");
|
|
2532
2472
|
},
|
|
2533
|
-
events
|
|
2473
|
+
events (module) {
|
|
2534
2474
|
module.exports = require("events");
|
|
2535
2475
|
},
|
|
2536
|
-
http
|
|
2476
|
+
http (module) {
|
|
2537
2477
|
module.exports = require("http");
|
|
2538
2478
|
},
|
|
2539
|
-
https
|
|
2479
|
+
https (module) {
|
|
2540
2480
|
module.exports = require("https");
|
|
2541
2481
|
},
|
|
2542
|
-
net
|
|
2482
|
+
net (module) {
|
|
2543
2483
|
module.exports = require("net");
|
|
2544
2484
|
},
|
|
2545
|
-
stream
|
|
2485
|
+
stream (module) {
|
|
2546
2486
|
module.exports = require("stream");
|
|
2547
2487
|
},
|
|
2548
|
-
tls
|
|
2488
|
+
tls (module) {
|
|
2549
2489
|
module.exports = require("tls");
|
|
2550
2490
|
},
|
|
2551
|
-
url
|
|
2491
|
+
url (module) {
|
|
2552
2492
|
module.exports = require("url");
|
|
2553
2493
|
},
|
|
2554
|
-
zlib
|
|
2494
|
+
zlib (module) {
|
|
2555
2495
|
module.exports = require("zlib");
|
|
2556
|
-
},
|
|
2557
|
-
"uWebSockets.js": function(module) {
|
|
2558
|
-
module.exports = import("uWebSockets.js");
|
|
2559
2496
|
}
|
|
2560
2497
|
};
|
|
2561
2498
|
var __webpack_module_cache__ = {};
|
|
@@ -2569,12 +2506,8 @@ function __webpack_require__(moduleId) {
|
|
|
2569
2506
|
return module.exports;
|
|
2570
2507
|
}
|
|
2571
2508
|
(()=>{
|
|
2572
|
-
__webpack_require__.n =
|
|
2573
|
-
var getter = module && module.__esModule ?
|
|
2574
|
-
return module['default'];
|
|
2575
|
-
} : function() {
|
|
2576
|
-
return module;
|
|
2577
|
-
};
|
|
2509
|
+
__webpack_require__.n = (module)=>{
|
|
2510
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
2578
2511
|
__webpack_require__.d(getter, {
|
|
2579
2512
|
a: getter
|
|
2580
2513
|
});
|
|
@@ -2582,7 +2515,7 @@ function __webpack_require__(moduleId) {
|
|
|
2582
2515
|
};
|
|
2583
2516
|
})();
|
|
2584
2517
|
(()=>{
|
|
2585
|
-
__webpack_require__.d =
|
|
2518
|
+
__webpack_require__.d = (exports1, definition)=>{
|
|
2586
2519
|
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
2587
2520
|
enumerable: true,
|
|
2588
2521
|
get: definition[key]
|
|
@@ -2590,9 +2523,7 @@ function __webpack_require__(moduleId) {
|
|
|
2590
2523
|
};
|
|
2591
2524
|
})();
|
|
2592
2525
|
(()=>{
|
|
2593
|
-
__webpack_require__.o =
|
|
2594
|
-
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
2595
|
-
};
|
|
2526
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
2596
2527
|
})();
|
|
2597
2528
|
var __webpack_exports__ = {};
|
|
2598
2529
|
(()=>{
|
|
@@ -2640,19 +2571,20 @@ var __webpack_exports__ = {};
|
|
|
2640
2571
|
}
|
|
2641
2572
|
async start(service) {
|
|
2642
2573
|
let lock = this.startingLocks.get(service.name);
|
|
2643
|
-
|
|
2574
|
+
while(lock)try {
|
|
2644
2575
|
await lock;
|
|
2645
2576
|
const isRunning = await this.isRunning(service);
|
|
2646
|
-
if (
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
}
|
|
2650
|
-
return;
|
|
2577
|
+
if (isRunning) return;
|
|
2578
|
+
this.startingLocks.delete(service.name);
|
|
2579
|
+
break;
|
|
2651
2580
|
} catch {
|
|
2652
2581
|
this.startingLocks.delete(service.name);
|
|
2582
|
+
break;
|
|
2653
2583
|
}
|
|
2654
2584
|
lock = (async ()=>{
|
|
2655
2585
|
try {
|
|
2586
|
+
const alreadyRunning = await this.isRunning(service);
|
|
2587
|
+
if (alreadyRunning) return void console.log(`[${service.name}] 服务已在运行,跳过启动`);
|
|
2656
2588
|
console.log(`[${service.name}] 正在启动...`);
|
|
2657
2589
|
const result = await this.executor.execute(service.commands.start, {
|
|
2658
2590
|
cwd: service.commands.cwd,
|
|
@@ -2683,29 +2615,375 @@ var __webpack_exports__ = {};
|
|
|
2683
2615
|
service._state.status = 'offline';
|
|
2684
2616
|
}
|
|
2685
2617
|
}
|
|
2618
|
+
const external_uWebSockets_js_namespaceObject = require("uWebSockets.js");
|
|
2619
|
+
var external_uWebSockets_js_default = /*#__PURE__*/ __webpack_require__.n(external_uWebSockets_js_namespaceObject);
|
|
2686
2620
|
const external_node_net_namespaceObject = require("node:net");
|
|
2687
2621
|
var external_node_net_default = /*#__PURE__*/ __webpack_require__.n(external_node_net_namespaceObject);
|
|
2622
|
+
const external_node_url_namespaceObject = require("node:url");
|
|
2688
2623
|
const external_node_http_namespaceObject = require("node:http");
|
|
2689
2624
|
var external_node_http_default = /*#__PURE__*/ __webpack_require__.n(external_node_http_namespaceObject);
|
|
2690
|
-
const external_node_https_namespaceObject = require("node:https");
|
|
2691
|
-
var external_node_https_default = /*#__PURE__*/ __webpack_require__.n(external_node_https_namespaceObject);
|
|
2692
|
-
const external_node_url_namespaceObject = require("node:url");
|
|
2693
2625
|
__webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/stream.js");
|
|
2694
2626
|
__webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/receiver.js");
|
|
2695
2627
|
__webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/sender.js");
|
|
2696
2628
|
var websocket = __webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket.js");
|
|
2697
2629
|
__webpack_require__("./node_modules/.pnpm/ws@8.19.0/node_modules/ws/lib/websocket-server.js");
|
|
2698
2630
|
const wrapper = websocket;
|
|
2631
|
+
class AdminApiHandler {
|
|
2632
|
+
config;
|
|
2633
|
+
logger;
|
|
2634
|
+
hostnameRoutes;
|
|
2635
|
+
portRoutes;
|
|
2636
|
+
serviceManager;
|
|
2637
|
+
constructor(config, logger, hostnameRoutes, portRoutes, serviceManager){
|
|
2638
|
+
this.config = config;
|
|
2639
|
+
this.logger = logger;
|
|
2640
|
+
this.hostnameRoutes = hostnameRoutes;
|
|
2641
|
+
this.portRoutes = portRoutes;
|
|
2642
|
+
this.serviceManager = serviceManager;
|
|
2643
|
+
}
|
|
2644
|
+
isIpAllowed(ip) {
|
|
2645
|
+
if (!this.config.adminApi?.allowedIps || 0 === this.config.adminApi.allowedIps.length) return true;
|
|
2646
|
+
return this.config.adminApi.allowedIps.includes(ip);
|
|
2647
|
+
}
|
|
2648
|
+
isAuthenticated(authHeader) {
|
|
2649
|
+
if (!this.config.adminApi?.authToken) return true;
|
|
2650
|
+
if (!authHeader) return false;
|
|
2651
|
+
const token = authHeader.replace('Bearer ', '');
|
|
2652
|
+
return token === this.config.adminApi.authToken;
|
|
2653
|
+
}
|
|
2654
|
+
getServiceUptime(service) {
|
|
2655
|
+
if ('online' === service._state.status && service._state.startTime) return service._state.totalUptime + (Date.now() - service._state.startTime);
|
|
2656
|
+
return service._state.totalUptime;
|
|
2657
|
+
}
|
|
2658
|
+
findServiceMapping(serviceName) {
|
|
2659
|
+
for (const mapping of this.hostnameRoutes.values())if (mapping.service.name === serviceName) return mapping;
|
|
2660
|
+
for (const mapping of this.portRoutes.values())if (mapping.service.name === serviceName) return mapping;
|
|
2661
|
+
}
|
|
2662
|
+
handleAdminApi(res, req) {
|
|
2663
|
+
const ip = req.getHeader('x-forwarded-for')?.split(',')[0]?.trim() || req.getHeader('cf-connecting-ip') || '127.0.0.1';
|
|
2664
|
+
if (!this.isIpAllowed(ip)) {
|
|
2665
|
+
res.cork(()=>{
|
|
2666
|
+
res.writeStatus('403 Forbidden');
|
|
2667
|
+
res.end('Forbidden');
|
|
2668
|
+
});
|
|
2669
|
+
this.logger.warn({
|
|
2670
|
+
msg: `🚫 [Admin API] 拒绝访问: ${ip}`
|
|
2671
|
+
});
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
const authHeader = req.getHeader('authorization');
|
|
2675
|
+
if (!this.isAuthenticated(authHeader)) return void res.cork(()=>{
|
|
2676
|
+
res.writeStatus('401 Unauthorized');
|
|
2677
|
+
res.writeHeader('WWW-Authenticate', 'Bearer');
|
|
2678
|
+
res.end('Unauthorized');
|
|
2679
|
+
});
|
|
2680
|
+
const url = req.getUrl();
|
|
2681
|
+
const method = req.getMethod();
|
|
2682
|
+
if ('/_dynapm/api/services' === url && 'get' === method.toLowerCase()) this.getServicesList(res);
|
|
2683
|
+
else if (url.startsWith('/_dynapm/api/services/') && 'get' === method.toLowerCase()) {
|
|
2684
|
+
const serviceName = url.split('/')[4];
|
|
2685
|
+
this.getServiceDetail(res, serviceName);
|
|
2686
|
+
} else if (url.endsWith('/stop') && 'post' === method.toLowerCase()) {
|
|
2687
|
+
const parts = url.split('/');
|
|
2688
|
+
const serviceName = parts[4];
|
|
2689
|
+
this.stopService(res, serviceName);
|
|
2690
|
+
} else if (url.endsWith('/start') && 'post' === method.toLowerCase()) {
|
|
2691
|
+
const parts = url.split('/');
|
|
2692
|
+
const serviceName = parts[4];
|
|
2693
|
+
this.startService(res, serviceName);
|
|
2694
|
+
} else if ('/_dynapm/api/events' === url && 'get' === method.toLowerCase()) this.handleEventStream(res);
|
|
2695
|
+
else res.cork(()=>{
|
|
2696
|
+
res.writeStatus('404 Not Found');
|
|
2697
|
+
res.end('Not Found');
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
getServicesList(res) {
|
|
2701
|
+
const serviceMap = new Map();
|
|
2702
|
+
for (const mapping of this.hostnameRoutes.values())serviceMap.set(mapping.service.name, mapping.service);
|
|
2703
|
+
for (const mapping of this.portRoutes.values())serviceMap.set(mapping.service.name, mapping.service);
|
|
2704
|
+
const services = Array.from(serviceMap.values()).map((service)=>({
|
|
2705
|
+
name: service.name,
|
|
2706
|
+
base: service.base,
|
|
2707
|
+
status: service._state.status,
|
|
2708
|
+
uptime: this.getServiceUptime(service),
|
|
2709
|
+
lastAccessTime: service._state.lastAccessTime,
|
|
2710
|
+
activeConnections: service._state.activeConnections,
|
|
2711
|
+
idleTimeout: service.idleTimeout,
|
|
2712
|
+
proxyOnly: service.proxyOnly || false,
|
|
2713
|
+
pid: service._state.pid
|
|
2714
|
+
}));
|
|
2715
|
+
res.cork(()=>{
|
|
2716
|
+
res.writeHeader('Content-Type', 'application/json');
|
|
2717
|
+
res.end(JSON.stringify({
|
|
2718
|
+
services
|
|
2719
|
+
}));
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
getServiceDetail(res, serviceName) {
|
|
2723
|
+
const mapping = this.findServiceMapping(serviceName);
|
|
2724
|
+
this.logger.info({
|
|
2725
|
+
msg: `🔍 [Admin API] 查找服务: ${serviceName}, 找到: ${mapping?.service.name || 'null'}`
|
|
2726
|
+
});
|
|
2727
|
+
if (!mapping) return void res.cork(()=>{
|
|
2728
|
+
res.writeStatus('404 Not Found');
|
|
2729
|
+
res.end(JSON.stringify({
|
|
2730
|
+
error: 'Service not found'
|
|
2731
|
+
}));
|
|
2732
|
+
});
|
|
2733
|
+
const service = mapping.service;
|
|
2734
|
+
const detail = {
|
|
2735
|
+
name: service.name,
|
|
2736
|
+
base: service.base,
|
|
2737
|
+
status: service._state.status,
|
|
2738
|
+
uptime: this.getServiceUptime(service),
|
|
2739
|
+
lastAccessTime: service._state.lastAccessTime,
|
|
2740
|
+
activeConnections: service._state.activeConnections,
|
|
2741
|
+
idleTimeout: service.idleTimeout,
|
|
2742
|
+
startTimeout: service.startTimeout,
|
|
2743
|
+
proxyOnly: service.proxyOnly || false,
|
|
2744
|
+
pid: service._state.pid,
|
|
2745
|
+
healthCheck: service.healthCheck || {
|
|
2746
|
+
type: 'tcp'
|
|
2747
|
+
},
|
|
2748
|
+
startCount: service._state.startCount,
|
|
2749
|
+
totalUptime: service._state.totalUptime
|
|
2750
|
+
};
|
|
2751
|
+
res.cork(()=>{
|
|
2752
|
+
res.writeHeader('Content-Type', 'application/json');
|
|
2753
|
+
res.end(JSON.stringify(detail));
|
|
2754
|
+
});
|
|
2755
|
+
}
|
|
2756
|
+
async stopService(res, serviceName) {
|
|
2757
|
+
let aborted = false;
|
|
2758
|
+
res.onAborted(()=>{
|
|
2759
|
+
aborted = true;
|
|
2760
|
+
});
|
|
2761
|
+
const mapping = this.findServiceMapping(serviceName);
|
|
2762
|
+
if (!mapping) {
|
|
2763
|
+
if (!aborted) res.cork(()=>{
|
|
2764
|
+
res.writeStatus('404 Not Found');
|
|
2765
|
+
res.end(JSON.stringify({
|
|
2766
|
+
error: 'Service not found'
|
|
2767
|
+
}));
|
|
2768
|
+
});
|
|
2769
|
+
return;
|
|
2770
|
+
}
|
|
2771
|
+
const service = mapping.service;
|
|
2772
|
+
if ('online' !== service._state.status) {
|
|
2773
|
+
if (!aborted) res.cork(()=>{
|
|
2774
|
+
res.writeStatus('400 Bad Request');
|
|
2775
|
+
res.end(JSON.stringify({
|
|
2776
|
+
error: 'Service is not online'
|
|
2777
|
+
}));
|
|
2778
|
+
});
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
try {
|
|
2782
|
+
service._state.status = 'stopping';
|
|
2783
|
+
if (service._state.startTime) {
|
|
2784
|
+
service._state.totalUptime += Date.now() - service._state.startTime;
|
|
2785
|
+
service._state.startTime = void 0;
|
|
2786
|
+
}
|
|
2787
|
+
await this.serviceManager.stop(service);
|
|
2788
|
+
service._state.status = 'offline';
|
|
2789
|
+
if (!aborted) res.cork(()=>{
|
|
2790
|
+
res.writeHeader('Content-Type', 'application/json');
|
|
2791
|
+
res.end(JSON.stringify({
|
|
2792
|
+
success: true,
|
|
2793
|
+
message: `服务 ${service.name} 已停止`
|
|
2794
|
+
}));
|
|
2795
|
+
});
|
|
2796
|
+
} catch (error) {
|
|
2797
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2798
|
+
service._state.status = 'online';
|
|
2799
|
+
if (!aborted) res.cork(()=>{
|
|
2800
|
+
res.writeStatus('500 Internal Server Error');
|
|
2801
|
+
res.end(JSON.stringify({
|
|
2802
|
+
error: message
|
|
2803
|
+
}));
|
|
2804
|
+
});
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
async startService(res, serviceName) {
|
|
2808
|
+
let aborted = false;
|
|
2809
|
+
res.onAborted(()=>{
|
|
2810
|
+
aborted = true;
|
|
2811
|
+
});
|
|
2812
|
+
const mapping = this.findServiceMapping(serviceName);
|
|
2813
|
+
if (!mapping) {
|
|
2814
|
+
if (!aborted) res.cork(()=>{
|
|
2815
|
+
res.writeStatus('404 Not Found');
|
|
2816
|
+
res.end(JSON.stringify({
|
|
2817
|
+
error: 'Service not found'
|
|
2818
|
+
}));
|
|
2819
|
+
});
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
const service = mapping.service;
|
|
2823
|
+
if ('online' === service._state.status || 'starting' === service._state.status) {
|
|
2824
|
+
if (!aborted) res.cork(()=>{
|
|
2825
|
+
res.writeStatus('400 Bad Request');
|
|
2826
|
+
res.end(JSON.stringify({
|
|
2827
|
+
error: 'Service is already running or starting'
|
|
2828
|
+
}));
|
|
2829
|
+
});
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
try {
|
|
2833
|
+
service._state.status = 'starting';
|
|
2834
|
+
this.serviceManager.start(service).catch((err)=>{
|
|
2835
|
+
this.logger.error({
|
|
2836
|
+
msg: `❌ [${service.name}] 启动失败`,
|
|
2837
|
+
error: err.message
|
|
2838
|
+
});
|
|
2839
|
+
service._state.status = 'offline';
|
|
2840
|
+
});
|
|
2841
|
+
const waitStartTime = Date.now();
|
|
2842
|
+
let isReady = false;
|
|
2843
|
+
while(Date.now() - waitStartTime < service.startTimeout){
|
|
2844
|
+
isReady = await checkTcpPort(service.base);
|
|
2845
|
+
if (isReady) break;
|
|
2846
|
+
await new Promise((resolve)=>setTimeout(resolve, 100));
|
|
2847
|
+
}
|
|
2848
|
+
if (!isReady) {
|
|
2849
|
+
service._state.status = 'offline';
|
|
2850
|
+
if (!aborted) res.cork(()=>{
|
|
2851
|
+
res.writeStatus('503 Service Unavailable');
|
|
2852
|
+
res.end(JSON.stringify({
|
|
2853
|
+
error: 'Service start timeout'
|
|
2854
|
+
}));
|
|
2855
|
+
});
|
|
2856
|
+
return;
|
|
2857
|
+
}
|
|
2858
|
+
service._state.status = 'online';
|
|
2859
|
+
service._state.startTime = Date.now();
|
|
2860
|
+
service._state.startCount++;
|
|
2861
|
+
if (!aborted) res.cork(()=>{
|
|
2862
|
+
res.writeHeader('Content-Type', 'application/json');
|
|
2863
|
+
res.end(JSON.stringify({
|
|
2864
|
+
success: true,
|
|
2865
|
+
message: `服务 ${service.name} 已启动`
|
|
2866
|
+
}));
|
|
2867
|
+
});
|
|
2868
|
+
} catch (error) {
|
|
2869
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2870
|
+
service._state.status = 'offline';
|
|
2871
|
+
if (!aborted) res.cork(()=>{
|
|
2872
|
+
res.writeStatus('500 Internal Server Error');
|
|
2873
|
+
res.end(JSON.stringify({
|
|
2874
|
+
error: message
|
|
2875
|
+
}));
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
handleEventStream(res) {
|
|
2880
|
+
res.cork(()=>{
|
|
2881
|
+
res.writeStatus('200 OK');
|
|
2882
|
+
res.writeHeader('Content-Type', 'text/event-stream');
|
|
2883
|
+
res.writeHeader('Cache-Control', 'no-cache');
|
|
2884
|
+
res.writeHeader('Connection', 'keep-alive');
|
|
2885
|
+
res.writeHeader('X-Accel-Buffering', 'no');
|
|
2886
|
+
});
|
|
2887
|
+
res.cork(()=>{
|
|
2888
|
+
res.end(`event: connected\ndata: {"timestamp":${Date.now()}}\n\n`);
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
function checkTcpPort(url) {
|
|
2893
|
+
const parsed = new external_node_url_namespaceObject.URL(url);
|
|
2894
|
+
const host = parsed.hostname;
|
|
2895
|
+
const port = parseInt(parsed.port || ('https:' === parsed.protocol ? '443' : '80'));
|
|
2896
|
+
return new Promise((resolve)=>{
|
|
2897
|
+
const socket = external_node_net_default().createConnection({
|
|
2898
|
+
host,
|
|
2899
|
+
port,
|
|
2900
|
+
timeout: 100
|
|
2901
|
+
}, ()=>{
|
|
2902
|
+
socket.destroy();
|
|
2903
|
+
resolve(true);
|
|
2904
|
+
});
|
|
2905
|
+
socket.on('error', ()=>{
|
|
2906
|
+
socket.destroy();
|
|
2907
|
+
resolve(false);
|
|
2908
|
+
});
|
|
2909
|
+
socket.on('timeout', ()=>{
|
|
2910
|
+
socket.destroy();
|
|
2911
|
+
resolve(false);
|
|
2912
|
+
});
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2699
2915
|
function formatTime(ms) {
|
|
2700
2916
|
if (ms < 1000) return `${ms}ms`;
|
|
2701
2917
|
return `${(ms / 1000).toFixed(2)}s`;
|
|
2702
2918
|
}
|
|
2919
|
+
function collectRequestBody(res) {
|
|
2920
|
+
return new Promise((resolve)=>{
|
|
2921
|
+
const chunks = [];
|
|
2922
|
+
let totalSize = 0;
|
|
2923
|
+
let resolved = false;
|
|
2924
|
+
res.onData((ab, isLast)=>{
|
|
2925
|
+
if (resolved) return;
|
|
2926
|
+
const chunk = Buffer.alloc(ab.byteLength);
|
|
2927
|
+
Buffer.from(ab).copy(chunk);
|
|
2928
|
+
totalSize += chunk.length;
|
|
2929
|
+
if (totalSize > GatewayConstants.MAX_REQUEST_BODY_SIZE) {
|
|
2930
|
+
resolved = true;
|
|
2931
|
+
resolve(Buffer.concat(chunks));
|
|
2932
|
+
return;
|
|
2933
|
+
}
|
|
2934
|
+
chunks.push(chunk);
|
|
2935
|
+
if (isLast) {
|
|
2936
|
+
resolved = true;
|
|
2937
|
+
resolve(Buffer.concat(chunks));
|
|
2938
|
+
}
|
|
2939
|
+
});
|
|
2940
|
+
res.onAborted(()=>{
|
|
2941
|
+
if (resolved) return;
|
|
2942
|
+
resolved = true;
|
|
2943
|
+
resolve(Buffer.concat(chunks));
|
|
2944
|
+
});
|
|
2945
|
+
});
|
|
2946
|
+
}
|
|
2947
|
+
function getStatusMessage(statusCode) {
|
|
2948
|
+
const messages = {
|
|
2949
|
+
200: 'OK',
|
|
2950
|
+
201: 'Created',
|
|
2951
|
+
204: 'No Content',
|
|
2952
|
+
301: 'Moved Permanently',
|
|
2953
|
+
302: 'Found',
|
|
2954
|
+
304: 'Not Modified',
|
|
2955
|
+
400: 'Bad Request',
|
|
2956
|
+
401: 'Unauthorized',
|
|
2957
|
+
403: 'Forbidden',
|
|
2958
|
+
404: 'Not Found',
|
|
2959
|
+
405: 'Method Not Allowed',
|
|
2960
|
+
409: 'Conflict',
|
|
2961
|
+
500: 'Internal Server Error',
|
|
2962
|
+
502: 'Bad Gateway',
|
|
2963
|
+
503: 'Service Unavailable'
|
|
2964
|
+
};
|
|
2965
|
+
return messages[statusCode] || 'Unknown';
|
|
2966
|
+
}
|
|
2703
2967
|
const GatewayConstants = {
|
|
2704
2968
|
IDLE_CHECK_INTERVAL: 3000,
|
|
2705
2969
|
TCP_CHECK_TIMEOUT: 100,
|
|
2706
|
-
BACKEND_READY_CHECK_DELAY: 50
|
|
2970
|
+
BACKEND_READY_CHECK_DELAY: 50,
|
|
2971
|
+
SKIP_RESPONSE_HEADERS: new Set([
|
|
2972
|
+
'connection',
|
|
2973
|
+
'transfer-encoding',
|
|
2974
|
+
'keep-alive',
|
|
2975
|
+
'content-length'
|
|
2976
|
+
]),
|
|
2977
|
+
CRLF_REGEX: /[\r\n]/g,
|
|
2978
|
+
SKIP_REQUEST_HEADERS: new Set([
|
|
2979
|
+
'connection',
|
|
2980
|
+
'keep-alive',
|
|
2981
|
+
'content-length'
|
|
2982
|
+
]),
|
|
2983
|
+
MAX_REQUEST_BODY_SIZE: 10485760,
|
|
2984
|
+
MAX_WS_MESSAGE_QUEUE_SIZE: 1000
|
|
2707
2985
|
};
|
|
2708
|
-
function
|
|
2986
|
+
function gateway_checkTcpPort(url) {
|
|
2709
2987
|
const parsed = new external_node_url_namespaceObject.URL(url);
|
|
2710
2988
|
const host = parsed.hostname;
|
|
2711
2989
|
const port = parseInt(parsed.port || ('https:' === parsed.protocol ? '443' : '80'));
|
|
@@ -2730,395 +3008,652 @@ var __webpack_exports__ = {};
|
|
|
2730
3008
|
}
|
|
2731
3009
|
class Gateway {
|
|
2732
3010
|
config;
|
|
2733
|
-
serviceManager;
|
|
2734
|
-
|
|
3011
|
+
serviceManager = new ServiceManager();
|
|
3012
|
+
hostnameRoutes = new Map();
|
|
3013
|
+
portRoutes = new Map();
|
|
2735
3014
|
logger;
|
|
3015
|
+
logging;
|
|
3016
|
+
adminApi;
|
|
3017
|
+
startingPromises = new Map();
|
|
2736
3018
|
constructor(config, logger){
|
|
2737
3019
|
this.config = config;
|
|
2738
|
-
this.serviceManager = new ServiceManager();
|
|
2739
|
-
this.services = new Map();
|
|
2740
3020
|
this.logger = logger;
|
|
3021
|
+
this.logging = {
|
|
3022
|
+
enableRequestLog: config.logging?.enableRequestLog ?? false,
|
|
3023
|
+
enableWebSocketLog: config.logging?.enableWebSocketLog ?? false,
|
|
3024
|
+
enablePerformanceLog: config.logging?.enablePerformanceLog ?? false
|
|
3025
|
+
};
|
|
3026
|
+
this.adminApi = new AdminApiHandler(config, logger, this.hostnameRoutes, this.portRoutes, this.serviceManager);
|
|
2741
3027
|
this.initServices();
|
|
2742
3028
|
this.initIdleChecker();
|
|
2743
3029
|
}
|
|
2744
3030
|
initServices() {
|
|
2745
|
-
|
|
3031
|
+
if (!this.config.services) return;
|
|
3032
|
+
console.log('[DynaPM] 初始化服务...');
|
|
3033
|
+
console.log('[DynaPM] 服务数量:', Object.keys(this.config.services).length);
|
|
3034
|
+
for (const service of Object.values(this.config.services)){
|
|
2746
3035
|
service._state = {
|
|
2747
3036
|
status: service.proxyOnly ? 'online' : 'offline',
|
|
2748
3037
|
lastAccessTime: Date.now(),
|
|
2749
|
-
activeConnections: 0
|
|
3038
|
+
activeConnections: 0,
|
|
3039
|
+
startCount: 0,
|
|
3040
|
+
totalUptime: 0
|
|
2750
3041
|
};
|
|
2751
|
-
|
|
3042
|
+
const routes = service.routes || [];
|
|
3043
|
+
if (0 === routes.length) {
|
|
3044
|
+
console.warn(`[DynaPM] ⚠️ [${service.name}] 没有配置路由`);
|
|
3045
|
+
continue;
|
|
3046
|
+
}
|
|
3047
|
+
console.log(`[DynaPM] ✅ [${service.name}] 配置了 ${routes.length} 个路由:`);
|
|
3048
|
+
for (const route of routes){
|
|
3049
|
+
const targetUrl = new external_node_url_namespaceObject.URL(route.target);
|
|
3050
|
+
const mapping = {
|
|
3051
|
+
service,
|
|
3052
|
+
target: route.target,
|
|
3053
|
+
targetUrl
|
|
3054
|
+
};
|
|
3055
|
+
if ('host' === route.type) {
|
|
3056
|
+
const hostname = route.value;
|
|
3057
|
+
this.hostnameRoutes.set(hostname, mapping);
|
|
3058
|
+
console.log(`[DynaPM] └─ hostname: ${hostname} -> ${route.target}`);
|
|
3059
|
+
} else if ('port' === route.type) {
|
|
3060
|
+
const port = route.value;
|
|
3061
|
+
this.portRoutes.set(port, mapping);
|
|
3062
|
+
console.log(`[DynaPM] └─ port: ${port} -> ${route.target}`);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
2752
3065
|
}
|
|
3066
|
+
const hostnameCount = this.hostnameRoutes.size;
|
|
3067
|
+
const portCount = this.portRoutes.size;
|
|
3068
|
+
console.log(`[DynaPM] 📊 共配置 ${hostnameCount} 个 hostname 映射, ${portCount} 个端口绑定`);
|
|
3069
|
+
this.logger.info({
|
|
3070
|
+
msg: `📊 共配置 ${hostnameCount} 个 hostname 映射, ${portCount} 个端口绑定`
|
|
3071
|
+
});
|
|
2753
3072
|
}
|
|
2754
3073
|
initIdleChecker() {
|
|
2755
3074
|
setInterval(()=>{
|
|
2756
3075
|
const now = Date.now();
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
error: err.message
|
|
2766
|
-
});
|
|
2767
|
-
});
|
|
2768
|
-
service._state.status = 'offline';
|
|
2769
|
-
}
|
|
3076
|
+
const checkedServices = new Set();
|
|
3077
|
+
for (const mapping of this.hostnameRoutes.values())if (!checkedServices.has(mapping.service)) {
|
|
3078
|
+
checkedServices.add(mapping.service);
|
|
3079
|
+
this.checkIdleService(mapping.service, now);
|
|
3080
|
+
}
|
|
3081
|
+
for (const mapping of this.portRoutes.values())if (!checkedServices.has(mapping.service)) {
|
|
3082
|
+
checkedServices.add(mapping.service);
|
|
3083
|
+
this.checkIdleService(mapping.service, now);
|
|
2770
3084
|
}
|
|
2771
3085
|
}, GatewayConstants.IDLE_CHECK_INTERVAL);
|
|
2772
3086
|
}
|
|
2773
|
-
|
|
3087
|
+
checkIdleService(service, now) {
|
|
3088
|
+
if (service.proxyOnly) return;
|
|
3089
|
+
if ('online' === service._state.status && 0 === service._state.activeConnections && now - service._state.lastAccessTime > service.idleTimeout) {
|
|
3090
|
+
this.logger.info({
|
|
3091
|
+
msg: `🛌 [${service.name}] 闲置超时,正在停止...`
|
|
3092
|
+
});
|
|
3093
|
+
service._state.status = 'stopping';
|
|
3094
|
+
if (service._state.startTime) {
|
|
3095
|
+
service._state.totalUptime += now - service._state.startTime;
|
|
3096
|
+
service._state.startTime = void 0;
|
|
3097
|
+
}
|
|
3098
|
+
this.serviceManager.stop(service).catch((err)=>{
|
|
3099
|
+
this.logger.error({
|
|
3100
|
+
msg: `❌ [${service.name}] 停止失败`,
|
|
3101
|
+
error: err.message
|
|
3102
|
+
});
|
|
3103
|
+
}).finally(()=>{
|
|
3104
|
+
service._state.status = 'offline';
|
|
3105
|
+
});
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
handlePortBindingRequest(res, req, mapping) {
|
|
3109
|
+
const service = mapping.service;
|
|
2774
3110
|
const startTime = Date.now();
|
|
2775
|
-
const hostname = req.getHeader('host')?.split(':')[0] || '';
|
|
2776
3111
|
const method = req.getMethod();
|
|
2777
3112
|
const url = req.getUrl();
|
|
2778
3113
|
const queryString = req.getQuery();
|
|
2779
3114
|
const fullUrl = queryString ? `${url}?${queryString}` : url;
|
|
2780
3115
|
const headers = {};
|
|
2781
3116
|
req.forEach((key, value)=>{
|
|
2782
|
-
const safeValue = value.replace(
|
|
3117
|
+
const safeValue = value.replace(GatewayConstants.CRLF_REGEX, '');
|
|
2783
3118
|
headers[key] = safeValue;
|
|
2784
3119
|
});
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
res.writeStatus('404 Not Found');
|
|
2792
|
-
res.end(`Service not found: ${hostname}`);
|
|
2793
|
-
});
|
|
3120
|
+
service._state.lastAccessTime = Date.now();
|
|
3121
|
+
const status = service._state.status;
|
|
3122
|
+
if ('starting' === status) {
|
|
3123
|
+
const startPromise = this.startingPromises.get(service.name);
|
|
3124
|
+
if (startPromise) this.handleServiceWithStartPromise(res, mapping, fullUrl, startTime, method, headers, startPromise);
|
|
3125
|
+
else this.handleDirectProxy(res, mapping, fullUrl, startTime, method, headers);
|
|
2794
3126
|
return;
|
|
2795
3127
|
}
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
else this.handleDirectProxy(res,
|
|
3128
|
+
const needsStart = 'offline' === status || 'stopping' === status;
|
|
3129
|
+
if (needsStart) if ('stopping' === status) this.handleServiceWithWait(res, mapping, fullUrl, startTime, method, headers);
|
|
3130
|
+
else this.handleServiceStart(res, mapping, fullUrl, startTime, method, headers);
|
|
3131
|
+
else this.handleDirectProxy(res, mapping, fullUrl, startTime, method, headers);
|
|
2800
3132
|
}
|
|
2801
|
-
|
|
2802
|
-
const
|
|
3133
|
+
handleRequest(res, req) {
|
|
3134
|
+
const startTime = Date.now();
|
|
3135
|
+
const hostHeader = req.getHeader('host');
|
|
3136
|
+
let hostname = '';
|
|
3137
|
+
if (hostHeader) {
|
|
3138
|
+
const colonIndex = hostHeader.indexOf(':');
|
|
3139
|
+
hostname = -1 !== colonIndex ? hostHeader.substring(0, colonIndex) : hostHeader;
|
|
3140
|
+
}
|
|
3141
|
+
const method = req.getMethod();
|
|
3142
|
+
const url = req.getUrl();
|
|
3143
|
+
const queryString = req.getQuery();
|
|
3144
|
+
const fullUrl = queryString ? url + '?' + queryString : url;
|
|
3145
|
+
const headers = {};
|
|
3146
|
+
req.forEach((key, value)=>{
|
|
3147
|
+
headers[key] = value.replace(GatewayConstants.CRLF_REGEX, '');
|
|
3148
|
+
});
|
|
3149
|
+
const mapping = this.hostnameRoutes.get(hostname);
|
|
3150
|
+
if (!mapping) {
|
|
3151
|
+
this.logger.info({
|
|
3152
|
+
msg: `❌ [${hostname}] ${method} ${fullUrl} - 404`
|
|
3153
|
+
});
|
|
3154
|
+
res.cork(()=>{
|
|
3155
|
+
res.writeStatus('404 Not Found');
|
|
3156
|
+
res.end(`Service not found: ${hostname}`);
|
|
3157
|
+
});
|
|
3158
|
+
return;
|
|
3159
|
+
}
|
|
3160
|
+
const service = mapping.service;
|
|
3161
|
+
service._state.lastAccessTime = Date.now();
|
|
3162
|
+
const status = service._state.status;
|
|
3163
|
+
if ('starting' === status) {
|
|
3164
|
+
const startPromise = this.startingPromises.get(service.name);
|
|
3165
|
+
if (startPromise) this.handleServiceWithStartPromise(res, mapping, fullUrl, startTime, method, headers, startPromise);
|
|
3166
|
+
else this.handleDirectProxy(res, mapping, fullUrl, startTime, method, headers);
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
const needsStart = 'offline' === status || 'stopping' === status;
|
|
3170
|
+
if (needsStart) if ('stopping' === status) this.handleServiceWithWait(res, mapping, fullUrl, startTime, method, headers);
|
|
3171
|
+
else this.handleServiceStart(res, mapping, fullUrl, startTime, method, headers);
|
|
3172
|
+
else this.handleDirectProxy(res, mapping, fullUrl, startTime, method, headers);
|
|
3173
|
+
}
|
|
3174
|
+
async startServiceAndProxy(res, mapping, fullUrl, startTime, method, headers, body) {
|
|
3175
|
+
const service = mapping.service;
|
|
3176
|
+
const target = mapping.target;
|
|
2803
3177
|
this.logger.info({
|
|
2804
3178
|
msg: `🚀 [${service.name}] ${method} ${fullUrl} - 启动服务...`
|
|
2805
3179
|
});
|
|
2806
3180
|
service._state.status = 'starting';
|
|
2807
|
-
|
|
2808
|
-
let
|
|
2809
|
-
|
|
2810
|
-
|
|
3181
|
+
let resolveStartPromise;
|
|
3182
|
+
let rejectStartPromise;
|
|
3183
|
+
const startPromise = new Promise((resolve, reject)=>{
|
|
3184
|
+
resolveStartPromise = resolve;
|
|
3185
|
+
rejectStartPromise = reject;
|
|
2811
3186
|
});
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
const waitDuration = Date.now() - waitStartTime;
|
|
2827
|
-
const totalDuration = Date.now() - startStartTime;
|
|
2828
|
-
this.logger.info({
|
|
2829
|
-
msg: `✅ [${service.name}] 服务就绪 (启动${formatTime(totalDuration - waitDuration)}, 等待${formatTime(waitDuration)})`
|
|
2830
|
-
});
|
|
2831
|
-
break;
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
if (!isReady) {
|
|
2835
|
-
service._state.status = 'offline';
|
|
2836
|
-
throw new Error(`服务启动超时: 端口 ${service.base} 不可用`);
|
|
2837
|
-
}
|
|
2838
|
-
service._state.status = 'online';
|
|
2839
|
-
if (aborted) return;
|
|
2840
|
-
await this.forwardProxyRequest(res, service, fullUrl, startTime, method, headers, fullBody);
|
|
2841
|
-
} catch (error) {
|
|
2842
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2843
|
-
if ('Client aborted' === message) return;
|
|
2844
|
-
this.logger.error({
|
|
2845
|
-
msg: `❌ [${service.name}] 启动失败`,
|
|
2846
|
-
error: message
|
|
2847
|
-
});
|
|
2848
|
-
if (!aborted) try {
|
|
2849
|
-
res.cork(()=>{
|
|
2850
|
-
res.writeStatus('503 Service Unavailable');
|
|
2851
|
-
res.end('Service Unavailable');
|
|
2852
|
-
});
|
|
2853
|
-
} catch (sendErr) {
|
|
2854
|
-
const sendErrMsg = sendErr instanceof Error ? sendErr.message : String(sendErr);
|
|
2855
|
-
this.logger.error({
|
|
2856
|
-
msg: `❌ [${service.name}] 发送错误响应失败`,
|
|
2857
|
-
error: sendErrMsg
|
|
2858
|
-
});
|
|
2859
|
-
}
|
|
2860
|
-
}
|
|
2861
|
-
})();
|
|
3187
|
+
this.startingPromises.set(service.name, startPromise);
|
|
3188
|
+
try {
|
|
3189
|
+
await this.serviceManager.start(service);
|
|
3190
|
+
const waitStartTime = Date.now();
|
|
3191
|
+
let isReady = false;
|
|
3192
|
+
while(Date.now() - waitStartTime < service.startTimeout){
|
|
3193
|
+
isReady = await gateway_checkTcpPort(target);
|
|
3194
|
+
if (isReady) {
|
|
3195
|
+
const waitDuration = Date.now() - waitStartTime;
|
|
3196
|
+
this.logger.info({
|
|
3197
|
+
msg: `✅ [${service.name}] 服务就绪 (等待${formatTime(waitDuration)})`
|
|
3198
|
+
});
|
|
3199
|
+
break;
|
|
3200
|
+
}
|
|
2862
3201
|
}
|
|
2863
|
-
|
|
3202
|
+
if (!isReady) {
|
|
3203
|
+
service._state.status = 'offline';
|
|
3204
|
+
throw new Error(`服务启动超时: 端口 ${target} 不可用`);
|
|
3205
|
+
}
|
|
3206
|
+
service._state.status = 'online';
|
|
3207
|
+
service._state.startTime = Date.now();
|
|
3208
|
+
service._state.startCount++;
|
|
3209
|
+
resolveStartPromise();
|
|
3210
|
+
this.startingPromises.delete(service.name);
|
|
3211
|
+
await this.forwardProxyRequest(res, mapping, fullUrl, startTime, method, headers, body);
|
|
3212
|
+
} catch (error) {
|
|
3213
|
+
this.startingPromises.delete(service.name);
|
|
3214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3215
|
+
if ('Client aborted' === message) return void resolveStartPromise();
|
|
3216
|
+
rejectStartPromise(error instanceof Error ? error : new Error(message));
|
|
3217
|
+
this.logger.error({
|
|
3218
|
+
msg: `❌ [${service.name}] 启动失败`,
|
|
3219
|
+
error: message
|
|
3220
|
+
});
|
|
3221
|
+
try {
|
|
3222
|
+
res.cork(()=>{
|
|
3223
|
+
res.writeStatus('503 Service Unavailable');
|
|
3224
|
+
res.end('Service Unavailable');
|
|
3225
|
+
});
|
|
3226
|
+
} catch {}
|
|
3227
|
+
}
|
|
2864
3228
|
}
|
|
2865
|
-
|
|
2866
|
-
const
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
aborted = true;
|
|
3229
|
+
handleServiceWithWait(res, mapping, fullUrl, startTime, method, headers) {
|
|
3230
|
+
const service = mapping.service;
|
|
3231
|
+
this.logger.info({
|
|
3232
|
+
msg: `⏳ [${service.name}] ${method} ${fullUrl} - 等待服务停止完成...`
|
|
2870
3233
|
});
|
|
2871
|
-
res
|
|
2872
|
-
|
|
2873
|
-
const
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
if (
|
|
2878
|
-
this.forwardProxyRequest(res, service, fullUrl, startTime, method, headers, fullBody).catch((err)=>{
|
|
2879
|
-
if ('Client aborted' === err.message) return;
|
|
3234
|
+
const bodyPromise = collectRequestBody(res);
|
|
3235
|
+
(async ()=>{
|
|
3236
|
+
const maxWaitTime = 30000;
|
|
3237
|
+
const checkInterval = 100;
|
|
3238
|
+
const waitStartTime = Date.now();
|
|
3239
|
+
while('stopping' === service._state.status){
|
|
3240
|
+
if (Date.now() - waitStartTime > maxWaitTime) {
|
|
2880
3241
|
this.logger.error({
|
|
2881
|
-
msg: `❌ [${service.name}]
|
|
2882
|
-
error: err.message
|
|
3242
|
+
msg: `❌ [${service.name}] 等待服务停止超时`
|
|
2883
3243
|
});
|
|
2884
|
-
|
|
3244
|
+
try {
|
|
2885
3245
|
res.cork(()=>{
|
|
2886
|
-
res.writeStatus('
|
|
2887
|
-
res.end('
|
|
2888
|
-
});
|
|
2889
|
-
} catch (sendErr) {
|
|
2890
|
-
const sendErrMsg = sendErr instanceof Error ? sendErr.message : String(sendErr);
|
|
2891
|
-
this.logger.error({
|
|
2892
|
-
msg: `❌ [${service.name}] 发送错误响应失败`,
|
|
2893
|
-
error: sendErrMsg
|
|
3246
|
+
res.writeStatus('503 Service Unavailable');
|
|
3247
|
+
res.end('Service stopping timeout');
|
|
2894
3248
|
});
|
|
2895
|
-
}
|
|
2896
|
-
|
|
3249
|
+
} catch {}
|
|
3250
|
+
return;
|
|
3251
|
+
}
|
|
3252
|
+
await new Promise((resolve)=>setTimeout(resolve, checkInterval));
|
|
2897
3253
|
}
|
|
2898
|
-
|
|
3254
|
+
this.logger.info({
|
|
3255
|
+
msg: `✅ [${service.name}] 服务已停止,开始启动...`
|
|
3256
|
+
});
|
|
3257
|
+
const body = await bodyPromise;
|
|
3258
|
+
await this.startServiceAndProxy(res, mapping, fullUrl, startTime, method, headers, body);
|
|
3259
|
+
})();
|
|
2899
3260
|
}
|
|
2900
|
-
|
|
2901
|
-
const
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
3261
|
+
handleServiceWithStartPromise(res, mapping, fullUrl, startTime, method, headers, startPromise) {
|
|
3262
|
+
const bodyPromise = collectRequestBody(res);
|
|
3263
|
+
(async ()=>{
|
|
3264
|
+
try {
|
|
3265
|
+
await startPromise;
|
|
3266
|
+
} catch {
|
|
3267
|
+
await bodyPromise;
|
|
3268
|
+
try {
|
|
3269
|
+
res.cork(()=>{
|
|
3270
|
+
res.writeStatus('503 Service Unavailable');
|
|
3271
|
+
res.end('Service start failed');
|
|
3272
|
+
});
|
|
3273
|
+
} catch {}
|
|
3274
|
+
return;
|
|
3275
|
+
}
|
|
3276
|
+
const body = await bodyPromise;
|
|
3277
|
+
await this.forwardProxyRequest(res, mapping, fullUrl, startTime, method, headers, body);
|
|
3278
|
+
})();
|
|
3279
|
+
}
|
|
3280
|
+
handleServiceStart(res, mapping, fullUrl, startTime, method, headers) {
|
|
3281
|
+
const bodyPromise = collectRequestBody(res);
|
|
3282
|
+
(async ()=>{
|
|
3283
|
+
const body = await bodyPromise;
|
|
3284
|
+
await this.startServiceAndProxy(res, mapping, fullUrl, startTime, method, headers, body);
|
|
3285
|
+
})();
|
|
3286
|
+
}
|
|
3287
|
+
handleDirectProxy(res, mapping, fullUrl, startTime, method, headers) {
|
|
3288
|
+
const service = mapping.service;
|
|
3289
|
+
const targetUrl = mapping.targetUrl;
|
|
3290
|
+
const proxyHeaders = {};
|
|
3291
|
+
for(const key in headers)if (!GatewayConstants.SKIP_REQUEST_HEADERS.has(key.toLowerCase())) proxyHeaders[key] = headers[key];
|
|
2909
3292
|
proxyHeaders['host'] = targetUrl.host;
|
|
2910
3293
|
const state = {
|
|
2911
3294
|
aborted: false,
|
|
2912
3295
|
responded: false
|
|
2913
3296
|
};
|
|
2914
3297
|
service._state.activeConnections++;
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
3298
|
+
const cleanup = ()=>{
|
|
3299
|
+
service._state.activeConnections--;
|
|
3300
|
+
};
|
|
3301
|
+
res.onAborted(()=>{
|
|
3302
|
+
state.aborted = true;
|
|
3303
|
+
cleanup();
|
|
3304
|
+
});
|
|
3305
|
+
const proxyReq = external_node_http_default().request({
|
|
3306
|
+
hostname: targetUrl.hostname,
|
|
3307
|
+
port: parseInt(targetUrl.port || ('https:' === targetUrl.protocol ? '443' : '80')),
|
|
3308
|
+
path: fullUrl,
|
|
3309
|
+
method: method.toUpperCase(),
|
|
3310
|
+
headers: proxyHeaders,
|
|
3311
|
+
timeout: 30000
|
|
3312
|
+
}, (proxyRes)=>{
|
|
3313
|
+
if (state.aborted) {
|
|
3314
|
+
proxyRes.destroy();
|
|
2923
3315
|
cleanup();
|
|
2924
|
-
|
|
2925
|
-
}
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
const statusCode = proxyRes.statusCode || 200;
|
|
3319
|
+
const statusMessage = getStatusMessage(statusCode);
|
|
3320
|
+
if (101 === statusCode) {
|
|
3321
|
+
res.cork(()=>{
|
|
3322
|
+
if (state.aborted) return;
|
|
3323
|
+
res.writeStatus(`${statusCode} ${statusMessage}`);
|
|
3324
|
+
for (const [key, value] of Object.entries(proxyRes.headers))if (!GatewayConstants.SKIP_RESPONSE_HEADERS.has(key.toLowerCase())) {
|
|
3325
|
+
if (value) res.writeHeader(key, Array.isArray(value) ? value.join(', ') : value);
|
|
3326
|
+
}
|
|
3327
|
+
res.end();
|
|
3328
|
+
state.responded = true;
|
|
3329
|
+
});
|
|
3330
|
+
cleanup();
|
|
3331
|
+
return;
|
|
3332
|
+
}
|
|
3333
|
+
res.cork(()=>{
|
|
3334
|
+
if (state.aborted) return;
|
|
3335
|
+
res.writeStatus(`${statusCode} ${statusMessage}`);
|
|
3336
|
+
for (const [key, value] of Object.entries(proxyRes.headers))if (!GatewayConstants.SKIP_RESPONSE_HEADERS.has(key.toLowerCase())) {
|
|
3337
|
+
if (value) res.writeHeader(key, Array.isArray(value) ? value.join(', ') : value);
|
|
2939
3338
|
}
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
3339
|
+
});
|
|
3340
|
+
proxyRes.on('data', (chunk)=>{
|
|
3341
|
+
if (state.aborted) return void proxyRes.destroy();
|
|
3342
|
+
let writeSuccess = false;
|
|
3343
|
+
res.cork(()=>{
|
|
3344
|
+
if (state.aborted) return;
|
|
3345
|
+
writeSuccess = res.write(chunk);
|
|
3346
|
+
});
|
|
3347
|
+
if (!writeSuccess) {
|
|
3348
|
+
proxyRes.pause();
|
|
3349
|
+
res.onWritable(()=>{
|
|
3350
|
+
if (state.aborted) {
|
|
3351
|
+
proxyRes.destroy();
|
|
3352
|
+
return false;
|
|
2954
3353
|
}
|
|
2955
|
-
|
|
2956
|
-
|
|
3354
|
+
proxyRes.resume();
|
|
3355
|
+
return true;
|
|
2957
3356
|
});
|
|
2958
|
-
cleanup();
|
|
2959
|
-
resolve();
|
|
2960
|
-
return;
|
|
2961
3357
|
}
|
|
3358
|
+
});
|
|
3359
|
+
proxyRes.on('end', ()=>{
|
|
3360
|
+
if (state.aborted) return void cleanup();
|
|
2962
3361
|
res.cork(()=>{
|
|
2963
3362
|
if (state.aborted) return;
|
|
2964
|
-
res.
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
const
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
3363
|
+
res.end();
|
|
3364
|
+
state.responded = true;
|
|
3365
|
+
if (this.logging.enableRequestLog) {
|
|
3366
|
+
const responseTime = Date.now() - startTime;
|
|
3367
|
+
this.logger.info({
|
|
3368
|
+
msg: `📤 [${service.name}] ${method} ${fullUrl} - ${statusCode} - ${formatTime(responseTime)}`,
|
|
3369
|
+
service: service.name,
|
|
3370
|
+
method,
|
|
3371
|
+
path: fullUrl,
|
|
3372
|
+
statusCode,
|
|
3373
|
+
responseTime
|
|
3374
|
+
});
|
|
2972
3375
|
}
|
|
2973
3376
|
});
|
|
2974
|
-
|
|
3377
|
+
cleanup();
|
|
3378
|
+
});
|
|
3379
|
+
proxyRes.on('error', (err)=>{
|
|
3380
|
+
if (state.aborted) return void cleanup();
|
|
3381
|
+
this.logger.error({
|
|
3382
|
+
msg: `❌ [${service.name}] 代理响应错误`,
|
|
3383
|
+
error: err.message
|
|
3384
|
+
});
|
|
3385
|
+
if (!state.responded) {
|
|
3386
|
+
state.responded = true;
|
|
3387
|
+
res.cork(()=>{
|
|
3388
|
+
res.writeStatus('502 Bad Gateway');
|
|
3389
|
+
res.end('Bad Gateway');
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
cleanup();
|
|
3393
|
+
});
|
|
3394
|
+
});
|
|
3395
|
+
proxyReq.on('error', (err)=>{
|
|
3396
|
+
if (state.aborted) return void cleanup();
|
|
3397
|
+
this.logger.error({
|
|
3398
|
+
msg: `❌ [${service.name}] 代理请求错误`,
|
|
3399
|
+
error: err.message
|
|
3400
|
+
});
|
|
3401
|
+
if (!state.responded) {
|
|
3402
|
+
state.responded = true;
|
|
3403
|
+
res.cork(()=>{
|
|
3404
|
+
res.writeStatus('502 Bad Gateway');
|
|
3405
|
+
res.end('Bad Gateway');
|
|
3406
|
+
});
|
|
3407
|
+
}
|
|
3408
|
+
if (!service.proxyOnly && 'online' === service._state.status) {
|
|
3409
|
+
this.logger.info({
|
|
3410
|
+
msg: `🔄 [${service.name}] 后端不可达,重置状态为 offline`
|
|
3411
|
+
});
|
|
3412
|
+
service._state.status = 'offline';
|
|
3413
|
+
}
|
|
3414
|
+
cleanup();
|
|
3415
|
+
});
|
|
3416
|
+
res.onData((ab, isLast)=>{
|
|
3417
|
+
if (state.aborted) return void proxyReq.destroy();
|
|
3418
|
+
const chunk = Buffer.from(ab);
|
|
3419
|
+
if (isLast) proxyReq.end(chunk);
|
|
3420
|
+
else proxyReq.write(chunk);
|
|
3421
|
+
});
|
|
3422
|
+
}
|
|
3423
|
+
async forwardProxyRequest(res, mapping, path, startTime, method, headers, body) {
|
|
3424
|
+
const service = mapping.service;
|
|
3425
|
+
const perfLog = this.logging.enablePerformanceLog;
|
|
3426
|
+
const perfPrepStart = perfLog ? performance.now() : 0;
|
|
3427
|
+
const targetUrl = mapping.targetUrl;
|
|
3428
|
+
const proxyHeaders = {};
|
|
3429
|
+
for(const key in headers){
|
|
3430
|
+
const keyLower = key.toLowerCase();
|
|
3431
|
+
if ('connection' !== keyLower && 'keep-alive' !== keyLower && 'content-length' !== keyLower && 'transfer-encoding' !== keyLower) proxyHeaders[key] = headers[key];
|
|
3432
|
+
}
|
|
3433
|
+
proxyHeaders['host'] = targetUrl.host;
|
|
3434
|
+
if (body.length > 0) proxyHeaders['content-length'] = String(body.length);
|
|
3435
|
+
const perfPrepTime = perfLog ? performance.now() - perfPrepStart : 0;
|
|
3436
|
+
const state = {
|
|
3437
|
+
aborted: false,
|
|
3438
|
+
responded: false
|
|
3439
|
+
};
|
|
3440
|
+
service._state.activeConnections++;
|
|
3441
|
+
const cleanup = ()=>{
|
|
3442
|
+
service._state.activeConnections--;
|
|
3443
|
+
};
|
|
3444
|
+
res.onAborted(()=>{
|
|
3445
|
+
state.aborted = true;
|
|
3446
|
+
cleanup();
|
|
3447
|
+
});
|
|
3448
|
+
const perfHttpStart = perfLog ? performance.now() : 0;
|
|
3449
|
+
let perfTtfb = 0;
|
|
3450
|
+
let perfStreamStart = 0;
|
|
3451
|
+
let chunkCount = 0;
|
|
3452
|
+
let totalBytes = 0;
|
|
3453
|
+
return new Promise((resolve, reject)=>{
|
|
3454
|
+
let proxyReq;
|
|
3455
|
+
try {
|
|
3456
|
+
proxyReq = external_node_http_default().request({
|
|
3457
|
+
hostname: targetUrl.hostname,
|
|
3458
|
+
port: parseInt(targetUrl.port || ('https:' === targetUrl.protocol ? '443' : '80')),
|
|
3459
|
+
path,
|
|
3460
|
+
method: method.toUpperCase(),
|
|
3461
|
+
headers: proxyHeaders,
|
|
3462
|
+
timeout: 30000
|
|
3463
|
+
}, (proxyRes)=>{
|
|
3464
|
+
if (perfLog && 0 === perfTtfb) {
|
|
3465
|
+
perfTtfb = performance.now() - perfHttpStart;
|
|
3466
|
+
perfStreamStart = performance.now();
|
|
3467
|
+
}
|
|
3468
|
+
const statusCode = proxyRes.statusCode || 200;
|
|
3469
|
+
const statusMessage = getStatusMessage(statusCode);
|
|
2975
3470
|
if (state.aborted) {
|
|
2976
3471
|
proxyRes.destroy();
|
|
3472
|
+
cleanup();
|
|
3473
|
+
resolve();
|
|
2977
3474
|
return;
|
|
2978
3475
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
proxyRes.pause();
|
|
2986
|
-
res.onWritable(()=>{
|
|
2987
|
-
if (state.aborted) {
|
|
2988
|
-
proxyRes.destroy();
|
|
2989
|
-
return false;
|
|
3476
|
+
if (101 === statusCode) {
|
|
3477
|
+
res.cork(()=>{
|
|
3478
|
+
if (state.aborted) return;
|
|
3479
|
+
res.writeStatus(`${statusCode} ${statusMessage}`);
|
|
3480
|
+
for (const [key, value] of Object.entries(proxyRes.headers))if (!GatewayConstants.SKIP_RESPONSE_HEADERS.has(key.toLowerCase())) {
|
|
3481
|
+
if (value) res.writeHeader(key, Array.isArray(value) ? value.join(', ') : value);
|
|
2990
3482
|
}
|
|
2991
|
-
|
|
2992
|
-
|
|
3483
|
+
res.end();
|
|
3484
|
+
state.responded = true;
|
|
2993
3485
|
});
|
|
2994
|
-
}
|
|
2995
|
-
});
|
|
2996
|
-
proxyRes.on('end', ()=>{
|
|
2997
|
-
if (state.aborted) {
|
|
2998
3486
|
cleanup();
|
|
2999
3487
|
resolve();
|
|
3000
3488
|
return;
|
|
3001
3489
|
}
|
|
3002
3490
|
res.cork(()=>{
|
|
3003
3491
|
if (state.aborted) return;
|
|
3004
|
-
res.
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3492
|
+
res.writeStatus(`${statusCode} ${statusMessage}`);
|
|
3493
|
+
for (const [key, value] of Object.entries(proxyRes.headers))if (!GatewayConstants.SKIP_RESPONSE_HEADERS.has(key.toLowerCase())) {
|
|
3494
|
+
if (value) res.writeHeader(key, Array.isArray(value) ? value.join(', ') : value);
|
|
3495
|
+
}
|
|
3496
|
+
});
|
|
3497
|
+
proxyRes.on('data', (chunk)=>{
|
|
3498
|
+
if (state.aborted) return void proxyRes.destroy();
|
|
3499
|
+
if (perfLog) {
|
|
3500
|
+
chunkCount++;
|
|
3501
|
+
totalBytes += chunk.length;
|
|
3502
|
+
}
|
|
3503
|
+
let writeSuccess = false;
|
|
3504
|
+
res.cork(()=>{
|
|
3505
|
+
if (state.aborted) return;
|
|
3506
|
+
writeSuccess = res.write(chunk);
|
|
3014
3507
|
});
|
|
3508
|
+
if (!writeSuccess) {
|
|
3509
|
+
proxyRes.pause();
|
|
3510
|
+
res.onWritable(()=>{
|
|
3511
|
+
if (state.aborted) {
|
|
3512
|
+
proxyRes.destroy();
|
|
3513
|
+
return false;
|
|
3514
|
+
}
|
|
3515
|
+
proxyRes.resume();
|
|
3516
|
+
return true;
|
|
3517
|
+
});
|
|
3518
|
+
}
|
|
3519
|
+
});
|
|
3520
|
+
proxyRes.on('end', ()=>{
|
|
3521
|
+
if (state.aborted) {
|
|
3522
|
+
cleanup();
|
|
3523
|
+
resolve();
|
|
3524
|
+
return;
|
|
3525
|
+
}
|
|
3526
|
+
const perfStreamTime = perfLog ? performance.now() - perfStreamStart : 0;
|
|
3527
|
+
const perfTotalTime = perfLog ? performance.now() - perfPrepStart : 0;
|
|
3528
|
+
res.cork(()=>{
|
|
3529
|
+
if (state.aborted) return;
|
|
3530
|
+
res.end();
|
|
3531
|
+
state.responded = true;
|
|
3532
|
+
if (this.logging.enableRequestLog) {
|
|
3533
|
+
const responseTime = Date.now() - startTime;
|
|
3534
|
+
this.logger.info({
|
|
3535
|
+
msg: `📤 [${service.name}] ${method} ${path} - ${statusCode} - ${formatTime(responseTime)}`,
|
|
3536
|
+
service: service.name,
|
|
3537
|
+
method,
|
|
3538
|
+
path,
|
|
3539
|
+
statusCode,
|
|
3540
|
+
responseTime
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
if (perfLog) console.error(`⚡ [${service.name}] 性能分析:`, {
|
|
3544
|
+
method,
|
|
3545
|
+
path,
|
|
3546
|
+
statusCode,
|
|
3547
|
+
prepTime: perfPrepTime.toFixed(3) + 'ms',
|
|
3548
|
+
ttfb: perfTtfb.toFixed(3) + 'ms',
|
|
3549
|
+
streamTime: perfStreamTime.toFixed(3) + 'ms',
|
|
3550
|
+
totalTime: perfTotalTime.toFixed(3) + 'ms',
|
|
3551
|
+
chunkCount,
|
|
3552
|
+
totalBytes,
|
|
3553
|
+
avgChunkSize: chunkCount > 0 ? (totalBytes / chunkCount).toFixed(1) + 'B' : '0B'
|
|
3554
|
+
});
|
|
3555
|
+
});
|
|
3556
|
+
cleanup();
|
|
3557
|
+
resolve();
|
|
3558
|
+
});
|
|
3559
|
+
proxyRes.on('error', (err)=>{
|
|
3560
|
+
if (state.aborted) {
|
|
3561
|
+
cleanup();
|
|
3562
|
+
resolve();
|
|
3563
|
+
return;
|
|
3564
|
+
}
|
|
3565
|
+
this.logger.error({
|
|
3566
|
+
msg: `❌ [${service.name}] 代理响应错误`,
|
|
3567
|
+
error: err.message
|
|
3568
|
+
});
|
|
3569
|
+
if (!state.responded) {
|
|
3570
|
+
state.responded = true;
|
|
3571
|
+
res.cork(()=>{
|
|
3572
|
+
res.writeStatus('502 Bad Gateway');
|
|
3573
|
+
res.end('Bad Gateway');
|
|
3574
|
+
});
|
|
3575
|
+
}
|
|
3576
|
+
cleanup();
|
|
3577
|
+
reject(err);
|
|
3015
3578
|
});
|
|
3016
|
-
cleanup();
|
|
3017
|
-
resolve();
|
|
3018
3579
|
});
|
|
3019
|
-
|
|
3580
|
+
proxyReq.on('error', (err)=>{
|
|
3020
3581
|
if (state.aborted) {
|
|
3021
3582
|
cleanup();
|
|
3022
3583
|
resolve();
|
|
3023
3584
|
return;
|
|
3024
3585
|
}
|
|
3025
3586
|
this.logger.error({
|
|
3026
|
-
msg: `❌ [${service.name}]
|
|
3587
|
+
msg: `❌ [${service.name}] 代理请求错误`,
|
|
3027
3588
|
error: err.message
|
|
3028
3589
|
});
|
|
3029
3590
|
if (!state.responded) {
|
|
3030
3591
|
state.responded = true;
|
|
3031
|
-
|
|
3032
|
-
res.
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
res.end('Bad Gateway');
|
|
3036
|
-
}
|
|
3037
|
-
});
|
|
3038
|
-
} catch (sendErr) {
|
|
3039
|
-
const sendErrMsg = sendErr instanceof Error ? sendErr.message : String(sendErr);
|
|
3040
|
-
this.logger.error({
|
|
3041
|
-
msg: `❌ [${service.name}] 发送错误响应失败`,
|
|
3042
|
-
error: sendErrMsg
|
|
3043
|
-
});
|
|
3044
|
-
}
|
|
3592
|
+
res.cork(()=>{
|
|
3593
|
+
res.writeStatus('502 Bad Gateway');
|
|
3594
|
+
res.end('Bad Gateway');
|
|
3595
|
+
});
|
|
3045
3596
|
}
|
|
3046
3597
|
cleanup();
|
|
3047
3598
|
reject(err);
|
|
3048
3599
|
});
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
resolve();
|
|
3054
|
-
return;
|
|
3055
|
-
}
|
|
3600
|
+
if (body.length > 0) proxyReq.end(body);
|
|
3601
|
+
else proxyReq.end();
|
|
3602
|
+
} catch (err) {
|
|
3603
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3056
3604
|
this.logger.error({
|
|
3057
|
-
msg: `❌ [${service.name}]
|
|
3058
|
-
error:
|
|
3605
|
+
msg: `❌ [${service.name}] 创建代理请求失败`,
|
|
3606
|
+
error: message
|
|
3059
3607
|
});
|
|
3060
3608
|
if (!state.responded) {
|
|
3061
3609
|
state.responded = true;
|
|
3062
|
-
|
|
3063
|
-
res.
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
res.end('Bad Gateway');
|
|
3067
|
-
}
|
|
3068
|
-
});
|
|
3069
|
-
} catch (sendErr) {
|
|
3070
|
-
const sendErrMsg = sendErr instanceof Error ? sendErr.message : String(sendErr);
|
|
3071
|
-
this.logger.error({
|
|
3072
|
-
msg: `❌ [${service.name}] 发送错误响应失败`,
|
|
3073
|
-
error: sendErrMsg
|
|
3074
|
-
});
|
|
3075
|
-
}
|
|
3610
|
+
res.cork(()=>{
|
|
3611
|
+
res.writeStatus('502 Bad Gateway');
|
|
3612
|
+
res.end('Bad Gateway');
|
|
3613
|
+
});
|
|
3076
3614
|
}
|
|
3077
3615
|
cleanup();
|
|
3078
|
-
reject(
|
|
3079
|
-
}
|
|
3080
|
-
state.proxyReq.write(body);
|
|
3081
|
-
state.proxyReq.end();
|
|
3616
|
+
reject(new Error(message));
|
|
3617
|
+
}
|
|
3082
3618
|
});
|
|
3083
3619
|
}
|
|
3084
3620
|
async start() {
|
|
3085
|
-
const uWS = await Promise.resolve().then(__webpack_require__.bind(__webpack_require__, "uWebSockets.js"));
|
|
3086
3621
|
const host = this.config.host || '127.0.0.1';
|
|
3087
3622
|
const port = this.config.port || 3000;
|
|
3088
|
-
const app =
|
|
3623
|
+
const app = external_uWebSockets_js_default().App();
|
|
3089
3624
|
app.ws('/*', {
|
|
3090
3625
|
upgrade: (res, req, context)=>{
|
|
3091
3626
|
const hostname = req.getHeader('host')?.split(':')[0] || '';
|
|
3092
|
-
const
|
|
3093
|
-
if (!
|
|
3094
|
-
res.
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
return;
|
|
3099
|
-
}
|
|
3627
|
+
const mapping = this.hostnameRoutes.get(hostname);
|
|
3628
|
+
if (!mapping) return void res.cork(()=>{
|
|
3629
|
+
res.writeStatus('404 Not Found');
|
|
3630
|
+
res.end(`Service not found: ${hostname}`);
|
|
3631
|
+
});
|
|
3632
|
+
const { service, target } = mapping;
|
|
3100
3633
|
service._state.lastAccessTime = Date.now();
|
|
3101
3634
|
const clientHeaders = {};
|
|
3102
3635
|
req.forEach((key, value)=>{
|
|
3103
|
-
const safeValue = value.replace(
|
|
3636
|
+
const safeValue = value.replace(GatewayConstants.CRLF_REGEX, '');
|
|
3104
3637
|
clientHeaders[key] = safeValue;
|
|
3105
3638
|
});
|
|
3106
3639
|
const clientPath = req.getUrl() + (req.getQuery() ? `?${req.getQuery()}` : '');
|
|
3107
3640
|
res.upgrade({
|
|
3108
3641
|
hostname,
|
|
3109
3642
|
service,
|
|
3643
|
+
target,
|
|
3110
3644
|
clientHeaders,
|
|
3111
3645
|
clientPath
|
|
3112
3646
|
}, req.getHeader('sec-websocket-key'), req.getHeader('sec-websocket-protocol'), req.getHeader('sec-websocket-extensions'), context);
|
|
3113
|
-
this.logger.info({
|
|
3647
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3114
3648
|
msg: `🔌 [${service.name}] WebSocket 升级请求: ${clientPath}`
|
|
3115
3649
|
});
|
|
3116
3650
|
},
|
|
3117
3651
|
open: (ws)=>{
|
|
3118
3652
|
const userData = ws.getUserData();
|
|
3119
3653
|
const service = userData.service;
|
|
3654
|
+
const target = userData.target;
|
|
3120
3655
|
service._state.activeConnections++;
|
|
3121
|
-
this.logger.info({
|
|
3656
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3122
3657
|
msg: `🔌 [${service.name}] WebSocket 连接已建立`
|
|
3123
3658
|
});
|
|
3124
3659
|
const wsState = {
|
|
@@ -3140,7 +3675,7 @@ var __webpack_exports__ = {};
|
|
|
3140
3675
|
const waitStartTime = Date.now();
|
|
3141
3676
|
let isReady = false;
|
|
3142
3677
|
while(Date.now() - waitStartTime < service.startTimeout){
|
|
3143
|
-
isReady = await
|
|
3678
|
+
isReady = await gateway_checkTcpPort(target);
|
|
3144
3679
|
if (isReady) {
|
|
3145
3680
|
const waitDuration = Date.now() - waitStartTime;
|
|
3146
3681
|
this.logger.info({
|
|
@@ -3159,13 +3694,15 @@ var __webpack_exports__ = {};
|
|
|
3159
3694
|
return;
|
|
3160
3695
|
}
|
|
3161
3696
|
service._state.status = 'online';
|
|
3697
|
+
service._state.startTime = Date.now();
|
|
3698
|
+
service._state.startCount++;
|
|
3162
3699
|
}
|
|
3163
|
-
const targetUrl = new external_node_url_namespaceObject.URL(
|
|
3164
|
-
const
|
|
3165
|
-
const clientPath =
|
|
3166
|
-
const clientHeaders =
|
|
3700
|
+
const targetUrl = new external_node_url_namespaceObject.URL(target);
|
|
3701
|
+
const wsUserData = ws.getUserData();
|
|
3702
|
+
const clientPath = wsUserData.clientPath;
|
|
3703
|
+
const clientHeaders = wsUserData.clientHeaders;
|
|
3167
3704
|
const wsUrl = `${'https:' === targetUrl.protocol ? 'wss:' : 'ws:'}//${targetUrl.host}${clientPath}`;
|
|
3168
|
-
this.logger.info({
|
|
3705
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3169
3706
|
msg: `🔌 [${service.name}] 连接后端 WebSocket: ${wsUrl}`
|
|
3170
3707
|
});
|
|
3171
3708
|
const backendHeaders = {};
|
|
@@ -3187,17 +3724,17 @@ var __webpack_exports__ = {};
|
|
|
3187
3724
|
});
|
|
3188
3725
|
wsState.backendWs = backendWs;
|
|
3189
3726
|
backendWs.on('open', ()=>{
|
|
3190
|
-
this.logger.info({
|
|
3727
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3191
3728
|
msg: `✅ [${service.name}] 后端 WebSocket 连接已建立`
|
|
3192
3729
|
});
|
|
3193
3730
|
wsState.backendReady = true;
|
|
3194
|
-
this.logger.info({
|
|
3731
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3195
3732
|
msg: `📤 [${service.name}] 发送队列中的 ${wsState.messageQueue.length} 条消息`
|
|
3196
3733
|
});
|
|
3197
3734
|
while(wsState.messageQueue.length > 0 && backendWs.readyState === wrapper.OPEN){
|
|
3198
3735
|
const msg = wsState.messageQueue.shift();
|
|
3199
3736
|
if (msg) {
|
|
3200
|
-
this.logger.info({
|
|
3737
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3201
3738
|
msg: `📨 [${service.name}] 发送队列消息: ${msg.length} 字节`
|
|
3202
3739
|
});
|
|
3203
3740
|
backendWs.send(msg);
|
|
@@ -3222,7 +3759,7 @@ var __webpack_exports__ = {};
|
|
|
3222
3759
|
}
|
|
3223
3760
|
});
|
|
3224
3761
|
backendWs.on('close', ()=>{
|
|
3225
|
-
this.logger.info({
|
|
3762
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3226
3763
|
msg: `🔌 [${service.name}] 后端 WebSocket 连接关闭`
|
|
3227
3764
|
});
|
|
3228
3765
|
if (null !== ws && !wsState.closing) {
|
|
@@ -3239,12 +3776,12 @@ var __webpack_exports__ = {};
|
|
|
3239
3776
|
if (null !== ws) ws.close();
|
|
3240
3777
|
});
|
|
3241
3778
|
backendWs.on('pause', ()=>{
|
|
3242
|
-
this.logger.info({
|
|
3779
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3243
3780
|
msg: `⏸️ [${service.name}] 后端 WebSocket 暂停(背压)`
|
|
3244
3781
|
});
|
|
3245
3782
|
});
|
|
3246
3783
|
backendWs.on('resume', ()=>{
|
|
3247
|
-
this.logger.info({
|
|
3784
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3248
3785
|
msg: `▶️ [${service.name}] 后端 WebSocket 恢复`
|
|
3249
3786
|
});
|
|
3250
3787
|
});
|
|
@@ -3264,23 +3801,23 @@ var __webpack_exports__ = {};
|
|
|
3264
3801
|
const wsState = ws.wsState;
|
|
3265
3802
|
if (wsState.backendReady && wsState.backendWs && wsState.backendWs.readyState === wrapper.OPEN) {
|
|
3266
3803
|
const msgBuffer = Buffer.from(message);
|
|
3267
|
-
this.logger.info({
|
|
3804
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3268
3805
|
msg: `📨 [${service.name}] 转发消息到后端: ${msgBuffer.length} 字节`
|
|
3269
3806
|
});
|
|
3270
3807
|
wsState.backendWs.send(msgBuffer);
|
|
3271
3808
|
service._state.lastAccessTime = Date.now();
|
|
3272
3809
|
} else {
|
|
3273
|
-
this.logger.info({
|
|
3810
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3274
3811
|
msg: `📦 [${service.name}] 消息加入队列`
|
|
3275
3812
|
});
|
|
3276
|
-
wsState.messageQueue.push(Buffer.from(message));
|
|
3813
|
+
if (wsState.messageQueue.length < GatewayConstants.MAX_WS_MESSAGE_QUEUE_SIZE) wsState.messageQueue.push(Buffer.from(message));
|
|
3277
3814
|
}
|
|
3278
3815
|
},
|
|
3279
3816
|
close: (ws)=>{
|
|
3280
3817
|
const userData = ws.getUserData();
|
|
3281
3818
|
const service = userData.service;
|
|
3282
3819
|
service._state.activeConnections--;
|
|
3283
|
-
this.logger.info({
|
|
3820
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3284
3821
|
msg: `🔌 [${service.name}] 客户端 WebSocket 连接关闭`
|
|
3285
3822
|
});
|
|
3286
3823
|
const wsState = ws.wsState;
|
|
@@ -3301,27 +3838,270 @@ var __webpack_exports__ = {};
|
|
|
3301
3838
|
msg: `❌ DynaPM 网关启动失败: ${host}:${port}`
|
|
3302
3839
|
});
|
|
3303
3840
|
});
|
|
3841
|
+
for (const [portNum, mapping] of this.portRoutes)this.createPortBindingListener(host, portNum, mapping);
|
|
3842
|
+
const adminApiConfig = this.config.adminApi;
|
|
3843
|
+
if (adminApiConfig && false !== adminApiConfig.enabled && adminApiConfig.port) this.createAdminApiListener(host, adminApiConfig.port);
|
|
3844
|
+
}
|
|
3845
|
+
createAdminApiListener(host, port) {
|
|
3846
|
+
const app = external_uWebSockets_js_default().App();
|
|
3847
|
+
app.any('/*', (res, req)=>{
|
|
3848
|
+
this.adminApi.handleAdminApi(res, req);
|
|
3849
|
+
});
|
|
3850
|
+
app.listen(host, port, (token)=>{
|
|
3851
|
+
if (token) this.logger.info({
|
|
3852
|
+
msg: `🔌 管理 API 已启动: http://${host}:${port}`
|
|
3853
|
+
});
|
|
3854
|
+
else this.logger.error({
|
|
3855
|
+
msg: `❌ 管理 API 启动失败: ${host}:${port}`
|
|
3856
|
+
});
|
|
3857
|
+
});
|
|
3858
|
+
}
|
|
3859
|
+
createPortBindingListener(host, portNum, mapping) {
|
|
3860
|
+
const { service, target } = mapping;
|
|
3861
|
+
const app = external_uWebSockets_js_default().App();
|
|
3862
|
+
app.ws('/*', {
|
|
3863
|
+
upgrade: (res, req, context)=>{
|
|
3864
|
+
service._state.lastAccessTime = Date.now();
|
|
3865
|
+
const clientHeaders = {};
|
|
3866
|
+
req.forEach((key, value)=>{
|
|
3867
|
+
const safeValue = value.replace(GatewayConstants.CRLF_REGEX, '');
|
|
3868
|
+
clientHeaders[key] = safeValue;
|
|
3869
|
+
});
|
|
3870
|
+
const clientPath = req.getUrl() + (req.getQuery() ? `?${req.getQuery()}` : '');
|
|
3871
|
+
res.upgrade({
|
|
3872
|
+
service,
|
|
3873
|
+
target,
|
|
3874
|
+
clientHeaders,
|
|
3875
|
+
clientPath
|
|
3876
|
+
}, req.getHeader('sec-websocket-key'), req.getHeader('sec-websocket-protocol'), req.getHeader('sec-websocket-extensions'), context);
|
|
3877
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3878
|
+
msg: `🔌 [${service.name}] 端口${portNum} WebSocket 升级请求: ${clientPath}`
|
|
3879
|
+
});
|
|
3880
|
+
},
|
|
3881
|
+
open: (ws)=>{
|
|
3882
|
+
const userData = ws.getUserData();
|
|
3883
|
+
const svc = userData.service;
|
|
3884
|
+
const backendTarget = userData.target;
|
|
3885
|
+
svc._state.activeConnections++;
|
|
3886
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3887
|
+
msg: `🔌 [${svc.name}] 端口${portNum} WebSocket 连接已建立`
|
|
3888
|
+
});
|
|
3889
|
+
const wsState = {
|
|
3890
|
+
backendReady: false,
|
|
3891
|
+
messageQueue: [],
|
|
3892
|
+
backendWs: void 0,
|
|
3893
|
+
closing: false
|
|
3894
|
+
};
|
|
3895
|
+
ws.wsState = wsState;
|
|
3896
|
+
(async ()=>{
|
|
3897
|
+
try {
|
|
3898
|
+
const needsStart = 'offline' === svc._state.status;
|
|
3899
|
+
if (needsStart) {
|
|
3900
|
+
this.logger.info({
|
|
3901
|
+
msg: `🚀 [${svc.name}] 端口${portNum} WebSocket - 启动服务...`
|
|
3902
|
+
});
|
|
3903
|
+
svc._state.status = 'starting';
|
|
3904
|
+
await this.serviceManager.start(svc);
|
|
3905
|
+
const waitStartTime = Date.now();
|
|
3906
|
+
let isReady = false;
|
|
3907
|
+
while(Date.now() - waitStartTime < svc.startTimeout){
|
|
3908
|
+
isReady = await gateway_checkTcpPort(backendTarget);
|
|
3909
|
+
if (isReady) {
|
|
3910
|
+
const waitDuration = Date.now() - waitStartTime;
|
|
3911
|
+
this.logger.info({
|
|
3912
|
+
msg: `✅ [${svc.name}] 端口${portNum} WebSocket 服务就绪 (等待${formatTime(waitDuration)})`
|
|
3913
|
+
});
|
|
3914
|
+
break;
|
|
3915
|
+
}
|
|
3916
|
+
await new Promise((resolve)=>setTimeout(resolve, GatewayConstants.BACKEND_READY_CHECK_DELAY));
|
|
3917
|
+
}
|
|
3918
|
+
if (!isReady) {
|
|
3919
|
+
svc._state.status = 'offline';
|
|
3920
|
+
this.logger.error({
|
|
3921
|
+
msg: `❌ [${svc.name}] 端口${portNum} WebSocket 服务启动超时`
|
|
3922
|
+
});
|
|
3923
|
+
ws.close();
|
|
3924
|
+
return;
|
|
3925
|
+
}
|
|
3926
|
+
svc._state.status = 'online';
|
|
3927
|
+
svc._state.startTime = Date.now();
|
|
3928
|
+
svc._state.startCount++;
|
|
3929
|
+
}
|
|
3930
|
+
const targetUrl = new external_node_url_namespaceObject.URL(backendTarget);
|
|
3931
|
+
const wsUserData = ws.getUserData();
|
|
3932
|
+
const clientPath = wsUserData.clientPath;
|
|
3933
|
+
const clientHeaders = wsUserData.clientHeaders;
|
|
3934
|
+
const wsUrl = `${'https:' === targetUrl.protocol ? 'wss:' : 'ws:'}//${targetUrl.host}${clientPath}`;
|
|
3935
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3936
|
+
msg: `🔌 [${svc.name}] 端口${portNum} 连接后端 WebSocket: ${wsUrl}`
|
|
3937
|
+
});
|
|
3938
|
+
const backendHeaders = {};
|
|
3939
|
+
const skipHeaders = new Set([
|
|
3940
|
+
'host',
|
|
3941
|
+
'connection',
|
|
3942
|
+
'upgrade',
|
|
3943
|
+
'sec-websocket-key',
|
|
3944
|
+
'sec-websocket-version'
|
|
3945
|
+
]);
|
|
3946
|
+
for (const [key, value] of Object.entries(clientHeaders))if (!skipHeaders.has(key.toLowerCase())) backendHeaders[key] = value;
|
|
3947
|
+
backendHeaders['Host'] = targetUrl.host;
|
|
3948
|
+
const backendWs = new wrapper(wsUrl, {
|
|
3949
|
+
headers: backendHeaders
|
|
3950
|
+
});
|
|
3951
|
+
wsState.backendWs = backendWs;
|
|
3952
|
+
backendWs.on('open', ()=>{
|
|
3953
|
+
if (this.logging.enableWebSocketLog) this.logger.info({
|
|
3954
|
+
msg: `✅ [${svc.name}] 端口${portNum} 后端 WebSocket 连接已建立`
|
|
3955
|
+
});
|
|
3956
|
+
wsState.backendReady = true;
|
|
3957
|
+
while(wsState.messageQueue.length > 0 && backendWs.readyState === wrapper.OPEN){
|
|
3958
|
+
const msg = wsState.messageQueue.shift();
|
|
3959
|
+
if (msg) backendWs.send(msg);
|
|
3960
|
+
}
|
|
3961
|
+
});
|
|
3962
|
+
backendWs.on('message', (data, isBinary)=>{
|
|
3963
|
+
if (null !== ws) ws.send(data, isBinary, false);
|
|
3964
|
+
});
|
|
3965
|
+
backendWs.on('close', ()=>{
|
|
3966
|
+
if (null !== ws && !wsState.closing) {
|
|
3967
|
+
wsState.closing = true;
|
|
3968
|
+
ws.close();
|
|
3969
|
+
}
|
|
3970
|
+
});
|
|
3971
|
+
backendWs.on('error', ()=>{
|
|
3972
|
+
wsState.closing = true;
|
|
3973
|
+
if (null !== ws) ws.close();
|
|
3974
|
+
});
|
|
3975
|
+
} catch (error) {
|
|
3976
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3977
|
+
this.logger.error({
|
|
3978
|
+
msg: `❌ [${service.name}] WebSocket 连接失败`,
|
|
3979
|
+
error: message
|
|
3980
|
+
});
|
|
3981
|
+
if (null !== ws) ws.close();
|
|
3982
|
+
}
|
|
3983
|
+
})();
|
|
3984
|
+
},
|
|
3985
|
+
message: (ws, message, _isBinary)=>{
|
|
3986
|
+
const userData = ws.getUserData();
|
|
3987
|
+
const svc = userData.service;
|
|
3988
|
+
const wsState = ws.wsState;
|
|
3989
|
+
if (wsState.backendReady && wsState.backendWs && wsState.backendWs.readyState === wrapper.OPEN) {
|
|
3990
|
+
wsState.backendWs.send(Buffer.from(message));
|
|
3991
|
+
svc._state.lastAccessTime = Date.now();
|
|
3992
|
+
} else if (wsState.messageQueue.length < GatewayConstants.MAX_WS_MESSAGE_QUEUE_SIZE) wsState.messageQueue.push(Buffer.from(message));
|
|
3993
|
+
},
|
|
3994
|
+
close: (ws)=>{
|
|
3995
|
+
const userData = ws.getUserData();
|
|
3996
|
+
const svc = userData.service;
|
|
3997
|
+
svc._state.activeConnections--;
|
|
3998
|
+
const wsState = ws.wsState;
|
|
3999
|
+
if (wsState?.backendWs && wsState.backendWs.readyState === wrapper.OPEN) {
|
|
4000
|
+
wsState.closing = true;
|
|
4001
|
+
wsState.backendWs.close();
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
});
|
|
4005
|
+
app.any('/*', (res, req)=>{
|
|
4006
|
+
this.handlePortBindingRequest(res, req, mapping);
|
|
4007
|
+
});
|
|
4008
|
+
app.listen(host, portNum, (token)=>{
|
|
4009
|
+
if (token) this.logger.info({
|
|
4010
|
+
msg: `🔌 端口绑定已启动: http://${host}:${portNum} -> ${service.name}`
|
|
4011
|
+
});
|
|
4012
|
+
else this.logger.error({
|
|
4013
|
+
msg: `❌ 端口绑定启动失败: ${host}:${portNum}`
|
|
4014
|
+
});
|
|
4015
|
+
});
|
|
4016
|
+
}
|
|
4017
|
+
async cleanup() {
|
|
4018
|
+
this.logger.info({
|
|
4019
|
+
msg: '🧹 正在清理所有服务...'
|
|
4020
|
+
});
|
|
4021
|
+
const cleanedServices = new Set();
|
|
4022
|
+
for (const mapping of this.hostnameRoutes.values())cleanedServices.add(mapping.service);
|
|
4023
|
+
for (const mapping of this.portRoutes.values())cleanedServices.add(mapping.service);
|
|
4024
|
+
const stopPromises = [];
|
|
4025
|
+
for (const service of cleanedServices)if ('online' === service._state.status || 'starting' === service._state.status) stopPromises.push(this.serviceManager.stop(service).catch((err)=>{
|
|
4026
|
+
this.logger.error({
|
|
4027
|
+
msg: `❌ [${service.name}] 停止失败`,
|
|
4028
|
+
error: err.message
|
|
4029
|
+
});
|
|
4030
|
+
}));
|
|
4031
|
+
await Promise.all(stopPromises);
|
|
4032
|
+
this.logger.info({
|
|
4033
|
+
msg: `✅ 已清理 ${cleanedServices.size} 个服务`
|
|
4034
|
+
});
|
|
3304
4035
|
}
|
|
3305
4036
|
}
|
|
3306
4037
|
const external_c12_namespaceObject = require("c12");
|
|
3307
4038
|
async function loadDynaPMConfig() {
|
|
4039
|
+
const configFile = process.env.DYNAPM_CONFIG || void 0;
|
|
3308
4040
|
const { config } = await (0, external_c12_namespaceObject.loadConfig)({
|
|
3309
4041
|
name: 'dynapm',
|
|
4042
|
+
configFile,
|
|
3310
4043
|
defaultConfig: {
|
|
3311
4044
|
port: 3000,
|
|
3312
4045
|
host: '127.0.0.1',
|
|
3313
|
-
services: {}
|
|
4046
|
+
services: {},
|
|
4047
|
+
adminApi: {
|
|
4048
|
+
enabled: true,
|
|
4049
|
+
port: 4000,
|
|
4050
|
+
allowedIps: [
|
|
4051
|
+
'127.0.0.1',
|
|
4052
|
+
'::1'
|
|
4053
|
+
]
|
|
4054
|
+
}
|
|
3314
4055
|
}
|
|
3315
4056
|
});
|
|
3316
4057
|
if (!config.services || 0 === Object.keys(config.services).length) throw new Error('配置文件中至少需要一个服务');
|
|
3317
|
-
|
|
3318
|
-
|
|
4058
|
+
const mainPort = config.port || 3000;
|
|
4059
|
+
if (config.adminApi?.enabled && config.adminApi.port === mainPort) throw new Error(`管理 API 端口 ${mainPort} 与主端口冲突`);
|
|
4060
|
+
const portMap = new Map();
|
|
4061
|
+
const hostnameMap = new Map();
|
|
4062
|
+
for (const [key, service] of Object.entries(config.services)){
|
|
4063
|
+
service.name = service.name || key;
|
|
3319
4064
|
service.idleTimeout = service.idleTimeout || 300000;
|
|
3320
4065
|
service.startTimeout = service.startTimeout || 30000;
|
|
3321
4066
|
service.healthCheck = service.healthCheck || {
|
|
3322
4067
|
type: 'tcp'
|
|
3323
4068
|
};
|
|
3324
4069
|
if ('http' === service.healthCheck.type && !service.healthCheck.url) service.healthCheck.url = service.base;
|
|
4070
|
+
let routes = service.routes;
|
|
4071
|
+
if (!routes || 0 === routes.length) {
|
|
4072
|
+
routes = [];
|
|
4073
|
+
if (service.host) routes.push({
|
|
4074
|
+
type: 'host',
|
|
4075
|
+
value: service.host,
|
|
4076
|
+
target: service.base
|
|
4077
|
+
});
|
|
4078
|
+
if (service.port) routes.push({
|
|
4079
|
+
type: 'port',
|
|
4080
|
+
value: service.port,
|
|
4081
|
+
target: service.base
|
|
4082
|
+
});
|
|
4083
|
+
if (0 === routes.length) routes.push({
|
|
4084
|
+
type: 'host',
|
|
4085
|
+
value: key,
|
|
4086
|
+
target: service.base
|
|
4087
|
+
});
|
|
4088
|
+
service.routes = routes;
|
|
4089
|
+
}
|
|
4090
|
+
for (const route of routes)if (!route.target) throw new Error(`服务 [${service.name}] 的路由配置缺少 target 字段`);
|
|
4091
|
+
for (const route of routes)if ('port' === route.type) {
|
|
4092
|
+
const port = route.value;
|
|
4093
|
+
if (port === mainPort) throw new Error(`服务 [${service.name}] 的路由端口 ${port} 与主端口冲突`);
|
|
4094
|
+
const adminApiPort = config.adminApi?.enabled ? config.adminApi.port : void 0;
|
|
4095
|
+
if (adminApiPort && port === adminApiPort) throw new Error(`服务 [${service.name}] 的路由端口 ${port} 与管理 API 端口冲突`);
|
|
4096
|
+
const existingService = portMap.get(port);
|
|
4097
|
+
if (existingService) throw new Error(`端口冲突: 服务 [${service.name}] 和 [${existingService}] 都配置了端口 ${port}`);
|
|
4098
|
+
portMap.set(port, service.name);
|
|
4099
|
+
} else if ('host' === route.type) {
|
|
4100
|
+
const hostname = route.value;
|
|
4101
|
+
const existingService = hostnameMap.get(hostname);
|
|
4102
|
+
if (existingService && existingService !== service.name) throw new Error(`Hostname 冲突: 服务 [${service.name}] 和 [${existingService}] 都配置了 hostname ${hostname}`);
|
|
4103
|
+
hostnameMap.set(hostname, service.name);
|
|
4104
|
+
}
|
|
3325
4105
|
}
|
|
3326
4106
|
return config;
|
|
3327
4107
|
}
|
|
@@ -3377,6 +4157,20 @@ var __webpack_exports__ = {};
|
|
|
3377
4157
|
msg: 'DynaPM 网关已启动',
|
|
3378
4158
|
port: config.port || 3000
|
|
3379
4159
|
});
|
|
4160
|
+
const cleanup = async (signal)=>{
|
|
4161
|
+
logger.info({
|
|
4162
|
+
msg: `⚠️ 收到 ${signal} 信号,正在清理...`
|
|
4163
|
+
});
|
|
4164
|
+
await gateway.cleanup();
|
|
4165
|
+
process.exit(0);
|
|
4166
|
+
};
|
|
4167
|
+
process.on('SIGINT', ()=>cleanup('SIGINT'));
|
|
4168
|
+
process.on('SIGTERM', ()=>cleanup('SIGTERM'));
|
|
4169
|
+
process.on('exit', ()=>{
|
|
4170
|
+
logger.info({
|
|
4171
|
+
msg: '👋 DynaPM 网关已退出'
|
|
4172
|
+
});
|
|
4173
|
+
});
|
|
3380
4174
|
} catch (error) {
|
|
3381
4175
|
logger.error({
|
|
3382
4176
|
msg: '启动失败',
|
|
@@ -3387,8 +4181,7 @@ var __webpack_exports__ = {};
|
|
|
3387
4181
|
}
|
|
3388
4182
|
main();
|
|
3389
4183
|
})();
|
|
3390
|
-
var
|
|
3391
|
-
|
|
3392
|
-
if (__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_target__, '__esModule', {
|
|
4184
|
+
for(var __rspack_i in __webpack_exports__)exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
4185
|
+
Object.defineProperty(exports, '__esModule', {
|
|
3393
4186
|
value: true
|
|
3394
4187
|
});
|