@interopio/gateway-server 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/gateway-server.cjs +726 -0
- package/changelog.md +27 -0
- package/dist/config.cjs +2 -0
- package/dist/config.cjs.map +7 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +7 -0
- package/dist/index.cjs +17 -3
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +17 -3
- package/dist/index.js.map +4 -4
- package/dist/metrics/publisher/rest.js +1 -1
- package/dist/metrics/publisher/rest.js.map +3 -3
- package/dist/tools.cjs +2 -0
- package/dist/tools.cjs.map +7 -0
- package/dist/tools.js +2 -0
- package/dist/tools.js.map +7 -0
- package/dist/web/test.js +13 -2
- package/dist/web/test.js.map +4 -4
- package/gateway-server.d.ts +3 -116
- package/package.json +11 -5
- package/readme.md +58 -0
- package/types/config.d.ts +147 -0
- package/types/manage.d.ts +46 -0
- package/types/path.d.ts +20 -0
- package/types/tools.d.ts +1 -2
- package/types/web/server.d.ts +11 -3
- package/types/web/test.d.ts +6 -2
- package/dist/metrics/publisher/rest.cjs +0 -2
- package/dist/metrics/publisher/rest.cjs.map +0 -7
- package/dist/tools/index.js +0 -2
- package/dist/tools/index.js.map +0 -7
- package/gateway-server +0 -442
package/gateway-server
DELETED
|
@@ -1,442 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
`use strict`;
|
|
4
|
-
|
|
5
|
-
import { writeFileSync } from 'node:fs';
|
|
6
|
-
import { configure } from '@interopio/gateway/logging/core';
|
|
7
|
-
|
|
8
|
-
// import { GatewayServer } from './src/index.ts';
|
|
9
|
-
// import { mkcert, argon2 } from './src/tools/index.ts';
|
|
10
|
-
|
|
11
|
-
import { GatewayServer } from './dist/index.js';
|
|
12
|
-
import { mkcert, argon2 } from './dist/tools/index.js';
|
|
13
|
-
|
|
14
|
-
function showHelp() {
|
|
15
|
-
console.log(`
|
|
16
|
-
Usage: gateway-server <command> [options]
|
|
17
|
-
|
|
18
|
-
Commands:
|
|
19
|
-
run Run the gateway server (default if no command specified)
|
|
20
|
-
passwd Generate password hash (default using Argon2)
|
|
21
|
-
mkcert Generate client and/or server certificate signed by Dev CA
|
|
22
|
-
|
|
23
|
-
run options:
|
|
24
|
-
-p, --port <port> Specify port or port range to bind the server to (default: 0 i.e. random available port)
|
|
25
|
-
examples: 8385, 8000-8100, 3000,4000-4050
|
|
26
|
-
-H, --host <host> Network address to bind to (default: unspecified, listens on all interfaces)
|
|
27
|
-
examples: localhost, 127.0.0.1, 0.0.0.0, ::1
|
|
28
|
-
-u, --user <name> Enables basic authentication and sets the admin username
|
|
29
|
-
-S, --ssl, --tls Enable HTTPS. Auto-generates Dev CA and/or server certificates if not present. Enables x509 client cert auth if --user not specified.
|
|
30
|
-
--cert <file> File with SSL/TLS certificate (default: ./gateway-server.crt)
|
|
31
|
-
--key <file> File with SSL/TLS private key (default: ./gateway-server.key)
|
|
32
|
-
--ca <file> File with CA certificates (default: ./gateway-ca.crt)
|
|
33
|
-
--ca-key <file> File with CA private key (default: ./gateway-ca.key)
|
|
34
|
-
--debug Enable debug logging (default: info level)
|
|
35
|
-
|
|
36
|
-
passwd options:
|
|
37
|
-
(no args) Prompt for password interactively (masked input)
|
|
38
|
-
--stdin Read password from stdin (for piping, e.g., echo "pass" | ...)
|
|
39
|
-
|
|
40
|
-
mkcert options:
|
|
41
|
-
--client Generate client certificate (default: server certificate)
|
|
42
|
-
-u, --user <name> Common Name for the certificate (default: dev-user for client certs) (--client only)
|
|
43
|
-
--ca <file> File with CA certificate (default: ./gateway-ca.crt)
|
|
44
|
-
--ca-key <file> File with CA private key (default: ./gateway-ca.key)
|
|
45
|
-
--key <file> Output file for private key (default: ./gateway-server.key for server, ./gateway-client.key for client)
|
|
46
|
-
--cert <file> Output file for certificate (default: ./gateway-server.crt for server, ./gateway-client.crt for client)
|
|
47
|
-
[name...] DNS names, IP addresses, or email addresses for subjectAltName
|
|
48
|
-
(DNS by default, prefix with 'IP:' or 'EMAIL:' for other types)
|
|
49
|
-
|
|
50
|
-
Global Options:
|
|
51
|
-
-v, --version Show version information and exit
|
|
52
|
-
--help Show this help message and exit
|
|
53
|
-
|
|
54
|
-
Examples:
|
|
55
|
-
gateway-server run -p 3000
|
|
56
|
-
gateway-server run -u admin --port 8385,8388 --debug
|
|
57
|
-
gateway-server run -p 8443 --ssl --user admin
|
|
58
|
-
gateway-server passwd
|
|
59
|
-
echo "mySecret123" | gateway-server passwd --stdin
|
|
60
|
-
gateway-server mkcert
|
|
61
|
-
gateway-server mkcert localhost 127.0.0.1 IP:192.168.1.100
|
|
62
|
-
gateway-server mkcert --client EMAIL:john.doe@example.com
|
|
63
|
-
gateway-server mkcert example.com *.example.com --key ./gateway-server.key --cert ./gateway-server.crt
|
|
64
|
-
`);
|
|
65
|
-
process.exit(0);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function showVersion() {
|
|
69
|
-
console.log(GatewayServer.VERSION);
|
|
70
|
-
process.exit(0);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
// Parse command-line arguments
|
|
75
|
-
const args = process.argv.slice(2);
|
|
76
|
-
|
|
77
|
-
// Check for global flags first
|
|
78
|
-
if (args.includes('--version') || args.includes('-v')) {
|
|
79
|
-
showVersion();
|
|
80
|
-
}
|
|
81
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
82
|
-
showHelp();
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Determine command (default to 'run' for backward compatibility)
|
|
86
|
-
let command = 'run';
|
|
87
|
-
let commandArgs = args;
|
|
88
|
-
|
|
89
|
-
if (args.length > 0 && !args[0].startsWith('-')) {
|
|
90
|
-
command = args[0];
|
|
91
|
-
commandArgs = args.slice(1);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Route to appropriate command handler
|
|
95
|
-
switch (command) {
|
|
96
|
-
case 'run':
|
|
97
|
-
await runServer(commandArgs);
|
|
98
|
-
break;
|
|
99
|
-
case 'passwd':
|
|
100
|
-
await passwdCommand(commandArgs);
|
|
101
|
-
break;
|
|
102
|
-
case 'mkcert':
|
|
103
|
-
await mkcertCommand(commandArgs);
|
|
104
|
-
break;
|
|
105
|
-
default:
|
|
106
|
-
console.error(`Error: Unknown command "${command}"`);
|
|
107
|
-
console.log('Use --help to see available commands');
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Command: passwd - Generate password hash
|
|
112
|
-
async function passwdCommand(args) {
|
|
113
|
-
let password = undefined;
|
|
114
|
-
let fromStdin = false;
|
|
115
|
-
|
|
116
|
-
for (let i = 0; i < args.length; i++) {
|
|
117
|
-
if (args[i] === '--stdin') {
|
|
118
|
-
fromStdin = true;
|
|
119
|
-
} else {
|
|
120
|
-
console.error(`Error: Unknown option "${args[i]}"`);
|
|
121
|
-
console.log('Use: gateway-server passwd');
|
|
122
|
-
console.log(' or: gateway-server passwd --stdin');
|
|
123
|
-
process.exit(1);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Read password from stdin if specified
|
|
128
|
-
if (fromStdin) {
|
|
129
|
-
password = await readPasswordFromStdin();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// If no password provided, prompt interactively
|
|
133
|
-
if (!password) {
|
|
134
|
-
password = await promptPassword('Enter password: ');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (!password) {
|
|
138
|
-
console.error('Error: password is required');
|
|
139
|
-
process.exit(1);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const hashValue = await argon2.hash(password);
|
|
143
|
-
const hash = `{argon2id}${hashValue}`;
|
|
144
|
-
console.log(hash);
|
|
145
|
-
process.exit(0);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Read password from piped stdin (non-interactive)
|
|
149
|
-
async function readPasswordFromStdin() {
|
|
150
|
-
// Check if stdin is a TTY (interactive terminal)
|
|
151
|
-
if (process.stdin.isTTY) {
|
|
152
|
-
// Interactive mode - use readline with hidden input
|
|
153
|
-
return promptPassword('Password: ');
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Piped mode - read until EOF or newline
|
|
157
|
-
const chunks = [];
|
|
158
|
-
for await (const chunk of process.stdin) {
|
|
159
|
-
chunks.push(chunk);
|
|
160
|
-
}
|
|
161
|
-
const content = Buffer.concat(chunks).toString('utf8');
|
|
162
|
-
// Strip trailing newline
|
|
163
|
-
return content.replace(/\r?\n$/, '');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Prompt for password with masked input
|
|
167
|
-
async function promptPassword(prompt) {
|
|
168
|
-
return new Promise((resolve) => {
|
|
169
|
-
process.stdout.write(prompt);
|
|
170
|
-
|
|
171
|
-
// Only set raw mode if stdin is a TTY
|
|
172
|
-
if (!process.stdin.isTTY) {
|
|
173
|
-
console.error('Error: Interactive password prompt requires a terminal');
|
|
174
|
-
process.exit(1);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
process.stdin.setRawMode(true);
|
|
178
|
-
process.stdin.resume();
|
|
179
|
-
process.stdin.setEncoding('utf8');
|
|
180
|
-
|
|
181
|
-
let password = '';
|
|
182
|
-
|
|
183
|
-
const cleanup = () => {
|
|
184
|
-
process.stdin.setRawMode(false);
|
|
185
|
-
process.stdin.pause();
|
|
186
|
-
process.stdin.removeListener('data', onData);
|
|
187
|
-
process.stdout.write('\n');
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
const onData = (char) => {
|
|
191
|
-
// Ctrl+C
|
|
192
|
-
if (char === '\u0003') {
|
|
193
|
-
cleanup();
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Enter
|
|
198
|
-
if (char === '\r' || char === '\n') {
|
|
199
|
-
cleanup();
|
|
200
|
-
resolve(password);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Backspace
|
|
205
|
-
if (char === '\u007f' || char === '\b') {
|
|
206
|
-
if (password.length > 0) {
|
|
207
|
-
password = password.slice(0, -1);
|
|
208
|
-
process.stdout.write('\b \b');
|
|
209
|
-
}
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Regular character
|
|
214
|
-
password += char;
|
|
215
|
-
process.stdout.write('*');
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
process.stdin.on('data', onData);
|
|
219
|
-
});
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Command: mkcert - Generate client or server certificate
|
|
223
|
-
async function mkcertCommand(args) {
|
|
224
|
-
let isClientCert = false;
|
|
225
|
-
let user = 'dev-user';
|
|
226
|
-
let caCert = './gateway-ca.crt';
|
|
227
|
-
let caKey = './gateway-ca.key';
|
|
228
|
-
let keyPath = undefined;
|
|
229
|
-
let certPath = undefined;
|
|
230
|
-
const sanEntries = [];
|
|
231
|
-
|
|
232
|
-
for (let i = 0; i < args.length; i++) {
|
|
233
|
-
if (args[i] === '--client') {
|
|
234
|
-
isClientCert = true;
|
|
235
|
-
} else if ((args[i] === '--user' || args[i] === '-u') && i + 1 < args.length) {
|
|
236
|
-
user = args[i + 1];
|
|
237
|
-
i++;
|
|
238
|
-
} else if (args[i] === '--ca' && i + 1 < args.length) {
|
|
239
|
-
caCert = args[i + 1];
|
|
240
|
-
i++;
|
|
241
|
-
} else if (args[i] === '--ca-key' && i + 1 < args.length) {
|
|
242
|
-
caKey = args[i + 1];
|
|
243
|
-
i++;
|
|
244
|
-
} else if (args[i] === '--key' && i + 1 < args.length) {
|
|
245
|
-
keyPath = args[i + 1];
|
|
246
|
-
i++;
|
|
247
|
-
} else if (args[i] === '--cert' && i + 1 < args.length) {
|
|
248
|
-
certPath = args[i + 1];
|
|
249
|
-
i++;
|
|
250
|
-
} else if (!args[i].startsWith('-')) {
|
|
251
|
-
// Positional argument - add to SAN entries
|
|
252
|
-
sanEntries.push(args[i]);
|
|
253
|
-
} else {
|
|
254
|
-
console.error(`Error: Unknown option "${args[i]}"`);
|
|
255
|
-
console.log('Use: gateway-server mkcert [--client] [--user <name>] [--ca <file>] [--ca-key <file>] [--key <file>] [--cert <file>] [name...]');
|
|
256
|
-
process.exit(1);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Default output paths
|
|
261
|
-
// Users typically specify just --cert, and key should go in the same file
|
|
262
|
-
if (!keyPath && !certPath) {
|
|
263
|
-
// Neither specified: use defaults based on certificate type
|
|
264
|
-
if (isClientCert) {
|
|
265
|
-
// Client certificate: combined file by default
|
|
266
|
-
keyPath = './gateway-client.key';
|
|
267
|
-
certPath = './gateway-client.crt';
|
|
268
|
-
} else {
|
|
269
|
-
// Server certificate: separate files by default
|
|
270
|
-
keyPath = './gateway-server.key';
|
|
271
|
-
certPath = './gateway-server.crt';
|
|
272
|
-
}
|
|
273
|
-
} else if (keyPath && !certPath) {
|
|
274
|
-
// Only --key specified: cert goes in the same file (combined)
|
|
275
|
-
certPath = keyPath;
|
|
276
|
-
} else if (!keyPath && certPath) {
|
|
277
|
-
// Only --cert specified: key goes in the same file (combined)
|
|
278
|
-
keyPath = certPath;
|
|
279
|
-
}
|
|
280
|
-
// else: both specified, use as-is
|
|
281
|
-
|
|
282
|
-
if (sanEntries.length === 0) {
|
|
283
|
-
if (isClientCert) {
|
|
284
|
-
sanEntries.push(user);
|
|
285
|
-
}
|
|
286
|
-
else {
|
|
287
|
-
// Default SAN entry for server certs
|
|
288
|
-
sanEntries.push('localhost');
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const { readFileSync } = await import('node:fs');
|
|
293
|
-
const { KEYUTIL, X509 } = await import('jsrsasign');
|
|
294
|
-
|
|
295
|
-
// Load CA key
|
|
296
|
-
let caKeyObj;
|
|
297
|
-
try {
|
|
298
|
-
const caKeyPem = readFileSync(caKey, 'utf8');
|
|
299
|
-
caKeyObj = KEYUTIL.getKey(caKeyPem);
|
|
300
|
-
} catch (error) {
|
|
301
|
-
console.error(`Error: Cannot read CA key from ${caKey}`);
|
|
302
|
-
console.error(error.message);
|
|
303
|
-
process.exit(1);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Extract issuer from CA certificate
|
|
307
|
-
let issuer;
|
|
308
|
-
try {
|
|
309
|
-
const caCertPem = readFileSync(caCert, 'utf8');
|
|
310
|
-
const caCertObj = new X509();
|
|
311
|
-
caCertObj.readCertPEM(caCertPem);
|
|
312
|
-
issuer = caCertObj.getSubjectString();
|
|
313
|
-
} catch (error) {
|
|
314
|
-
console.error(`Error: Cannot read CA certificate from ${caCert}`);
|
|
315
|
-
console.error(error.message);
|
|
316
|
-
process.exit(1);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Generate certificate using SAN entries
|
|
320
|
-
const cert = mkcert.generateCert(caKeyObj, issuer, sanEntries, isClientCert);
|
|
321
|
-
|
|
322
|
-
// Write files
|
|
323
|
-
try {
|
|
324
|
-
if (keyPath === certPath) {
|
|
325
|
-
// Concatenate cert and key into single file (cert first, then key)
|
|
326
|
-
const combined = cert.cert + cert.key;
|
|
327
|
-
writeFileSync(keyPath, combined, { mode: 0o600 });
|
|
328
|
-
console.log(`${isClientCert ? 'Client' : 'Server'} certificate generated successfully:`);
|
|
329
|
-
console.log(` Combined (cert+key): ${keyPath}`);
|
|
330
|
-
if (sanEntries.length > 0) {
|
|
331
|
-
console.log(` SAN: ${sanEntries.join(', ')}`);
|
|
332
|
-
}
|
|
333
|
-
} else {
|
|
334
|
-
// Write separate files
|
|
335
|
-
writeFileSync(keyPath, cert.key, { mode: 0o600 });
|
|
336
|
-
writeFileSync(certPath, cert.cert, { mode: 0o644 });
|
|
337
|
-
console.log(`${isClientCert ? 'Client' : 'Server'} certificate generated successfully:`);
|
|
338
|
-
console.log(` Private key: ${keyPath}`);
|
|
339
|
-
console.log(` Certificate: ${certPath}`);
|
|
340
|
-
if (sanEntries.length > 0) {
|
|
341
|
-
console.log(` SAN: ${sanEntries.join(', ')}`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
process.exit(0);
|
|
346
|
-
} catch (error) {
|
|
347
|
-
console.error(`Error: Cannot write certificate files`);
|
|
348
|
-
console.error(error.message);
|
|
349
|
-
process.exit(1);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Command: run - Start the server
|
|
354
|
-
async function runServer(args) {
|
|
355
|
-
const options = {
|
|
356
|
-
port: 0,
|
|
357
|
-
host: undefined,
|
|
358
|
-
user: undefined,
|
|
359
|
-
debug: false,
|
|
360
|
-
ssl: false,
|
|
361
|
-
cert: undefined,
|
|
362
|
-
key: undefined,
|
|
363
|
-
ca: undefined,
|
|
364
|
-
caKey: undefined
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
for (let i = 0; i < args.length; i++) {
|
|
368
|
-
if ((args[i] === '--port' || args[i] === '-p') && i + 1 < args.length) {
|
|
369
|
-
options.port = args[i + 1];
|
|
370
|
-
i++;
|
|
371
|
-
} else if ((args[i] === '--host' || args[i] === '-H') && i + 1 < args.length) {
|
|
372
|
-
options.host = args[i + 1];
|
|
373
|
-
i++;
|
|
374
|
-
} else if ((args[i] === '--user' || args[i] === '-u') && i + 1 < args.length) {
|
|
375
|
-
options.user = args[i + 1];
|
|
376
|
-
i++;
|
|
377
|
-
} else if (args[i] === '--ssl' || args[i] === '-S' || args[i] === '--tls') {
|
|
378
|
-
options.ssl = true;
|
|
379
|
-
} else if (args[i] === '--cert' && i + 1 < args.length) {
|
|
380
|
-
options.cert = args[i + 1];
|
|
381
|
-
i++;
|
|
382
|
-
} else if (args[i] === '--key' && i + 1 < args.length) {
|
|
383
|
-
options.key = args[i + 1];
|
|
384
|
-
i++;
|
|
385
|
-
} else if (args[i] === '--ca' && i + 1 < args.length) {
|
|
386
|
-
options.ca = args[i + 1];
|
|
387
|
-
i++;
|
|
388
|
-
} else if (args[i] === '--ca-key' && i + 1 < args.length) {
|
|
389
|
-
options.caKey = args[i + 1];
|
|
390
|
-
i++;
|
|
391
|
-
} else if (args[i] === '--debug') {
|
|
392
|
-
options.debug = true;
|
|
393
|
-
} else {
|
|
394
|
-
console.error(`Error: Unknown option "${args[i]}"`);
|
|
395
|
-
console.log('Use --help to see available options');
|
|
396
|
-
process.exit(1);
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
configure({
|
|
401
|
-
level: options.debug ? 'debug' : 'info',
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
const auth = {
|
|
405
|
-
type: options.user ? 'basic' : options.ssl ? 'x509' : 'none'
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
if (options.user) {
|
|
409
|
-
auth.user = { name: options.user, roles: ['admin'] };
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
if (options.ssl) {
|
|
413
|
-
auth.x509 = {
|
|
414
|
-
key: options.caKey || './gateway-ca.key',
|
|
415
|
-
// principalAltName: 'email',
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const server = await GatewayServer.Factory({
|
|
420
|
-
port: options.port,
|
|
421
|
-
host: options.host,
|
|
422
|
-
ssl: options.ssl ? {
|
|
423
|
-
key: options.key,
|
|
424
|
-
cert: options.cert,
|
|
425
|
-
ca: options.ca,
|
|
426
|
-
requestCert: auth.type === 'x509',
|
|
427
|
-
rejectUnauthorized: false
|
|
428
|
-
} : undefined,
|
|
429
|
-
auth,
|
|
430
|
-
gateway: { route: '/gw', authorize: { access: auth.type !== 'none' ? 'authenticated' : 'permitted' } },
|
|
431
|
-
app: async (_configurer) => {
|
|
432
|
-
// No custom app logic for now
|
|
433
|
-
},
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
process.on('SIGINT', async () => {
|
|
437
|
-
await server.close();
|
|
438
|
-
process.exit(0);
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
process.title = `${GatewayServer.VERSION} ${server.address.port}`;
|
|
442
|
-
}
|