oox 0.3.3 → 0.3.5
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/README.md +13 -0
- package/app.js +1 -1
- package/bin/cli.js +75 -15
- package/bin/configurer.js +6 -9
- package/bin/register.js +48 -15
- package/bin/starter.js +8 -0
- package/config.js +116 -0
- package/index.js +70 -44
- package/{rpc-keepalive-connection.js → keepalive-connection.js} +36 -16
- package/modules/http/index.js +52 -63
- package/modules/http/router.js +9 -4
- package/modules/index.js +78 -74
- package/modules/module.js +0 -9
- package/modules/socketio/adapter.js +30 -139
- package/modules/socketio/server.js +37 -38
- package/modules/socketio/utils.js +5 -2
- package/package.json +10 -4
- package/registry.js +145 -0
- package/samples/index.js +1 -0
- package/samples/keepalive-connection-sample.js +147 -0
- package/types/app.d.ts +20 -20
- package/types/bin/configurer.d.ts +3 -1
- package/types/bin/register.d.ts +2 -1
- package/types/bin/starter.d.ts +1 -2
- package/types/config.d.ts +54 -0
- package/types/index.d.ts +37 -31
- package/types/keepalive-connection.d.ts +104 -0
- package/types/modules/http/index.d.ts +4 -7
- package/types/modules/index.d.ts +36 -21
- package/types/modules/module.d.ts +9 -9
- package/types/modules/socketio/adapter.d.ts +10 -24
- package/types/modules/socketio/server.d.ts +6 -10
- package/types/modules/socketio/utils.d.ts +5 -2
- package/types/registry.d.ts +41 -0
- package/types/samples/index.d.ts +1 -0
- package/types/samples/keepalive-connection-sample.d.ts +55 -0
- package/utils.js +1 -1
- package/types/rpc-keepalive-connection.d.ts +0 -57
package/README.md
CHANGED
|
@@ -27,3 +27,16 @@ npm install oox
|
|
|
27
27
|
```bash
|
|
28
28
|
npx oox --help
|
|
29
29
|
```
|
|
30
|
+
|
|
31
|
+
### Examples
|
|
32
|
+
- [basic-example](./examples/basic-example)
|
|
33
|
+
- [routing-example](./examples/routing-example)
|
|
34
|
+
- [rpc-example](./examples/rpc-example)
|
|
35
|
+
- [distributed-example](./examples/distributed-example)
|
|
36
|
+
- [load-balancing-example](./examples/load-balancing-example)
|
|
37
|
+
- [tracing-example](./examples/tracing-example)
|
|
38
|
+
- [service-discovery-example](./examples/service-discovery-example)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Docs
|
|
42
|
+
[config](./docs/config.md)
|
package/app.js
CHANGED
|
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
|
|
|
2
2
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
3
3
|
import { genKVMethods } from './utils.js';
|
|
4
4
|
export * as logger from './logger.js';
|
|
5
|
-
export class
|
|
5
|
+
export class AppContext {
|
|
6
6
|
// 请求溯源ID
|
|
7
7
|
traceId = '';
|
|
8
8
|
}
|
package/bin/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ if (execFilename.endsWith('oox') || fileURLToPath(import.meta.url) === execFilen
|
|
|
16
16
|
if (!command || ['help', '-h', '-help', '--help', 'version', '-v', '-version', '--version'].includes(command)) {
|
|
17
17
|
isStartup = false;
|
|
18
18
|
console.log();
|
|
19
|
-
console.log(
|
|
19
|
+
console.log(pkg.description);
|
|
20
20
|
console.log(chalk.bold('version'), chalk.bold.green(`${pkg.version}`));
|
|
21
21
|
console.log(chalk.underline(`${pkg.homepage}`));
|
|
22
22
|
console.log();
|
|
@@ -27,23 +27,56 @@ if (execFilename.endsWith('oox') || fileURLToPath(import.meta.url) === execFilen
|
|
|
27
27
|
console.log();
|
|
28
28
|
console.log(' oox entry.js port=8080');
|
|
29
29
|
console.log();
|
|
30
|
-
console.log(' oox
|
|
30
|
+
console.log(' oox entry.js group=app/ registry=:6000');
|
|
31
|
+
console.log(' oox app/entry/index.js group=app/ env=.env/prod.js ignore=core');
|
|
31
32
|
console.log();
|
|
32
33
|
console.log(chalk.bold('Params:'));
|
|
33
|
-
const
|
|
34
|
-
['
|
|
35
|
-
['
|
|
36
|
-
['
|
|
37
|
-
['
|
|
38
|
-
['
|
|
39
|
-
['
|
|
40
|
-
[' socketio [ json ] ', 'SocketIO server options, support flat name'],
|
|
41
|
-
[' registry [ urls ] ', 'registry service url, support string | array<string>'],
|
|
42
|
-
[' origin [ urls ] ', 'set', chalk.bold('*'), 'allow any connections <Access-Control-Allow-Origin>'],
|
|
43
|
-
[' errorStack [ bool ] ', 'set no to hidden error stack return'],
|
|
44
|
-
[' ... ', 'set params as', chalk.bold('foo=bar') + ',', 'usage as', chalk.bold('oox.config.foo')]
|
|
34
|
+
const params_base = [
|
|
35
|
+
['default-env', 'file', '.js or .json file, merge to oox.config'],
|
|
36
|
+
['env', 'file', `.js or .json file, merge to oox.config, ${chalk.bold('(after default-env)')}`],
|
|
37
|
+
['port', 'int', `default is ${chalk.bold('0')}, for random port, or any integer > 0`],
|
|
38
|
+
['group', 'dir', 'service group directory, all LocalCall transform to RPC'],
|
|
39
|
+
['ignore', 'name', 'set a name for LocalCall do not transform to RPC, support string | array<string>'],
|
|
40
|
+
['errorStack', 'bool', 'set no to hidden error stack return'],
|
|
45
41
|
];
|
|
46
|
-
|
|
42
|
+
const params_registry = [
|
|
43
|
+
['isRegistry', 'bool', `set yes to enable service registry`],
|
|
44
|
+
['registryAdapter', 'name', 'set service registry adapter name'],
|
|
45
|
+
['registryToken', 'token', 'set token for service registry'],
|
|
46
|
+
['registry', 'urls', 'registry service url, support string | array<string>'],
|
|
47
|
+
['registry.url', 'url', 'set service registry url'],
|
|
48
|
+
['registry.token', 'token', 'set service registry token'],
|
|
49
|
+
];
|
|
50
|
+
const params_auth = [
|
|
51
|
+
['token', 'token', 'set token for connection auth'],
|
|
52
|
+
['allow.untrusted', 'bool', `set yes to allow untrusted caller`],
|
|
53
|
+
['allow.ip', 'ip', 'set allow IP, support string | array<string>'],
|
|
54
|
+
['allow.caller', 'name', 'set allow caller, support string | array<string>'],
|
|
55
|
+
['origin', 'urls', `set ${chalk.bold('*')}, allow any connections <Access-Control-Allow-Origin>`],
|
|
56
|
+
];
|
|
57
|
+
const params_modules = [
|
|
58
|
+
['http', 'json/bool', 'HTTP server options, support flat name, ex: http.path=/api, or set no to disable HTTP server'],
|
|
59
|
+
['http.port', 'int server port'],
|
|
60
|
+
['http.path', 'string', 'set HTTP server path'],
|
|
61
|
+
['http.origin', 'urls', 'set HTTP server origin, support string | array<string>'],
|
|
62
|
+
['http.ssl', 'json', 'set yes to enable HTTPS'],
|
|
63
|
+
['http.ssl.cert', 'file', 'set HTTPS certificate path'],
|
|
64
|
+
['http.ssl.key', 'file', 'set HTTPS private key path'],
|
|
65
|
+
['http.ssl.ca', 'file', 'set HTTPS CA path'],
|
|
66
|
+
['socketio', 'json/bool', 'SocketIO server options, support flat name, or set no to disable SocketIO server'],
|
|
67
|
+
['socketio.port', 'int server port'],
|
|
68
|
+
['socketio.path', 'string', 'set SocketIO server path'],
|
|
69
|
+
['socketio.origin', 'urls', 'set SocketIO server origin, support string | array<string>'],
|
|
70
|
+
['socketio.ssl', 'json', 'like http.ssl'],
|
|
71
|
+
];
|
|
72
|
+
console.log(mergeCommandParams(params_base));
|
|
73
|
+
console.log(chalk.bold('Registry:'));
|
|
74
|
+
console.log(mergeCommandParams(params_registry));
|
|
75
|
+
console.log(chalk.bold('Auth:'));
|
|
76
|
+
console.log(mergeCommandParams(params_auth));
|
|
77
|
+
console.log(chalk.bold('Modules:'));
|
|
78
|
+
console.log(mergeCommandParams(params_modules));
|
|
79
|
+
console.log(' ...', 'set params as', chalk.bold('foo=bar') + ',', 'usage as', chalk.bold('oox.config.foo'));
|
|
47
80
|
console.log();
|
|
48
81
|
}
|
|
49
82
|
if (isStartup) {
|
|
@@ -55,3 +88,30 @@ if (execFilename.endsWith('oox') || fileURLToPath(import.meta.url) === execFilen
|
|
|
55
88
|
await startup();
|
|
56
89
|
}
|
|
57
90
|
}
|
|
91
|
+
function mergeCommandParams(params) {
|
|
92
|
+
let result = '';
|
|
93
|
+
const perLinePrefix = ' '.repeat(2);
|
|
94
|
+
const nameMaxLength = 12;
|
|
95
|
+
const typeMaxLength = 6;
|
|
96
|
+
for (const param of params) {
|
|
97
|
+
const name = param[0];
|
|
98
|
+
const type = param[1];
|
|
99
|
+
const desc = param.slice(2).join(' ');
|
|
100
|
+
result += perLinePrefix + name;
|
|
101
|
+
if (name.length > nameMaxLength) {
|
|
102
|
+
result += '\n' + perLinePrefix;
|
|
103
|
+
result += ' '.repeat(nameMaxLength);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
result += ' '.repeat(Math.max(0, nameMaxLength - name.length));
|
|
107
|
+
}
|
|
108
|
+
const typeLeftSpaceLength = Math.max(0, Math.floor((typeMaxLength - type.length) / 2));
|
|
109
|
+
const typeLeftSpaces = ' '.repeat(typeLeftSpaceLength);
|
|
110
|
+
const typeRightSpaces = ' '.repeat(Math.max(0, typeMaxLength - type.length - typeLeftSpaceLength));
|
|
111
|
+
result += '[' + typeLeftSpaces + type + typeRightSpaces + ']';
|
|
112
|
+
result += ' ';
|
|
113
|
+
result += desc;
|
|
114
|
+
result += '\n';
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
package/bin/configurer.js
CHANGED
|
@@ -12,7 +12,11 @@ function mergeFlatEnv(env) {
|
|
|
12
12
|
const valueKey = subKeys.pop() || '';
|
|
13
13
|
let tmpEnv = env;
|
|
14
14
|
for (const subKey of subKeys) {
|
|
15
|
-
if (subKey in tmpEnv) {
|
|
15
|
+
if (subKey in tmpEnv) {
|
|
16
|
+
if ('object' !== typeof tmpEnv[subKey] || null === tmpEnv[subKey]) {
|
|
17
|
+
tmpEnv[subKey] = {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
16
20
|
else {
|
|
17
21
|
tmpEnv[subKey] = {};
|
|
18
22
|
}
|
|
@@ -42,20 +46,13 @@ async function readEnvFile(filePath) {
|
|
|
42
46
|
return env;
|
|
43
47
|
}
|
|
44
48
|
export async function buildConfig(mergeEnv) {
|
|
45
|
-
const env =
|
|
49
|
+
const env = {};
|
|
46
50
|
const defaultEnvPath = argv.getEnvArg('default-env');
|
|
47
51
|
const targetEnvPath = argv.getEnvArg('env');
|
|
48
52
|
const defaultEnv = defaultEnvPath ? await readEnvFile(defaultEnvPath) : {};
|
|
49
53
|
const targetEnv = targetEnvPath ? await readEnvFile(targetEnvPath) : {};
|
|
50
54
|
Object.assign(env, defaultEnv, targetEnv, argv.getAllEnvArgs());
|
|
51
55
|
mergeFlatEnv(env);
|
|
52
|
-
if ('string' === typeof env.ignore)
|
|
53
|
-
env.ignore = env.ignore.split(',');
|
|
54
|
-
if ('string' === typeof env.registry)
|
|
55
|
-
env.registry = env.registry.split(',');
|
|
56
|
-
if ('string' === typeof env.origin && env.origin.includes(',')) {
|
|
57
|
-
env.origin = env.origin.split(',');
|
|
58
|
-
}
|
|
59
56
|
if (mergeEnv)
|
|
60
57
|
Object.assign(env, mergeEnv);
|
|
61
58
|
return env;
|
package/bin/register.js
CHANGED
|
@@ -1,32 +1,65 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import * as oox from '../index.js';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
3
4
|
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
4
|
-
async function connect(
|
|
5
|
-
|
|
5
|
+
async function connect(identify, prevError = null) {
|
|
6
|
+
let url = '';
|
|
7
|
+
let connectionData;
|
|
8
|
+
if (typeof identify === 'string') {
|
|
9
|
+
url = identify;
|
|
10
|
+
connectionData = {
|
|
11
|
+
adapter: oox.config.registryAdapter,
|
|
12
|
+
url,
|
|
13
|
+
ip: '',
|
|
14
|
+
name: 'anonymous',
|
|
15
|
+
id: randomUUID(),
|
|
16
|
+
token: oox.config.registryToken,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
url = String(identify.url || '');
|
|
21
|
+
connectionData = identify;
|
|
22
|
+
}
|
|
23
|
+
if (!connectionData.name)
|
|
24
|
+
connectionData.name = 'anonymous';
|
|
25
|
+
if (!connectionData.id)
|
|
26
|
+
connectionData.id = randomUUID();
|
|
27
|
+
if (!connectionData.token)
|
|
28
|
+
connectionData.token = oox.config.registryToken;
|
|
29
|
+
if (!connectionData.adapter)
|
|
30
|
+
connectionData.adapter = oox.config.registryAdapter;
|
|
31
|
+
const adapter = oox.keepAliveConnectionAdapters.get(connectionData.adapter);
|
|
32
|
+
if (!adapter) {
|
|
33
|
+
console.error(chalk.red('[Registry]'), `Registry Adapter ${chalk.bold(connectionData.adapter)} is not found`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!url) {
|
|
37
|
+
console.error(chalk.red('[Registry]'), `Registry URL ${chalk.bold(url)} is empty`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
6
40
|
try {
|
|
7
|
-
const connection = await adapter.open(
|
|
8
|
-
|
|
41
|
+
const connection = await adapter.open(connectionData);
|
|
42
|
+
connection.isRegistry = true;
|
|
43
|
+
onConnection(connection);
|
|
9
44
|
}
|
|
10
45
|
catch (error) {
|
|
11
46
|
if (!prevError)
|
|
12
|
-
console.
|
|
47
|
+
console.error(chalk.red('[Registry]'), chalk.underline.red(`${url}`), 'error.');
|
|
13
48
|
await delay(2000);
|
|
14
|
-
connect(
|
|
49
|
+
connect(identify, error);
|
|
15
50
|
}
|
|
16
51
|
}
|
|
17
|
-
async function onConnection(connection
|
|
52
|
+
async function onConnection(connection) {
|
|
18
53
|
const { name } = connection.data;
|
|
19
54
|
connection.once('disconnect', async () => {
|
|
20
|
-
console.
|
|
55
|
+
console.error(chalk.red('[Registry]'), `Service<${name}>`, chalk.underline.red(`${connection.data.url || name}`), 'disconnected.');
|
|
21
56
|
await delay(1000);
|
|
22
|
-
connect(
|
|
57
|
+
connect(connection.data);
|
|
23
58
|
});
|
|
24
|
-
console.
|
|
59
|
+
console.info(chalk.green('[Registry]'), `Service<${name}>`, chalk.underline.green(`${connection.data.url || name}`), 'connected.');
|
|
25
60
|
}
|
|
26
|
-
export async function registry(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
for (const url of urls) {
|
|
30
|
-
connect(url);
|
|
61
|
+
export async function registry(identifies) {
|
|
62
|
+
for (const identify of identifies) {
|
|
63
|
+
connect(identify);
|
|
31
64
|
}
|
|
32
65
|
}
|
package/bin/starter.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as path from 'node:path';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import { formatters as configFormatters } from '../config.js';
|
|
3
4
|
import * as oox from '../index.js';
|
|
4
5
|
import { buildConfig } from './configurer.js';
|
|
5
6
|
import { registry } from './register.js';
|
|
@@ -17,6 +18,8 @@ function getEntryInfo(env, entryFilename) {
|
|
|
17
18
|
const directory = fullDirectory.split(path.sep).pop();
|
|
18
19
|
const groupFullDirectory = env.group ? path.resolve(env.group) : '';
|
|
19
20
|
const entryMatch = filename.match(entryMatchRegExp);
|
|
21
|
+
if (!entryMatch)
|
|
22
|
+
throw new Error('Invalid entry file name: ' + entryFilename);
|
|
20
23
|
const entryFilenameWithoutExtension = entryMatch[1];
|
|
21
24
|
const name = entryFilenameWithoutExtension === 'index' && groupFullDirectory !== fullDirectory ? directory : entryFilenameWithoutExtension;
|
|
22
25
|
return { name, path: fullPath, group: groupFullDirectory };
|
|
@@ -37,6 +40,11 @@ export async function configure(env, entryFilename) {
|
|
|
37
40
|
path: entryInfo.path.replace(/\\/g, '/'),
|
|
38
41
|
group: entryInfo.group.replace(/\\/g, '/'),
|
|
39
42
|
};
|
|
43
|
+
for (const key in oox.config) {
|
|
44
|
+
if (key in configFormatters) {
|
|
45
|
+
oox.config[key] = configFormatters[key](oox.config[key]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
40
48
|
// 模块配置
|
|
41
49
|
oox.modules.setConfig(oox.config);
|
|
42
50
|
oox.emit('app:configured');
|
package/config.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { getIPAddress } from "./utils.js";
|
|
3
|
+
export class Config {
|
|
4
|
+
// 服务ID
|
|
5
|
+
id = randomUUID();
|
|
6
|
+
// 服务名称
|
|
7
|
+
name = 'local';
|
|
8
|
+
// 服务列表路径
|
|
9
|
+
group = '';
|
|
10
|
+
// 非服务模块
|
|
11
|
+
ignore = [];
|
|
12
|
+
// 启动文件
|
|
13
|
+
entryInfo = {
|
|
14
|
+
// 启动文件路径
|
|
15
|
+
path: '',
|
|
16
|
+
// 服务列表路径
|
|
17
|
+
group: '',
|
|
18
|
+
};
|
|
19
|
+
// 主机地址
|
|
20
|
+
host = getIPAddress(4)[0];
|
|
21
|
+
// 默认监听端口
|
|
22
|
+
port = 0;
|
|
23
|
+
// 默认跨域设置
|
|
24
|
+
origin = '';
|
|
25
|
+
// 默认返回错误调用栈信息信息
|
|
26
|
+
errorStack = true;
|
|
27
|
+
// 是否开启服务注册功能
|
|
28
|
+
isRegistry = false;
|
|
29
|
+
// 服务注册列表
|
|
30
|
+
registry = [];
|
|
31
|
+
// 注册服务令牌
|
|
32
|
+
registryToken = '';
|
|
33
|
+
// 服务注册适配器
|
|
34
|
+
registryAdapter = 'socketio';
|
|
35
|
+
// 当前服务的令牌,验证成功则忽略allow配置
|
|
36
|
+
token = randomUUID();
|
|
37
|
+
allow = {
|
|
38
|
+
// 是否允许未验证令牌连接
|
|
39
|
+
untrusted: false,
|
|
40
|
+
// 是否允许指定IP连接
|
|
41
|
+
ip: [],
|
|
42
|
+
// 是否允许指定名称连接
|
|
43
|
+
caller: [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export const formatters = {
|
|
47
|
+
id: function (value) {
|
|
48
|
+
return String(value || randomUUID());
|
|
49
|
+
},
|
|
50
|
+
name: function (value) {
|
|
51
|
+
return String(value || 'local');
|
|
52
|
+
},
|
|
53
|
+
group: function (value) {
|
|
54
|
+
return String(value || '');
|
|
55
|
+
},
|
|
56
|
+
ignore: function (value) {
|
|
57
|
+
if (Array.isArray(value))
|
|
58
|
+
return value;
|
|
59
|
+
if (typeof value === 'string')
|
|
60
|
+
return value.split(',');
|
|
61
|
+
return [];
|
|
62
|
+
},
|
|
63
|
+
entryInfo: function (value) {
|
|
64
|
+
return value;
|
|
65
|
+
},
|
|
66
|
+
host: function (value) {
|
|
67
|
+
return String(value || getIPAddress(4)[0]);
|
|
68
|
+
},
|
|
69
|
+
port: function (value) {
|
|
70
|
+
return Number(value) || 0;
|
|
71
|
+
},
|
|
72
|
+
origin: function (value) {
|
|
73
|
+
if (value === '*')
|
|
74
|
+
return value;
|
|
75
|
+
if (Array.isArray(value))
|
|
76
|
+
return value;
|
|
77
|
+
if (typeof value === 'string')
|
|
78
|
+
return value.split(',');
|
|
79
|
+
return '';
|
|
80
|
+
},
|
|
81
|
+
errorStack: function (value) {
|
|
82
|
+
return Boolean(value);
|
|
83
|
+
},
|
|
84
|
+
isRegistry: function (value) {
|
|
85
|
+
return Boolean(value);
|
|
86
|
+
},
|
|
87
|
+
registry: function (value) {
|
|
88
|
+
if ('string' === typeof value)
|
|
89
|
+
value = value.split(',');
|
|
90
|
+
if (value && !Array.isArray(value))
|
|
91
|
+
value = [value];
|
|
92
|
+
return value;
|
|
93
|
+
},
|
|
94
|
+
registryToken: function (value) {
|
|
95
|
+
return String(value || '');
|
|
96
|
+
},
|
|
97
|
+
registryAdapter: function (value) {
|
|
98
|
+
return String(value || '');
|
|
99
|
+
},
|
|
100
|
+
token: function (value) {
|
|
101
|
+
return String(value || '');
|
|
102
|
+
},
|
|
103
|
+
allow: function (value) {
|
|
104
|
+
value = value || {
|
|
105
|
+
untrusted: false,
|
|
106
|
+
ip: [],
|
|
107
|
+
caller: [],
|
|
108
|
+
};
|
|
109
|
+
const { ip, caller } = value;
|
|
110
|
+
if ('string' === typeof ip)
|
|
111
|
+
value.ip = ip.split(',');
|
|
112
|
+
if ('string' === typeof caller)
|
|
113
|
+
value.caller = caller.split(',');
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
};
|
package/index.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
export
|
|
8
|
-
export { Module, ModuleConfig };
|
|
9
|
-
export const modules = new Modules;
|
|
10
|
-
export const { asyncStore, setMethods, getMethods, kvMethods, sourceKVMethods, call, execute, logger, on, once, off, emit, } = app;
|
|
11
|
-
export class Context extends app.Context {
|
|
2
|
+
import { asyncStore, AppContext, } from './app.js';
|
|
3
|
+
import * as registry from './registry.js';
|
|
4
|
+
import { Config } from './config.js';
|
|
5
|
+
import Module from './modules/module.js';
|
|
6
|
+
import { KeepAliveConnection, keepAliveConnections, enabledKeepAliveConnections, KeepAliveConnectionError, } from './keepalive-connection.js';
|
|
7
|
+
export class Context extends AppContext {
|
|
12
8
|
// 请求溯源IP
|
|
13
9
|
sourceIP = '';
|
|
14
10
|
// 请求者IP
|
|
@@ -19,41 +15,21 @@ export class Context extends app.Context {
|
|
|
19
15
|
callerId = '';
|
|
20
16
|
// 请求者连接把柄
|
|
21
17
|
connection;
|
|
18
|
+
constructor(context) {
|
|
19
|
+
super();
|
|
20
|
+
if (context) {
|
|
21
|
+
if ('toJSON' in context && typeof context.toJSON !== 'function') {
|
|
22
|
+
throw new Error('Context.toJSON must be a function');
|
|
23
|
+
}
|
|
24
|
+
Object.assign(this, context);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
22
27
|
toJSON() {
|
|
23
28
|
const context = Object.assign({}, this);
|
|
24
29
|
delete context.connection;
|
|
25
30
|
return context;
|
|
26
31
|
}
|
|
27
32
|
}
|
|
28
|
-
export class Config {
|
|
29
|
-
// 服务ID
|
|
30
|
-
id = randomUUID();
|
|
31
|
-
// 服务名称
|
|
32
|
-
name = 'local';
|
|
33
|
-
// 服务列表路径
|
|
34
|
-
group = '';
|
|
35
|
-
// 非服务模块
|
|
36
|
-
ignore = [];
|
|
37
|
-
// 启动文件
|
|
38
|
-
entryInfo = {
|
|
39
|
-
// 启动文件路径
|
|
40
|
-
path: '',
|
|
41
|
-
// 服务列表路径
|
|
42
|
-
group: '',
|
|
43
|
-
};
|
|
44
|
-
// 主机地址
|
|
45
|
-
host = getIPAddress(4)[0];
|
|
46
|
-
// 默认监听端口
|
|
47
|
-
port = 0;
|
|
48
|
-
// 默认跨域设置
|
|
49
|
-
origin = '';
|
|
50
|
-
// 默认返回错误调用栈信息信息
|
|
51
|
-
errorStack = true;
|
|
52
|
-
// 服务注册列表
|
|
53
|
-
registry = [];
|
|
54
|
-
// 服务注册适配器
|
|
55
|
-
registryAdapter = 'socketio';
|
|
56
|
-
}
|
|
57
33
|
export const config = new Config();
|
|
58
34
|
let genTraceIdFunction = () => randomUUID();
|
|
59
35
|
export function setGenTraceIdFunction(fn) {
|
|
@@ -69,7 +45,7 @@ export function genTraceId() {
|
|
|
69
45
|
* 获取链路跟踪上下文
|
|
70
46
|
*/
|
|
71
47
|
export function genContext(context) {
|
|
72
|
-
const newContext =
|
|
48
|
+
const newContext = new Context(context);
|
|
73
49
|
if (!newContext.traceId)
|
|
74
50
|
newContext.traceId = genTraceId();
|
|
75
51
|
if (!newContext.sourceIP)
|
|
@@ -82,19 +58,62 @@ export function getContext() {
|
|
|
82
58
|
}
|
|
83
59
|
export async function serve() {
|
|
84
60
|
await modules.serve();
|
|
61
|
+
registry.startBroadcastTimer();
|
|
85
62
|
}
|
|
86
63
|
export async function stop() {
|
|
87
64
|
await modules.stop();
|
|
65
|
+
registry.stopBroadcastTimer();
|
|
88
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* 检查连接是否被允许
|
|
69
|
+
* @param ip 连接主机 IP
|
|
70
|
+
* @param caller 连接服务名称
|
|
71
|
+
* @param token 令牌
|
|
72
|
+
* @returns 是否被允许
|
|
73
|
+
*/
|
|
74
|
+
export function checkAllow(ip, caller, token) {
|
|
75
|
+
// check trusted token
|
|
76
|
+
const trusted = !config.token || token === config.token;
|
|
77
|
+
if (!trusted) {
|
|
78
|
+
const { allow } = config;
|
|
79
|
+
if (!allow.untrusted) {
|
|
80
|
+
return { allow: false, reason: 'untrusted' };
|
|
81
|
+
}
|
|
82
|
+
if (Array.isArray(allow.ip) && allow.ip.length) {
|
|
83
|
+
let allowed = allow.ip.some(item => item === '*' || new RegExp(item).test(ip));
|
|
84
|
+
if (!allowed)
|
|
85
|
+
return { allow: false, reason: 'not allow ip=' + ip };
|
|
86
|
+
}
|
|
87
|
+
if (Array.isArray(allow.caller) && allow.caller.length) {
|
|
88
|
+
let allowed = allow.caller.some(item => item === '*' || new RegExp(item).test(caller));
|
|
89
|
+
if (!allowed)
|
|
90
|
+
return { allow: false, reason: 'not allow caller=' + caller };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { allow: true };
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 添加长连接
|
|
97
|
+
* @throws {KeepAliveConnectionError} 连接已存在
|
|
98
|
+
* @param connection
|
|
99
|
+
*/
|
|
89
100
|
export function addKeepAliveConnection(connection) {
|
|
101
|
+
if (keepAliveConnections.has(connection.data.name, connection.data.id)) {
|
|
102
|
+
connection.disconnect();
|
|
103
|
+
throw new KeepAliveConnectionError('Connection already exists', connection);
|
|
104
|
+
}
|
|
90
105
|
keepAliveConnections.add(connection);
|
|
91
106
|
}
|
|
92
107
|
export function removeKeepAliveConnection(arg1, arg2) {
|
|
93
108
|
if (typeof arg1 === 'string') {
|
|
109
|
+
const connection = keepAliveConnections.get(arg1, arg2);
|
|
110
|
+
if (connection)
|
|
111
|
+
connection.disconnect();
|
|
94
112
|
keepAliveConnections.remove(arg1, arg2);
|
|
95
113
|
enabledKeepAliveConnections.remove(arg1, arg2);
|
|
96
114
|
}
|
|
97
115
|
else {
|
|
116
|
+
arg1.disconnect();
|
|
98
117
|
keepAliveConnections.remove(arg1);
|
|
99
118
|
enabledKeepAliveConnections.remove(arg1);
|
|
100
119
|
}
|
|
@@ -119,16 +138,23 @@ export async function rpc(arg1, action, params, context) {
|
|
|
119
138
|
if (!context || !context.traceId) {
|
|
120
139
|
context = getContext();
|
|
121
140
|
}
|
|
122
|
-
let connection;
|
|
123
|
-
if (arg1 instanceof
|
|
141
|
+
let connection = null;
|
|
142
|
+
if (arg1 instanceof KeepAliveConnection) {
|
|
124
143
|
connection = arg1;
|
|
125
144
|
}
|
|
126
145
|
else if ('string' === typeof arg1) {
|
|
127
146
|
connection = loadBalancePolicy(arg1);
|
|
128
|
-
if (!connection)
|
|
129
|
-
throw new Error(`Connection<${arg1}> not found`);
|
|
130
147
|
}
|
|
131
148
|
else
|
|
132
149
|
throw new Error(`Unknown rpc arg1<${arg1}>`);
|
|
150
|
+
if (!connection)
|
|
151
|
+
throw new Error(`Connection<${arg1}> not found`);
|
|
133
152
|
return connection.adapter.rpc(connection, action, params, context);
|
|
134
153
|
}
|
|
154
|
+
export * from './app.js';
|
|
155
|
+
export * from './keepalive-connection.js';
|
|
156
|
+
export { Module };
|
|
157
|
+
export { registry };
|
|
158
|
+
// 放在最后导入,避免循环依赖问题
|
|
159
|
+
import * as modules from './modules/index.js';
|
|
160
|
+
export { modules };
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
|
-
export class
|
|
2
|
+
export class KeepAliveConnectionError extends Error {
|
|
3
|
+
connection;
|
|
4
|
+
constructor(message, connection) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.connection = connection;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class KeepAliveConnection extends EventEmitter {
|
|
10
|
+
// 是否为注册服务连接
|
|
11
|
+
isRegistry = false;
|
|
3
12
|
// 连接是否可用
|
|
4
13
|
#enabled = false;
|
|
5
14
|
data;
|
|
@@ -9,8 +18,14 @@ export class RPCKeepAliveConnection extends EventEmitter {
|
|
|
9
18
|
super();
|
|
10
19
|
this.adapter = adapter;
|
|
11
20
|
this.nativeConnection = nativeConnection;
|
|
12
|
-
this.data = data
|
|
21
|
+
this.data = Object.assign(data, {
|
|
22
|
+
adapter: adapter.name,
|
|
23
|
+
});
|
|
13
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* 设置连接是否可用,并自动触发enabled或disabled事件
|
|
27
|
+
* @param enabled 是否可用
|
|
28
|
+
*/
|
|
14
29
|
set enabled(enabled) {
|
|
15
30
|
if (enabled !== this.#enabled) {
|
|
16
31
|
this.#enabled = enabled;
|
|
@@ -26,21 +41,26 @@ export class RPCKeepAliveConnection extends EventEmitter {
|
|
|
26
41
|
get enabled() {
|
|
27
42
|
return this.#enabled;
|
|
28
43
|
}
|
|
29
|
-
|
|
44
|
+
/**
|
|
45
|
+
* enable & mount connection
|
|
46
|
+
* @param params
|
|
47
|
+
* @returns
|
|
48
|
+
*/
|
|
49
|
+
ready(params) {
|
|
50
|
+
// 连接已就绪,不允许重复就绪
|
|
51
|
+
if (this.enabled)
|
|
52
|
+
return;
|
|
53
|
+
const { id, name } = params;
|
|
30
54
|
keepAliveConnections.remove(this);
|
|
31
|
-
|
|
55
|
+
// 更新连接数据
|
|
56
|
+
this.data.id = id;
|
|
57
|
+
this.data.name = name;
|
|
32
58
|
keepAliveConnections.add(this);
|
|
59
|
+
this.enabled = true;
|
|
33
60
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
this.
|
|
37
|
-
keepAliveConnections.add(this);
|
|
38
|
-
}
|
|
39
|
-
updateIdAndName(newId, newName) {
|
|
40
|
-
keepAliveConnections.remove(this);
|
|
41
|
-
this.data.id = newId;
|
|
42
|
-
this.data.name = newName;
|
|
43
|
-
keepAliveConnections.add(this);
|
|
61
|
+
disconnect() {
|
|
62
|
+
this.enabled = false;
|
|
63
|
+
this.nativeConnection.disconnect();
|
|
44
64
|
}
|
|
45
65
|
}
|
|
46
66
|
export class KeepAliveConnectionStore {
|
|
@@ -74,7 +94,7 @@ export class KeepAliveConnectionStore {
|
|
|
74
94
|
}
|
|
75
95
|
}
|
|
76
96
|
remove(name, id) {
|
|
77
|
-
if (name instanceof
|
|
97
|
+
if (name instanceof KeepAliveConnection) {
|
|
78
98
|
id = name.data.id;
|
|
79
99
|
name = name.data.name;
|
|
80
100
|
}
|
|
@@ -84,6 +104,6 @@ export class KeepAliveConnectionStore {
|
|
|
84
104
|
}
|
|
85
105
|
}
|
|
86
106
|
}
|
|
87
|
-
export const
|
|
107
|
+
export const keepAliveConnectionAdapters = new Map();
|
|
88
108
|
export const keepAliveConnections = new KeepAliveConnectionStore();
|
|
89
109
|
export const enabledKeepAliveConnections = new KeepAliveConnectionStore();
|