oox 0.3.4 → 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.
@@ -1,10 +1,11 @@
1
1
  import * as http from 'node:http';
2
2
  import * as https from 'node:https';
3
- import { httpRequest, parseHTTPBody } from './utils.js';
3
+ import { parseHTTPBody } from './utils.js';
4
4
  import Router from './router.js';
5
5
  import * as oox from '../../index.js';
6
- import Module, { ModuleConfig } from '../module.js';
7
- export class HTTPConfig extends ModuleConfig {
6
+ import Module from '../module.js';
7
+ export class HTTPConfig {
8
+ enabled = true;
8
9
  // listen port
9
10
  port = 0;
10
11
  // service path
@@ -80,9 +81,9 @@ export default class HTTPModule extends Module {
80
81
  if (ssl.enabled) {
81
82
  const fs = await import('node:fs');
82
83
  const options = {
83
- key: ssl.key ? fs.readFileSync(ssl.key) : null,
84
- cert: ssl.cert ? fs.readFileSync(ssl.cert) : null,
85
- ca: ssl.ca ? fs.readFileSync(ssl.ca) : null
84
+ key: ssl.key ? fs.readFileSync(ssl.key) : undefined,
85
+ cert: ssl.cert ? fs.readFileSync(ssl.cert) : undefined,
86
+ ca: ssl.ca ? fs.readFileSync(ssl.ca) : undefined
86
87
  };
87
88
  if (!options.key || !options.cert) {
88
89
  throw new Error('HTTPS enabled but missing key or cert');
@@ -101,16 +102,18 @@ export default class HTTPModule extends Module {
101
102
  /**
102
103
  * stop http service
103
104
  */
104
- stop() {
105
- if (this.server && this.server.listening)
106
- return new Promise((resolve, reject) => {
107
- this.server.close(function (error) {
108
- if (error)
109
- reject(error);
110
- else
111
- resolve();
112
- });
105
+ async stop() {
106
+ const { server } = this;
107
+ if (!server || !server.listening)
108
+ return Promise.resolve();
109
+ return new Promise((resolve, reject) => {
110
+ server.close(function (error) {
111
+ if (error)
112
+ reject(error);
113
+ else
114
+ resolve();
113
115
  });
116
+ });
114
117
  }
115
118
  /**
116
119
  * browser cross origin
@@ -120,9 +123,14 @@ export default class HTTPModule extends Module {
120
123
  const origin = this.config.origin;
121
124
  const requestOrigin = request.headers.origin;
122
125
  if (origin && requestOrigin) {
123
- if (origin === '*' ||
124
- origin === requestOrigin ||
125
- (Array.isArray(origin) && origin.includes(requestOrigin))) {
126
+ let allow = false;
127
+ if ('string' === typeof origin) {
128
+ allow = origin === '*' || new RegExp(origin).test(requestOrigin);
129
+ }
130
+ else if (Array.isArray(origin)) {
131
+ allow = origin.some(item => item === '*' || new RegExp(item).test(requestOrigin));
132
+ }
133
+ if (allow) {
126
134
  response.setHeader('Access-Control-Allow-Origin', requestOrigin);
127
135
  response.setHeader('Vary', 'Origin');
128
136
  }
@@ -132,7 +140,7 @@ export default class HTTPModule extends Module {
132
140
  return false;
133
141
  }
134
142
  response.setHeader('Access-Control-Max-Age', 3600);
135
- response.setHeader('Access-Control-Allow-Headers', 'x-caller,content-type');
143
+ response.setHeader('Access-Control-Allow-Headers', 'x-caller,x-token,content-type');
136
144
  response.setHeader('Access-Control-Allow-Methods', '*');
137
145
  }
138
146
  if (request.method === 'OPTIONS') {
@@ -143,9 +151,7 @@ export default class HTTPModule extends Module {
143
151
  return true;
144
152
  }
145
153
  async requestHandler(request, response) {
146
- if (!this.cors(request, response))
147
- return;
148
- const url = new URL(request.url, `http://${request.headers.host || 'localhost'}`);
154
+ const url = new URL(request.url || '', `http://${request.headers.host || 'localhost'}`);
149
155
  const method = request.method || 'GET';
150
156
  // 尝试匹配路由
151
157
  const matched = this.router.match(method, url.pathname);
@@ -170,6 +176,9 @@ export default class HTTPModule extends Module {
170
176
  await this.router.handleRoute(matched.route, matched.params, req, res);
171
177
  return;
172
178
  }
179
+ // 内置cors仅处理RPC请求
180
+ if (!this.cors(request, response))
181
+ return;
173
182
  // 回退到RPC调用
174
183
  if (url.pathname === this.config.path) {
175
184
  await this.call(request, response);
@@ -199,6 +208,24 @@ export default class HTTPModule extends Module {
199
208
  * HTTP-RPC服务器请求监听方法
200
209
  */
201
210
  async call(request, response) {
211
+ // global unique id
212
+ const traceId = String(request.headers['x-trace-id'] || '');
213
+ // service name, required
214
+ const caller = String(request.headers['x-caller'] || 'anonymous');
215
+ // client ip or caller service ip
216
+ const ip = String(request.headers['x-real-ip'] || request.socket.remoteAddress || '');
217
+ // startup client ip
218
+ const sourceIP = String(request.headers['x-source-ip'] || request.socket.remoteAddress || '');
219
+ // check token
220
+ const token = String(request.headers['x-token'] || '');
221
+ const { allow, reason } = oox.checkAllow(ip, caller, token);
222
+ if (!allow)
223
+ return this.respond(request, response, {
224
+ success: false,
225
+ error: {
226
+ message: reason
227
+ }
228
+ });
202
229
  let callArgs = Object.create(null);
203
230
  try {
204
231
  callArgs = await this.getCallArgsFromRequest(request);
@@ -212,14 +239,6 @@ export default class HTTPModule extends Module {
212
239
  }
213
240
  });
214
241
  }
215
- // global unique id
216
- const traceId = String(request.headers['x-trace-id'] || '');
217
- // service name, required
218
- const caller = String(request.headers['x-caller'] || 'anonymous');
219
- // client ip or caller service ip
220
- const ip = String(request.headers['x-ip'] || request.socket.remoteAddress || '');
221
- // startup client ip
222
- const sourceIP = String(request.headers['x-real-ip'] || '');
223
242
  const { action, params = [] } = callArgs;
224
243
  const context = oox.genContext({ traceId, caller, sourceIP, ip, callerId: '' });
225
244
  const format = await oox.call(action, params, context);
@@ -237,15 +256,15 @@ export default class HTTPModule extends Module {
237
256
  try {
238
257
  returnsString = JSON.stringify(returns);
239
258
  }
240
- catch ({ message, stack }) {
259
+ catch (error) {
241
260
  delete returns.body;
242
261
  returns.success = false;
243
262
  returns.error = {
244
- message
263
+ message: error.message
245
264
  };
246
265
  if (oox.config.errorStack) {
247
266
  // 返回错误调用栈信息
248
- returns.error.stack = stack;
267
+ returns.error.stack = error.stack;
249
268
  }
250
269
  returnsString = JSON.stringify(returns);
251
270
  }
@@ -253,34 +272,4 @@ export default class HTTPModule extends Module {
253
272
  response.setHeader('Content-Length', Buffer.byteLength(returnsString));
254
273
  response.end(returnsString);
255
274
  }
256
- /**
257
- * HTTP RPC
258
- */
259
- async rpc(url, action, params, context) {
260
- if (!context || !context.traceId) {
261
- context = oox.getContext();
262
- }
263
- const { traceId, caller, sourceIP } = context;
264
- const headers = {
265
- 'Content-Type': 'application/json',
266
- 'x-trace-id': String(traceId),
267
- };
268
- if (caller)
269
- headers['x-caller'] = String(caller);
270
- if (sourceIP)
271
- headers['x-real-ip'] = sourceIP;
272
- // headers [ 'x-ip' ] = getIPAddress ( 4 ) [ 0 ]
273
- const format = await httpRequest(url, {
274
- headers
275
- }, JSON.stringify({ action, params }));
276
- if ('string' === typeof format)
277
- throw new Error(format);
278
- const { success, error, body } = format;
279
- if (success)
280
- return body;
281
- else if (error)
282
- throw new Error(error.message);
283
- else
284
- throw new Error('[RPC] Unknown Error');
285
- }
286
275
  }
@@ -84,12 +84,17 @@ export default class Router {
84
84
  }
85
85
  // 添加路由
86
86
  addRoute(method, path, handler, middleware = []) {
87
- if (!this.routes.has(method)) {
88
- this.routes.set(method, []);
89
- }
90
87
  // 编译路径为正则表达式,支持路径参数
91
88
  const { regex, paramNames } = this.compilePath(path);
92
- this.routes.get(method).push({
89
+ let routes = [];
90
+ if (this.routes.has(method)) {
91
+ routes = this.routes.get(method) || [];
92
+ }
93
+ else {
94
+ routes = [];
95
+ this.routes.set(method, routes);
96
+ }
97
+ routes.push({
93
98
  path,
94
99
  method,
95
100
  handler,
package/modules/index.js CHANGED
@@ -1,86 +1,90 @@
1
- import Module from './module.js';
2
1
  import HTTP from './http/index.js';
3
2
  import SocketIO from './socketio/index.js';
4
- export default class Modules extends Module {
5
- /**
6
- * the module unique name
7
- */
8
- name = 'oox:modules';
9
- /**
10
- * FIFO queue for modules starting
11
- */
12
- #queue = [];
13
- /**
14
- * all modules map
15
- */
16
- #map = new Map();
17
- /**
18
- * all builtin modules
19
- */
20
- builtins = {
21
- http: new HTTP,
22
- socketio: new SocketIO,
23
- };
24
- constructor() {
25
- super();
26
- this.add(this.builtins.http);
27
- this.add(this.builtins.socketio);
28
- }
29
- add(module) {
30
- if (!module.name || 'string' !== typeof module.name)
31
- throw new Error(`Module<${module.name}> has noname`);
32
- if (this.#map.has(module.name))
33
- throw new Error(`Module<${module.name}> has exists`);
34
- this.#map.set(module.name, module);
35
- this.#queue.push(module);
36
- return this;
37
- }
38
- get(name) {
39
- return this.#map.get(name);
40
- }
41
- async remove(name) {
42
- const module = this.get(name);
43
- if (!module)
44
- return;
45
- await module.stop();
46
- const index = this.#queue.indexOf(module);
47
- this.#queue.splice(index, 1);
48
- this.#map.delete(name);
49
- }
50
- setConfig(config) {
51
- for (const module of this.#queue) {
52
- if (module.name in config) {
53
- const moduleConfig = config[module.name];
54
- if ('boolean' === typeof moduleConfig) {
55
- // eg: http=yes/no
56
- module.setConfig({ enabled: moduleConfig });
57
- }
58
- else {
59
- module.setConfig(moduleConfig);
60
- }
3
+ /**
4
+ * FIFO queue for modules starting
5
+ */
6
+ export const queue = [];
7
+ /**
8
+ * all modules map
9
+ */
10
+ export const map = new Map();
11
+ /**
12
+ * add module to modules queue
13
+ */
14
+ export function add(module) {
15
+ if (!module.name || 'string' !== typeof module.name)
16
+ throw new Error(`Module<${module.name}> has noname`);
17
+ if (map.has(module.name))
18
+ throw new Error(`Module<${module.name}> has exists`);
19
+ map.set(module.name, module);
20
+ queue.push(module);
21
+ }
22
+ /**
23
+ * get module by name
24
+ */
25
+ export function get(name) {
26
+ return map.get(name) || null;
27
+ }
28
+ export async function remove(name) {
29
+ const module = map.get(name);
30
+ if (!module)
31
+ return;
32
+ await module.stop();
33
+ const index = queue.indexOf(module);
34
+ queue.splice(index, 1);
35
+ map.delete(name);
36
+ }
37
+ /**
38
+ * set modules config
39
+ */
40
+ export function setConfig(config) {
41
+ for (const module of queue) {
42
+ if (module.name in config) {
43
+ const moduleConfig = config[module.name];
44
+ if ('boolean' === typeof moduleConfig) {
45
+ // eg: http=yes/no
46
+ module.setConfig({ enabled: moduleConfig });
61
47
  }
62
48
  else {
63
- module.setConfig({ enabled: true });
49
+ module.setConfig(moduleConfig);
64
50
  }
65
- config[module.name] = module.getConfig();
66
51
  }
52
+ else {
53
+ module.setConfig({ enabled: true });
54
+ }
55
+ config[module.name] = module.getConfig();
67
56
  }
68
- async serve() {
69
- try {
70
- for (const module of this.#queue) {
71
- if (module.getConfig().enabled) {
72
- await module.serve();
73
- }
57
+ }
58
+ /**
59
+ * serve modules
60
+ */
61
+ export async function serve() {
62
+ try {
63
+ for (const module of queue) {
64
+ if (module.getConfig().enabled) {
65
+ await module.serve();
74
66
  }
75
67
  }
76
- catch (error) {
77
- await this.stop();
78
- throw error;
79
- }
80
68
  }
81
- async stop() {
82
- for (const module of this.#queue) {
83
- await module.stop();
84
- }
69
+ catch (error) {
70
+ await stop();
71
+ throw error;
72
+ }
73
+ }
74
+ /**
75
+ * stop all modules
76
+ */
77
+ export async function stop() {
78
+ for (const module of queue) {
79
+ await module.stop();
85
80
  }
86
81
  }
82
+ /**
83
+ * all builtin modules
84
+ */
85
+ export const builtins = {
86
+ http: new HTTP,
87
+ socketio: new SocketIO,
88
+ };
89
+ add(builtins.http);
90
+ add(builtins.socketio);
package/modules/module.js CHANGED
@@ -1,11 +1,2 @@
1
- export class ModuleConfig {
2
- enabled = true;
3
- }
4
1
  export default class Module {
5
- config;
6
- name;
7
- setConfig(config) { }
8
- getConfig() { return this.config; }
9
- async serve() { }
10
- async stop() { }
11
2
  }
@@ -7,31 +7,43 @@ export default class SocketIOAdapter extends SampleKeepAliveConnectionAdapter {
7
7
  name = 'socketio';
8
8
  OOXEvent = OOXEvent;
9
9
  nativeEvent = { CONNECT: 'connect', DISCONNECT: 'disconnect', ERROR: 'connect_error' };
10
- newConnection(url) {
11
- const { id, host, name } = oox.config;
10
+ newConnection(identify) {
11
+ const { id, name } = oox.config;
12
12
  const headers = {
13
- 'x-ip': host,
14
13
  'x-caller': name,
15
14
  'x-caller-id': id,
16
15
  };
17
16
  let mURL;
18
- if ('string' === typeof url) {
19
- mURL = genWebSocketURL(url);
17
+ const connectionData = {
18
+ name: 'anonymous',
19
+ id: randomUUID(),
20
+ adapter: this.name,
21
+ ip: '',
22
+ token: ''
23
+ };
24
+ if ('string' === typeof identify) {
25
+ mURL = genWebSocketURL(identify);
26
+ }
27
+ else if (identify instanceof URL) {
28
+ mURL = identify;
29
+ }
30
+ else if (identify.url) {
31
+ // KeepAliveConnectionData
32
+ Object.assign(connectionData, identify);
33
+ mURL = new URL(identify.url);
34
+ if (identify.token) {
35
+ headers['x-token'] = identify.token;
36
+ }
20
37
  }
21
38
  else {
22
- mURL = url;
39
+ throw new Error('identify must be string, URL, or KeepAliveConnectionData');
23
40
  }
24
41
  const socket = SocketIOClient.io(mURL.origin, {
25
42
  extraHeaders: headers,
26
43
  path: mURL.pathname,
27
44
  autoConnect: false,
28
45
  });
29
- const connection = new oox.KeepAliveConnection(this, socket, {
30
- name: 'anonymous',
31
- id: randomUUID(),
32
- host: mURL.host,
33
- url: mURL
34
- });
46
+ const connection = new oox.KeepAliveConnection(this, socket, connectionData);
35
47
  return connection;
36
48
  }
37
49
  }
@@ -2,10 +2,11 @@ import * as http from 'node:http';
2
2
  import * as https from 'node:https';
3
3
  import { Server } from 'socket.io';
4
4
  import * as oox from '../../index.js';
5
- import { Module, ModuleConfig } from '../../index.js';
5
+ import { Module } from '../../index.js';
6
6
  import SocketIOAdapter from './adapter.js';
7
7
  import { OOXEvent } from './utils.js';
8
- export class SocketIOConfig extends ModuleConfig {
8
+ export class SocketIOConfig {
9
+ enabled = true;
9
10
  // listen port
10
11
  port = 0;
11
12
  // service path
@@ -34,6 +35,10 @@ export default class SocketIOServer extends Module {
34
35
  server = null;
35
36
  socketServer = null;
36
37
  adapter = new SocketIOAdapter();
38
+ constructor() {
39
+ super();
40
+ oox.keepAliveConnectionAdapters.set(this.name, this.adapter);
41
+ }
37
42
  getURL() {
38
43
  const { host } = oox.config;
39
44
  const { port, path } = this.config;
@@ -53,21 +58,22 @@ export default class SocketIOServer extends Module {
53
58
  return this.config;
54
59
  }
55
60
  async serve() {
56
- oox.keepAliveConnectionAdapters.set(this.name, this.adapter);
57
61
  await this.stop();
58
62
  const { port, ssl } = this.config;
59
63
  const isSelfServer = this.#isSelfServer = this.server ? true : false;
60
- let server;
64
+ let server = null;
61
65
  if (isSelfServer) {
66
+ if (!this.server)
67
+ throw new Error('HTTP Server is not created');
62
68
  server = this.server;
63
69
  }
64
70
  else {
65
71
  if (ssl.enabled) {
66
72
  const fs = await import('node:fs');
67
73
  const options = {
68
- key: ssl.key ? fs.readFileSync(ssl.key) : null,
69
- cert: ssl.cert ? fs.readFileSync(ssl.cert) : null,
70
- ca: ssl.ca ? fs.readFileSync(ssl.ca) : null
74
+ key: ssl.key ? fs.readFileSync(ssl.key) : undefined,
75
+ cert: ssl.cert ? fs.readFileSync(ssl.cert) : undefined,
76
+ ca: ssl.ca ? fs.readFileSync(ssl.ca) : undefined
71
77
  };
72
78
  if (!options.key || !options.cert) {
73
79
  throw new Error('HTTPS enabled but missing key or cert');
@@ -88,10 +94,11 @@ export default class SocketIOServer extends Module {
88
94
  this.createSocketIOServer();
89
95
  }
90
96
  async stop() {
91
- if (this.socketServer)
92
- await new Promise((resolve, reject) => this.socketServer.close(error => error ? reject(error) : resolve()));
97
+ const { server, socketServer } = this;
98
+ if (socketServer)
99
+ await new Promise((resolve, reject) => socketServer.close(error => error ? reject(error) : resolve()));
93
100
  if (this.#isSelfServer)
94
- await new Promise((resolve, reject) => this.server.listening && this.server.close(error => error ? reject(error) : resolve()));
101
+ await new Promise((resolve, reject) => server?.close(error => error ? reject(error) : resolve()));
95
102
  }
96
103
  genSocketIOServerOptions() {
97
104
  const options = {
@@ -127,7 +134,10 @@ export default class SocketIOServer extends Module {
127
134
  return options;
128
135
  }
129
136
  createSocketIOServer() {
130
- const socketServer = this.socketServer = new Server(this.server, this.genSocketIOServerOptions());
137
+ const { server } = this;
138
+ if (!server)
139
+ throw new Error('HTTP Server is not created');
140
+ const socketServer = this.socketServer = new Server(server, this.genSocketIOServerOptions());
131
141
  socketServer.on('connection', async (socket) => {
132
142
  try {
133
143
  this.serverOnSocketConnection(socket);
@@ -145,22 +155,30 @@ export default class SocketIOServer extends Module {
145
155
  const headers = socket.handshake.headers;
146
156
  const callerId = String(headers['x-caller-id'] || '') || socket.id;
147
157
  // client ip or caller service ip
148
- const ip = String(headers['x-real-ip'] || headers['x-ip'] || socket.handshake.address);
158
+ const ip = String(headers['x-real-ip'] || socket.handshake.address);
149
159
  // service name
150
160
  const caller = String(headers['x-caller'] || 'anonymous');
161
+ const token = String(headers['x-token'] || '');
162
+ // check token
163
+ const { allow, reason } = oox.checkAllow(ip, caller, token);
164
+ if (!allow) {
165
+ socket.send(reason).disconnect(true);
166
+ return;
167
+ }
151
168
  const connection = new oox.KeepAliveConnection(this.adapter, socket, {
152
- host: ip,
169
+ ip,
153
170
  name: caller,
154
171
  id: callerId,
172
+ token,
155
173
  });
156
174
  oox.addKeepAliveConnection(connection);
157
175
  this.bindServerConnectionEvents(connection);
158
176
  this.adapter.bindCall(connection);
159
- connection.enabled = true;
160
177
  socket.emit(OOXEvent.READY, {
161
178
  id: oox.config.id,
162
179
  name: oox.config.name
163
180
  });
181
+ connection.enabled = true;
164
182
  }
165
183
  /**
166
184
  * 绑定服务器连接事件
@@ -168,7 +186,7 @@ export default class SocketIOServer extends Module {
168
186
  */
169
187
  bindServerConnectionEvents(connection) {
170
188
  const socket = connection.nativeConnection;
171
- socket.on(OOXEvent.SYNC_CONNECTIONS, async (fn) => {
189
+ socket.on(OOXEvent.REGISTRY_SYNC_CONNECTIONS, async (fn) => {
172
190
  if ('function' !== typeof fn)
173
191
  return;
174
192
  oox.registry.onSyncConnections(connection, {}, fn);
@@ -3,7 +3,9 @@ export var OOXEvent;
3
3
  OOXEvent["READY"] = "oox:ready";
4
4
  OOXEvent["ENABLED"] = "oox:enabled";
5
5
  OOXEvent["DISABLED"] = "oox:disabled";
6
- OOXEvent["SYNC_CONNECTIONS"] = "oox:sync_connections";
6
+ OOXEvent["REGISTRY_SYNC_CONNECTIONS"] = "oox:registry:sync_connections";
7
+ OOXEvent["REGISTRY_SUBSCRIBE"] = "oox:registry:subscribe";
8
+ OOXEvent["REGISTRY_NOTIFY"] = "oox:registry:notify";
7
9
  })(OOXEvent || (OOXEvent = {}));
8
10
  export function isWebSocketURL(url) {
9
11
  if ('string' === typeof url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oox",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "OOX Service Engine",
5
5
  "keywords": [
6
6
  "http",
@@ -10,7 +10,9 @@
10
10
  "framework",
11
11
  "microservice",
12
12
  "distributed",
13
- "tracing"
13
+ "tracing",
14
+ "service-discovery",
15
+ "load-balancing"
14
16
  ],
15
17
  "type": "module",
16
18
  "main": "./index.js",
@@ -23,14 +25,16 @@
23
25
  },
24
26
  "./loader": "./bin/loader.mjs",
25
27
  "./cli": "./bin/cli.js",
26
- "./samples": "./samples/index.js",
27
- "./samples/types": "./types/samples/index.d.ts"
28
+ "./samples": {
29
+ "types": "./types/samples/index.d.ts",
30
+ "default": "./samples/index.js"
31
+ }
28
32
  },
29
33
  "bin": {
30
34
  "oox": "./bin/cli.js"
31
35
  },
32
36
  "author": "lipingruan",
33
- "homepage": "https://github.com/lipingruan/oox",
37
+ "homepage": "https://gitee.com/lipingruan/oox",
34
38
  "license": "MIT",
35
39
  "dependencies": {
36
40
  "chalk": "^5.6.2",