geonix 1.12.3 → 1.20.1
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/eslint.config.js +25 -0
- package/examples/ServeStatic/index.js +5 -5
- package/examples/ServeStatic/package.json +14 -14
- package/examples/SimpleService/index.js +8 -8
- package/examples/SimpleService/package.json +14 -14
- package/exports.js +14 -43
- package/index.d.ts +29 -24
- package/package.json +10 -6
- package/src/Connection.js +78 -55
- package/src/Gateway.js +211 -207
- package/src/Registry.js +38 -32
- package/src/Remote.js +3 -3
- package/src/Request.js +66 -52
- package/src/RequestOptions.js +4 -4
- package/src/Service.js +128 -121
- package/src/Stream.js +48 -139
- package/src/Util.js +85 -124
- package/src/WebServer.js +68 -65
- package/test/gateway.js +15 -0
- package/test/package.json +16 -0
- package/test/stream.js +38 -0
- package/tsconfig.json +10 -10
- package/src/status/deps/babel.development.js +0 -135978
- package/src/status/deps/babel.min.js +0 -17
- package/src/status/deps/react-dom.development.js +0 -29869
- package/src/status/deps/react-dom.production.min.js +0 -267
- package/src/status/deps/react.development.js +0 -3342
- package/src/status/deps/react.production.min.js +0 -31
- package/src/status/index.html +0 -13
- package/src/status/main.js +0 -30
package/src/Gateway.js
CHANGED
|
@@ -1,250 +1,260 @@
|
|
|
1
|
-
import { connection } from
|
|
2
|
-
import { registry } from
|
|
3
|
-
import { createTCPServer, GeonixVersion, picoid, proxyHttp, sleep } from
|
|
4
|
-
import express, { Router } from
|
|
5
|
-
import { Request } from
|
|
6
|
-
import { HEALTH_CHECK_ENDPOINT } from
|
|
7
|
-
import expressWs from
|
|
8
|
-
import querystring from
|
|
9
|
-
import semver from
|
|
10
|
-
import { WebSocket } from
|
|
11
|
-
|
|
12
|
-
const raw = express.raw({ limit:
|
|
13
|
-
|
|
14
|
-
const DEBUG_ENDPOINT =
|
|
15
|
-
const endpointMatcher = /^((?<options>.+)\|)?(?<verb>WS|GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS|ALL)\s(?<url>.*)
|
|
1
|
+
import { connection } from "./Connection.js";
|
|
2
|
+
import { registry } from "./Registry.js";
|
|
3
|
+
import { createTCPServer, GeonixVersion, picoid, proxyHttp, sleep } from "./Util.js";
|
|
4
|
+
import express, { Router } from "express";
|
|
5
|
+
import { Request } from "./Request.js";
|
|
6
|
+
import { HEALTH_CHECK_ENDPOINT } from "./WebServer.js";
|
|
7
|
+
import expressWs from "express-ws";
|
|
8
|
+
import querystring from "querystring";
|
|
9
|
+
import semver from "semver";
|
|
10
|
+
import { WebSocket } from "ws";
|
|
11
|
+
|
|
12
|
+
const raw = express.raw({ limit: "100mb" });
|
|
13
|
+
|
|
14
|
+
const DEBUG_ENDPOINT = "/lZ6jD2eC3iP0zB3jJ1yJ9pM8gG3yI3vS";
|
|
15
|
+
const endpointMatcher = /^((?<options>.+)\|)?(?<verb>WS|GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS|ALL)\s(?<url>.*)/;
|
|
16
16
|
|
|
17
17
|
const logger = (req, res, next) => {
|
|
18
|
-
console.info(`HTTP ${req.method} ${req.url}`)
|
|
18
|
+
console.info(`HTTP ${req.method} ${req.url}`);
|
|
19
19
|
|
|
20
|
-
next()
|
|
21
|
-
}
|
|
20
|
+
next();
|
|
21
|
+
};
|
|
22
22
|
|
|
23
23
|
const stats = {
|
|
24
24
|
requests: 0,
|
|
25
25
|
proxied: 0,
|
|
26
26
|
proxied_over_nats: 0,
|
|
27
27
|
debug_requests: 0
|
|
28
|
-
}
|
|
28
|
+
};
|
|
29
29
|
|
|
30
30
|
const defaultOpts = {
|
|
31
|
-
|
|
32
|
-
}
|
|
31
|
+
beforeRequest: (_req, _res) => { },
|
|
32
|
+
afterRequest: (_req, _res) => { }
|
|
33
|
+
};
|
|
33
34
|
|
|
34
35
|
export class Gateway {
|
|
35
36
|
|
|
36
37
|
static start(opts) {
|
|
37
|
-
return new Gateway(opts)
|
|
38
|
+
return new Gateway(opts);
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
#opts = defaultOpts
|
|
41
|
-
#api = express()
|
|
42
|
-
#router = (req, res, next) => next()
|
|
43
|
-
#
|
|
44
|
-
#port
|
|
41
|
+
#opts = defaultOpts;
|
|
42
|
+
#api = express();
|
|
43
|
+
#router = (req, res, next) => next();
|
|
44
|
+
#port;
|
|
45
45
|
|
|
46
|
-
#rebuildRouter = false
|
|
47
|
-
#buildRouterRunning = false
|
|
48
|
-
#endpoints = []
|
|
46
|
+
#rebuildRouter = false;
|
|
47
|
+
#buildRouterRunning = false;
|
|
48
|
+
#endpoints = [];
|
|
49
49
|
|
|
50
|
-
#registry = {}
|
|
50
|
+
#registry = {};
|
|
51
51
|
|
|
52
52
|
constructor(opts) {
|
|
53
|
-
expressWs(this.#api)
|
|
53
|
+
expressWs(this.#api);
|
|
54
54
|
|
|
55
|
-
this.#opts = { ...this.#opts, ...opts }
|
|
55
|
+
this.#opts = { ...this.#opts, ...opts };
|
|
56
56
|
|
|
57
|
-
this.#start()
|
|
57
|
+
this.#start();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
async #start(port = 8080) {
|
|
61
|
-
await connection.waitUntilReady()
|
|
61
|
+
await connection.waitUntilReady();
|
|
62
62
|
|
|
63
|
-
this.#port = process.env.PORT || port
|
|
64
|
-
|
|
63
|
+
this.#port = process.env.PORT || port;
|
|
64
|
+
this.#api.listen(this.#port);
|
|
65
65
|
|
|
66
|
-
console.debug(`geonix.gateway: listening on http://0.0.0.0:${this.#port}`)
|
|
66
|
+
console.debug(`geonix.gateway: listening on http://0.0.0.0:${this.#port}`);
|
|
67
67
|
|
|
68
68
|
// logging
|
|
69
|
-
this.#api.use(logger)
|
|
69
|
+
this.#api.use(logger);
|
|
70
70
|
|
|
71
71
|
// cors
|
|
72
72
|
this.#api.use((req, res, next) => {
|
|
73
|
-
const origin = req.headers[
|
|
74
|
-
const allMethods =
|
|
75
|
-
const allHeaders =
|
|
76
|
-
const requestMethod = req.headers[
|
|
77
|
-
const requestHeaders = req.headers[
|
|
73
|
+
const origin = req.headers["origin"];
|
|
74
|
+
const allMethods = "GET,PUT,POST,DELETE,OPTIONS,HEAD";
|
|
75
|
+
const allHeaders = "*";
|
|
76
|
+
const requestMethod = req.headers["access-control-request-method"];
|
|
77
|
+
const requestHeaders = req.headers["access-control-request-headers"];
|
|
78
78
|
|
|
79
|
-
res.set(
|
|
80
|
-
res.set(
|
|
81
|
-
res.set(
|
|
82
|
-
res.set(
|
|
83
|
-
res.set(
|
|
79
|
+
res.set("Access-Control-Allow-Credentials", "true");
|
|
80
|
+
res.set("Access-Control-Allow-Origin", origin || "*");
|
|
81
|
+
res.set("Access-Control-Allow-Methods", requestMethod || allMethods);
|
|
82
|
+
res.set("Allow", requestMethod || allMethods);
|
|
83
|
+
res.set("Access-Control-Allow-Headers", requestHeaders || allHeaders);
|
|
84
84
|
|
|
85
|
-
next()
|
|
86
|
-
})
|
|
85
|
+
next();
|
|
86
|
+
});
|
|
87
87
|
|
|
88
88
|
// debug router (only available in non-production environments)
|
|
89
|
-
if (process.env.NODE_ENV !==
|
|
90
|
-
this.#api.use(DEBUG_ENDPOINT, this.#debugRouter())
|
|
89
|
+
if (process.env.NODE_ENV !== "production")
|
|
90
|
+
this.#api.use(DEBUG_ENDPOINT, this.#debugRouter());
|
|
91
|
+
|
|
92
|
+
this.#api.use((req, res, next) => {
|
|
93
|
+
if (this.#opts.beforeRequest) {
|
|
94
|
+
this.#opts.beforeRequest(req, res);
|
|
95
|
+
}
|
|
96
|
+
next();
|
|
97
|
+
});
|
|
91
98
|
|
|
92
99
|
// handle mapped endpoints as service calls
|
|
93
100
|
this.#api.use(raw, (req, res, next) => {
|
|
94
|
-
stats.requests
|
|
101
|
+
stats.requests++;
|
|
95
102
|
|
|
96
103
|
if (this.#router)
|
|
97
|
-
this.#router(req, res, next)
|
|
104
|
+
this.#router(req, res, next);
|
|
98
105
|
else
|
|
99
|
-
next()
|
|
100
|
-
})
|
|
106
|
+
next();
|
|
107
|
+
});
|
|
101
108
|
|
|
102
109
|
this.#api.use((req, res, next) => {
|
|
103
110
|
if (this.#opts.afterRequest) {
|
|
104
|
-
this.#opts.afterRequest(req, res)
|
|
111
|
+
this.#opts.afterRequest(req, res);
|
|
105
112
|
}
|
|
106
|
-
next()
|
|
107
|
-
})
|
|
113
|
+
next();
|
|
114
|
+
});
|
|
108
115
|
|
|
109
116
|
// config
|
|
110
|
-
this.#api.disable(
|
|
111
|
-
this.#api.disable(
|
|
117
|
+
this.#api.disable("x-powered-by");
|
|
118
|
+
this.#api.disable("etag");
|
|
112
119
|
|
|
113
120
|
// default answer
|
|
114
|
-
this.#api.all(
|
|
121
|
+
this.#api.all("*", (req, res) => {
|
|
115
122
|
res.status(404).send({
|
|
116
123
|
error: 404,
|
|
117
|
-
source:
|
|
118
|
-
})
|
|
119
|
-
})
|
|
124
|
+
source: "gw"
|
|
125
|
+
});
|
|
126
|
+
});
|
|
120
127
|
|
|
121
128
|
setInterval(() => {
|
|
122
129
|
if (this.#rebuildRouter)
|
|
123
|
-
this.#buildRouter()
|
|
124
|
-
}, 1000)
|
|
130
|
+
this.#buildRouter();
|
|
131
|
+
}, 1000);
|
|
125
132
|
|
|
126
133
|
while (true) {
|
|
127
134
|
try {
|
|
128
135
|
// send keeplive to check if connection is still alive
|
|
129
|
-
await connection.publish(
|
|
136
|
+
await connection.publish("gx.gateway.keepalive", Date.now());
|
|
130
137
|
|
|
131
|
-
await this.#handleAddedServics()
|
|
132
|
-
await this.#handleRemovedServices()
|
|
133
|
-
await sleep(1000)
|
|
138
|
+
await this.#handleAddedServics();
|
|
139
|
+
await this.#handleRemovedServices();
|
|
140
|
+
await sleep(1000);
|
|
134
141
|
|
|
135
|
-
if (connection.isClosed())
|
|
136
|
-
break
|
|
142
|
+
if (connection.isClosed()) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
137
145
|
} catch (e) {
|
|
138
|
-
console.error(e)
|
|
146
|
+
console.error(e);
|
|
139
147
|
}
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
// terminate process
|
|
143
|
-
console.debug(
|
|
144
|
-
process.exit(0)
|
|
151
|
+
console.debug("geonix.gateway: stopped");
|
|
152
|
+
process.exit(0);
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
async #handleAddedServics() {
|
|
148
|
-
let entries = Object.values(registry.getEntries())
|
|
156
|
+
let entries = Object.values(registry.getEntries());
|
|
149
157
|
|
|
150
158
|
const processEntry = async (entry) => {
|
|
151
159
|
if (this.#registry[entry.i] !== undefined)
|
|
152
|
-
return false
|
|
160
|
+
return false;
|
|
153
161
|
|
|
154
|
-
console.log(`gateway.onServiceAdded: ${entry.n}@${entry.v} (#${entry.i})`)
|
|
162
|
+
console.log(`gateway.onServiceAdded: ${entry.n}@${entry.v} (#${entry.i})`);
|
|
155
163
|
|
|
156
164
|
// figure out if endpoints is reachable via direct http call
|
|
157
|
-
let backend
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
let backend;
|
|
166
|
+
if (entry.a) {
|
|
167
|
+
for (let address of entry.a)
|
|
168
|
+
try {
|
|
169
|
+
const ac = new AbortController();
|
|
170
|
+
const timeout = setTimeout(() => ac.abort(), 500);
|
|
171
|
+
const result = await (await fetch(`http://${address}${HEALTH_CHECK_ENDPOINT}`, { signal: ac.signal })).json();
|
|
172
|
+
clearTimeout(timeout);
|
|
173
|
+
if (result.status === "healthy" && result.services?.includes(entry.n)) {
|
|
174
|
+
backend = address;
|
|
175
|
+
console.log(`${entry.n}@${entry.v} (#${entry.i}) directly reachable @ ${address}`);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
// silently ignore errors
|
|
168
180
|
}
|
|
169
|
-
|
|
170
|
-
// silently ignore errors
|
|
171
|
-
}
|
|
181
|
+
}
|
|
172
182
|
|
|
173
|
-
let proxy
|
|
183
|
+
let proxy;
|
|
174
184
|
if (!backend) {
|
|
175
185
|
// create proxy over nats
|
|
176
186
|
proxy = await createTCPServer(async (client) => {
|
|
177
|
-
const streamId = picoid()
|
|
178
|
-
stats.proxied_over_nats
|
|
187
|
+
const streamId = picoid();
|
|
188
|
+
stats.proxied_over_nats++;
|
|
179
189
|
|
|
180
190
|
try {
|
|
181
|
-
await this.#proxyHttpOverNats(streamId, entry, client)
|
|
191
|
+
await this.#proxyHttpOverNats(streamId, entry, client);
|
|
182
192
|
} catch (e) {
|
|
183
|
-
console.error(
|
|
184
|
-
client.destroy()
|
|
193
|
+
console.error("nats.proxy.error", e);
|
|
194
|
+
client.destroy();
|
|
185
195
|
}
|
|
186
|
-
}, 50000, 10000)
|
|
187
|
-
backend = `127.0.0.1:${proxy.port}
|
|
196
|
+
}, 50000, 10000);
|
|
197
|
+
backend = `127.0.0.1:${proxy.port}`;
|
|
188
198
|
}
|
|
189
199
|
|
|
190
|
-
this.#registry[entry.i] = { entry, proxy, backend }
|
|
200
|
+
this.#registry[entry.i] = { entry, proxy, backend };
|
|
191
201
|
|
|
192
|
-
return true
|
|
193
|
-
}
|
|
202
|
+
return true;
|
|
203
|
+
};
|
|
194
204
|
|
|
195
|
-
entries = (await Promise.all(entries.map(processEntry))).filter(result => result === true)
|
|
205
|
+
entries = (await Promise.all(entries.map(processEntry))).filter(result => result === true);
|
|
196
206
|
|
|
197
207
|
if (entries.length > 0)
|
|
198
|
-
this.#rebuildRouter = true
|
|
208
|
+
this.#rebuildRouter = true;
|
|
199
209
|
}
|
|
200
210
|
|
|
201
211
|
async #handleRemovedServices() {
|
|
202
|
-
const localEntries = Object.values(this.#registry)
|
|
203
|
-
const registryEntries = registry.getEntries()
|
|
212
|
+
const localEntries = Object.values(this.#registry);
|
|
213
|
+
const registryEntries = registry.getEntries();
|
|
204
214
|
|
|
205
215
|
for (let { entry, proxy } of localEntries) {
|
|
206
216
|
if (registryEntries[entry.i] === undefined) {
|
|
207
|
-
proxy?.server?.close()
|
|
208
|
-
console.log(`gateway.onServiceRemoved: ${entry.n}@${entry.v}`)
|
|
209
|
-
delete this.#registry[entry.i]
|
|
217
|
+
proxy?.server?.close();
|
|
218
|
+
console.log(`gateway.onServiceRemoved: ${entry.n}@${entry.v}`);
|
|
219
|
+
delete this.#registry[entry.i];
|
|
210
220
|
|
|
211
|
-
this.#rebuildRouter = true
|
|
221
|
+
this.#rebuildRouter = true;
|
|
212
222
|
}
|
|
213
223
|
}
|
|
214
224
|
}
|
|
215
225
|
|
|
216
226
|
#debugRouter() {
|
|
217
|
-
const router = Router()
|
|
227
|
+
const router = Router();
|
|
218
228
|
|
|
219
229
|
router.use((req, res, next) => {
|
|
220
|
-
stats.debug_requests
|
|
230
|
+
stats.debug_requests++;
|
|
221
231
|
|
|
222
|
-
next()
|
|
223
|
-
})
|
|
232
|
+
next();
|
|
233
|
+
});
|
|
224
234
|
|
|
225
|
-
router.get(
|
|
226
|
-
const services = Object.values(registry.getEntries()).map(e => (`${e.n}@${e.v}`))
|
|
227
|
-
services.sort()
|
|
228
|
-
res.send(services)
|
|
229
|
-
})
|
|
235
|
+
router.get("/services", (req, res) => {
|
|
236
|
+
const services = Object.values(registry.getEntries()).map(e => (`${e.n}@${e.v}`));
|
|
237
|
+
services.sort();
|
|
238
|
+
res.send(services);
|
|
239
|
+
});
|
|
230
240
|
|
|
231
|
-
router.get(
|
|
232
|
-
res.send(this.#endpoints)
|
|
233
|
-
})
|
|
241
|
+
router.get("/endpoints", (req, res) => {
|
|
242
|
+
res.send(this.#endpoints);
|
|
243
|
+
});
|
|
234
244
|
|
|
235
|
-
router.get(
|
|
236
|
-
res.send(this.#registry)
|
|
237
|
-
})
|
|
245
|
+
router.get("/router-registry", (req, res) => {
|
|
246
|
+
res.send(this.#registry);
|
|
247
|
+
});
|
|
238
248
|
|
|
239
|
-
router.get(
|
|
240
|
-
res.send(registry.getEntries())
|
|
241
|
-
})
|
|
249
|
+
router.get("/registry", (req, res) => {
|
|
250
|
+
res.send(registry.getEntries());
|
|
251
|
+
});
|
|
242
252
|
|
|
243
|
-
router.get(
|
|
244
|
-
res.send(stats)
|
|
245
|
-
})
|
|
253
|
+
router.get("/stats", (req, res) => {
|
|
254
|
+
res.send(stats);
|
|
255
|
+
});
|
|
246
256
|
|
|
247
|
-
router.get(
|
|
257
|
+
router.get("/info", (req, res) => {
|
|
248
258
|
res.send({
|
|
249
259
|
geonix: GeonixVersion,
|
|
250
260
|
node: {
|
|
@@ -256,39 +266,35 @@ export class Gateway {
|
|
|
256
266
|
mem: process.memoryUsage(),
|
|
257
267
|
rss: process.memoryUsage.rss(),
|
|
258
268
|
cpu: process.cpuUsage()
|
|
259
|
-
})
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
router.get('/rebuild', (req, res) => {
|
|
263
|
-
this.#rebuildRouter = true
|
|
264
|
-
res.send({ result: 'ok' })
|
|
265
|
-
})
|
|
269
|
+
});
|
|
270
|
+
});
|
|
266
271
|
|
|
267
|
-
router.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
})
|
|
272
|
+
router.all("*", (req, res) => {
|
|
273
|
+
res.status(404).send({ error: 404 });
|
|
274
|
+
});
|
|
271
275
|
|
|
272
|
-
return router
|
|
276
|
+
return router;
|
|
273
277
|
}
|
|
274
278
|
|
|
275
279
|
async #proxyHttpOverNats(streamId, entry, client) {
|
|
276
|
-
if (await Request(entry.n,
|
|
277
|
-
const ingress = await connection.subscribe(`gx2.stream.${streamId}.a`)
|
|
278
|
-
|
|
279
|
-
client.on(
|
|
280
|
-
client.on(
|
|
281
|
-
connection.unsubscribe(ingress)
|
|
282
|
-
connection.publish(`gx2.stream.${streamId}.c`, Buffer.from(
|
|
283
|
-
})
|
|
284
|
-
client.on(
|
|
280
|
+
if (await Request(entry.n, "SYS_createConnection", [streamId])) {
|
|
281
|
+
const ingress = await connection.subscribe(`gx2.stream.${streamId}.a`);
|
|
282
|
+
|
|
283
|
+
client.on("data", (chunk) => connection.publishRaw(`gx2.stream.${streamId}.b`, chunk));
|
|
284
|
+
client.on("close", () => {
|
|
285
|
+
connection.unsubscribe(ingress);
|
|
286
|
+
connection.publish(`gx2.stream.${streamId}.c`, Buffer.from("end"));
|
|
287
|
+
});
|
|
288
|
+
client.on("error", (_error) => {
|
|
289
|
+
// silently ignore errors
|
|
290
|
+
});
|
|
285
291
|
|
|
286
292
|
const dataLoop = async () => {
|
|
287
293
|
for await (const event of ingress)
|
|
288
|
-
client.write(event.data)
|
|
289
|
-
}
|
|
294
|
+
client.write(event.data);
|
|
295
|
+
};
|
|
290
296
|
|
|
291
|
-
dataLoop()
|
|
297
|
+
dataLoop();
|
|
292
298
|
}
|
|
293
299
|
}
|
|
294
300
|
|
|
@@ -305,45 +311,44 @@ export class Gateway {
|
|
|
305
311
|
headers: {
|
|
306
312
|
...req.headers
|
|
307
313
|
}
|
|
308
|
-
})
|
|
314
|
+
});
|
|
309
315
|
|
|
310
|
-
backend.on(
|
|
311
|
-
backend.on(
|
|
312
|
-
inbound.on(
|
|
316
|
+
backend.on("open", () => {
|
|
317
|
+
backend.on("message", (data, isBinary) => inbound.send(isBinary ? data : data.toString()));
|
|
318
|
+
inbound.on("message", data => backend.send(data));
|
|
313
319
|
|
|
314
|
-
backend.on(
|
|
315
|
-
inbound.on(
|
|
316
|
-
})
|
|
320
|
+
backend.on("close", () => inbound.close());
|
|
321
|
+
inbound.on("close", () => backend.close());
|
|
322
|
+
});
|
|
317
323
|
} catch (e) {
|
|
318
|
-
console.error(e)
|
|
324
|
+
console.error(e);
|
|
319
325
|
}
|
|
320
326
|
}
|
|
321
327
|
|
|
322
|
-
|
|
323
328
|
async #buildRouter() {
|
|
324
329
|
if (this.#buildRouterRunning)
|
|
325
|
-
return
|
|
330
|
+
return;
|
|
326
331
|
|
|
327
|
-
console.debug(
|
|
332
|
+
console.debug("gateway.buildRouter");
|
|
328
333
|
|
|
329
|
-
this.#rebuildRouter = false
|
|
330
|
-
this.#buildRouterRunning = true
|
|
334
|
+
this.#rebuildRouter = false;
|
|
335
|
+
this.#buildRouterRunning = true;
|
|
331
336
|
|
|
332
337
|
try {
|
|
333
|
-
const router = Router()
|
|
334
|
-
const entries = Object.values(this.#registry)
|
|
338
|
+
const router = Router();
|
|
339
|
+
const entries = Object.values(this.#registry);
|
|
335
340
|
|
|
336
|
-
const endpoints = []
|
|
341
|
+
const endpoints = [];
|
|
337
342
|
|
|
338
343
|
for (let { entry, backend } of entries) {
|
|
339
344
|
// generate global endpoint list
|
|
340
345
|
for (let e of entry.m) {
|
|
341
346
|
if (endpointMatcher.test(e)) {
|
|
342
|
-
const endpoint = endpointMatcher.exec(e).groups
|
|
347
|
+
const endpoint = endpointMatcher.exec(e).groups;
|
|
343
348
|
let options = {
|
|
344
349
|
order: 100,
|
|
345
350
|
...(endpoint.options ? querystring.parse(endpoint.options) : {})
|
|
346
|
-
}
|
|
351
|
+
};
|
|
347
352
|
|
|
348
353
|
try {
|
|
349
354
|
endpoints.push({
|
|
@@ -352,67 +357,66 @@ export class Gateway {
|
|
|
352
357
|
options,
|
|
353
358
|
endpoint,
|
|
354
359
|
backend: [backend]
|
|
355
|
-
})
|
|
360
|
+
});
|
|
356
361
|
} catch (e) {
|
|
357
|
-
console.error(
|
|
358
|
-
console.error(
|
|
362
|
+
console.error("gateway.buildRouter.error:", entry);
|
|
363
|
+
console.error("gateway.buildRouter.error:", e);
|
|
359
364
|
}
|
|
360
365
|
}
|
|
361
366
|
}
|
|
362
367
|
}
|
|
363
368
|
|
|
364
369
|
// handle duplicates (round-robin)
|
|
365
|
-
let index = endpoints.length
|
|
370
|
+
let index = endpoints.length;
|
|
366
371
|
while (index--) {
|
|
367
|
-
const version = endpoints[index].version
|
|
368
|
-
const url = `${endpoints[index].endpoint.verb} ${endpoints[index].endpoint.url}
|
|
372
|
+
const version = endpoints[index].version;
|
|
373
|
+
const url = `${endpoints[index].endpoint.verb} ${endpoints[index].endpoint.url}`;
|
|
369
374
|
|
|
370
375
|
for (let n = 0; n < index; n++)
|
|
371
376
|
if (`${endpoints[n].endpoint.verb} ${endpoints[n].endpoint.url}` === url && endpoints[n].version === version) {
|
|
372
|
-
endpoints[n].backend = endpoints[n].backend.concat(endpoints[index].backend)
|
|
373
|
-
endpoints.splice(index, 1)
|
|
374
|
-
break
|
|
377
|
+
endpoints[n].backend = endpoints[n].backend.concat(endpoints[index].backend);
|
|
378
|
+
endpoints.splice(index, 1);
|
|
379
|
+
break;
|
|
375
380
|
}
|
|
376
381
|
}
|
|
377
382
|
|
|
378
383
|
// sort endpoints by order, if there is one
|
|
379
|
-
endpoints.sort((a, b) => semver.rcompare(a.version, b.version))
|
|
380
|
-
endpoints.sort((a, b) => parseInt(a.options.order) - parseInt(b.options.order))
|
|
384
|
+
endpoints.sort((a, b) => semver.rcompare(a.version, b.version));
|
|
385
|
+
endpoints.sort((a, b) => parseInt(a.options.order) - parseInt(b.options.order));
|
|
381
386
|
|
|
382
|
-
this.#endpoints = endpoints
|
|
387
|
+
this.#endpoints = endpoints;
|
|
383
388
|
|
|
384
389
|
// build the router
|
|
385
390
|
for (let endpoint of endpoints) {
|
|
386
|
-
let { verb, url: uri } = endpoint.endpoint
|
|
387
|
-
let backend = endpoint.backend[endpoint.requests++ % endpoint.backend.length]
|
|
391
|
+
let { verb, url: uri } = endpoint.endpoint;
|
|
392
|
+
let backend = endpoint.backend[endpoint.requests++ % endpoint.backend.length];
|
|
388
393
|
|
|
389
|
-
verb = verb.toLowerCase()
|
|
394
|
+
verb = verb.toLowerCase();
|
|
390
395
|
|
|
391
|
-
if (verb ===
|
|
396
|
+
if (verb === "ws") {
|
|
392
397
|
router.ws(uri, (ws, req) => {
|
|
393
|
-
const url = req.originalUrl.replace(/\/\.websocket$/,
|
|
398
|
+
const url = req.originalUrl.replace(/\/\.websocket$/, "");
|
|
394
399
|
|
|
395
|
-
console.debug(
|
|
396
|
-
this.#proxyWebsocketOverNats(`ws://${backend}${url}`, ws, req)
|
|
397
|
-
})
|
|
400
|
+
console.debug("proxy.web.ws.to:", backend + req.originalUrl);
|
|
401
|
+
this.#proxyWebsocketOverNats(`ws://${backend}${url}`, ws, req);
|
|
402
|
+
});
|
|
398
403
|
} else
|
|
399
|
-
router[verb](uri, async (req, res,
|
|
400
|
-
stats.proxied
|
|
401
|
-
backend = endpoint.backend[endpoint.requests++ % endpoint.backend.length]
|
|
404
|
+
router[verb](uri, async (req, res, _next) => {
|
|
405
|
+
stats.proxied++;
|
|
406
|
+
backend = endpoint.backend[endpoint.requests++ % endpoint.backend.length];
|
|
402
407
|
|
|
403
408
|
try {
|
|
404
|
-
console.debug(
|
|
405
|
-
await proxyHttp(`http://${backend}`, req, res)
|
|
409
|
+
console.debug("proxy.web.to:", backend + req.originalUrl);
|
|
410
|
+
await proxyHttp(`http://${backend}`, req, res);
|
|
406
411
|
} catch (e) {
|
|
407
|
-
console.error(
|
|
408
|
-
} finally {
|
|
412
|
+
console.error("proxy.web.error:", e);
|
|
409
413
|
}
|
|
410
|
-
})
|
|
414
|
+
});
|
|
411
415
|
}
|
|
412
416
|
|
|
413
|
-
this.#router = router
|
|
417
|
+
this.#router = router;
|
|
414
418
|
} finally {
|
|
415
|
-
this.#buildRouterRunning = false
|
|
419
|
+
this.#buildRouterRunning = false;
|
|
416
420
|
}
|
|
417
421
|
}
|
|
418
422
|
|