geonix 1.12.3 → 1.20.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/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 +18 -19
- package/package.json +10 -6
- package/src/Connection.js +78 -55
- package/src/Gateway.js +205 -203
- 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,258 @@
|
|
|
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
|
|
165
|
+
let backend;
|
|
158
166
|
for (let address of entry.a)
|
|
159
167
|
try {
|
|
160
|
-
const ac = new AbortController()
|
|
161
|
-
const timeout = setTimeout(() => ac.abort(), 500)
|
|
162
|
-
const result = await (await fetch(`http://${address}${HEALTH_CHECK_ENDPOINT}`, { signal: ac.signal })).json()
|
|
163
|
-
clearTimeout(timeout)
|
|
164
|
-
if (result.status ===
|
|
165
|
-
backend = address
|
|
166
|
-
console.log(`${
|
|
167
|
-
break
|
|
168
|
+
const ac = new AbortController();
|
|
169
|
+
const timeout = setTimeout(() => ac.abort(), 500);
|
|
170
|
+
const result = await (await fetch(`http://${address}${HEALTH_CHECK_ENDPOINT}`, { signal: ac.signal })).json();
|
|
171
|
+
clearTimeout(timeout);
|
|
172
|
+
if (result.status === "healthy" && result.services?.includes(entry.n)) {
|
|
173
|
+
backend = address;
|
|
174
|
+
console.log(`${entry.n}@${entry.v} (#${entry.i}) directly reachable @ ${address}`);
|
|
175
|
+
break;
|
|
168
176
|
}
|
|
169
|
-
} catch
|
|
177
|
+
} catch {
|
|
170
178
|
// silently ignore errors
|
|
171
179
|
}
|
|
172
180
|
|
|
173
|
-
let proxy
|
|
181
|
+
let proxy;
|
|
174
182
|
if (!backend) {
|
|
175
183
|
// create proxy over nats
|
|
176
184
|
proxy = await createTCPServer(async (client) => {
|
|
177
|
-
const streamId = picoid()
|
|
178
|
-
stats.proxied_over_nats
|
|
185
|
+
const streamId = picoid();
|
|
186
|
+
stats.proxied_over_nats++;
|
|
179
187
|
|
|
180
188
|
try {
|
|
181
|
-
await this.#proxyHttpOverNats(streamId, entry, client)
|
|
189
|
+
await this.#proxyHttpOverNats(streamId, entry, client);
|
|
182
190
|
} catch (e) {
|
|
183
|
-
console.error(
|
|
184
|
-
client.destroy()
|
|
191
|
+
console.error("nats.proxy.error", e);
|
|
192
|
+
client.destroy();
|
|
185
193
|
}
|
|
186
|
-
}, 50000, 10000)
|
|
187
|
-
backend = `127.0.0.1:${proxy.port}
|
|
194
|
+
}, 50000, 10000);
|
|
195
|
+
backend = `127.0.0.1:${proxy.port}`;
|
|
188
196
|
}
|
|
189
197
|
|
|
190
|
-
this.#registry[entry.i] = { entry, proxy, backend }
|
|
198
|
+
this.#registry[entry.i] = { entry, proxy, backend };
|
|
191
199
|
|
|
192
|
-
return true
|
|
193
|
-
}
|
|
200
|
+
return true;
|
|
201
|
+
};
|
|
194
202
|
|
|
195
|
-
entries = (await Promise.all(entries.map(processEntry))).filter(result => result === true)
|
|
203
|
+
entries = (await Promise.all(entries.map(processEntry))).filter(result => result === true);
|
|
196
204
|
|
|
197
205
|
if (entries.length > 0)
|
|
198
|
-
this.#rebuildRouter = true
|
|
206
|
+
this.#rebuildRouter = true;
|
|
199
207
|
}
|
|
200
208
|
|
|
201
209
|
async #handleRemovedServices() {
|
|
202
|
-
const localEntries = Object.values(this.#registry)
|
|
203
|
-
const registryEntries = registry.getEntries()
|
|
210
|
+
const localEntries = Object.values(this.#registry);
|
|
211
|
+
const registryEntries = registry.getEntries();
|
|
204
212
|
|
|
205
213
|
for (let { entry, proxy } of localEntries) {
|
|
206
214
|
if (registryEntries[entry.i] === undefined) {
|
|
207
|
-
proxy?.server?.close()
|
|
208
|
-
console.log(`gateway.onServiceRemoved: ${entry.n}@${entry.v}`)
|
|
209
|
-
delete this.#registry[entry.i]
|
|
215
|
+
proxy?.server?.close();
|
|
216
|
+
console.log(`gateway.onServiceRemoved: ${entry.n}@${entry.v}`);
|
|
217
|
+
delete this.#registry[entry.i];
|
|
210
218
|
|
|
211
|
-
this.#rebuildRouter = true
|
|
219
|
+
this.#rebuildRouter = true;
|
|
212
220
|
}
|
|
213
221
|
}
|
|
214
222
|
}
|
|
215
223
|
|
|
216
224
|
#debugRouter() {
|
|
217
|
-
const router = Router()
|
|
225
|
+
const router = Router();
|
|
218
226
|
|
|
219
227
|
router.use((req, res, next) => {
|
|
220
|
-
stats.debug_requests
|
|
228
|
+
stats.debug_requests++;
|
|
221
229
|
|
|
222
|
-
next()
|
|
223
|
-
})
|
|
230
|
+
next();
|
|
231
|
+
});
|
|
224
232
|
|
|
225
|
-
router.get(
|
|
226
|
-
const services = Object.values(registry.getEntries()).map(e => (`${e.n}@${e.v}`))
|
|
227
|
-
services.sort()
|
|
228
|
-
res.send(services)
|
|
229
|
-
})
|
|
233
|
+
router.get("/services", (req, res) => {
|
|
234
|
+
const services = Object.values(registry.getEntries()).map(e => (`${e.n}@${e.v}`));
|
|
235
|
+
services.sort();
|
|
236
|
+
res.send(services);
|
|
237
|
+
});
|
|
230
238
|
|
|
231
|
-
router.get(
|
|
232
|
-
res.send(this.#endpoints)
|
|
233
|
-
})
|
|
239
|
+
router.get("/endpoints", (req, res) => {
|
|
240
|
+
res.send(this.#endpoints);
|
|
241
|
+
});
|
|
234
242
|
|
|
235
|
-
router.get(
|
|
236
|
-
res.send(this.#registry)
|
|
237
|
-
})
|
|
243
|
+
router.get("/router-registry", (req, res) => {
|
|
244
|
+
res.send(this.#registry);
|
|
245
|
+
});
|
|
238
246
|
|
|
239
|
-
router.get(
|
|
240
|
-
res.send(registry.getEntries())
|
|
241
|
-
})
|
|
247
|
+
router.get("/registry", (req, res) => {
|
|
248
|
+
res.send(registry.getEntries());
|
|
249
|
+
});
|
|
242
250
|
|
|
243
|
-
router.get(
|
|
244
|
-
res.send(stats)
|
|
245
|
-
})
|
|
251
|
+
router.get("/stats", (req, res) => {
|
|
252
|
+
res.send(stats);
|
|
253
|
+
});
|
|
246
254
|
|
|
247
|
-
router.get(
|
|
255
|
+
router.get("/info", (req, res) => {
|
|
248
256
|
res.send({
|
|
249
257
|
geonix: GeonixVersion,
|
|
250
258
|
node: {
|
|
@@ -256,39 +264,35 @@ export class Gateway {
|
|
|
256
264
|
mem: process.memoryUsage(),
|
|
257
265
|
rss: process.memoryUsage.rss(),
|
|
258
266
|
cpu: process.cpuUsage()
|
|
259
|
-
})
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
router.get('/rebuild', (req, res) => {
|
|
263
|
-
this.#rebuildRouter = true
|
|
264
|
-
res.send({ result: 'ok' })
|
|
265
|
-
})
|
|
267
|
+
});
|
|
268
|
+
});
|
|
266
269
|
|
|
267
|
-
router.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
})
|
|
270
|
+
router.all("*", (req, res) => {
|
|
271
|
+
res.status(404).send({ error: 404 });
|
|
272
|
+
});
|
|
271
273
|
|
|
272
|
-
return router
|
|
274
|
+
return router;
|
|
273
275
|
}
|
|
274
276
|
|
|
275
277
|
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(
|
|
278
|
+
if (await Request(entry.n, "SYS_createConnection", [streamId])) {
|
|
279
|
+
const ingress = await connection.subscribe(`gx2.stream.${streamId}.a`);
|
|
280
|
+
|
|
281
|
+
client.on("data", (chunk) => connection.publishRaw(`gx2.stream.${streamId}.b`, chunk));
|
|
282
|
+
client.on("close", () => {
|
|
283
|
+
connection.unsubscribe(ingress);
|
|
284
|
+
connection.publish(`gx2.stream.${streamId}.c`, Buffer.from("end"));
|
|
285
|
+
});
|
|
286
|
+
client.on("error", (_error) => {
|
|
287
|
+
// silently ignore errors
|
|
288
|
+
});
|
|
285
289
|
|
|
286
290
|
const dataLoop = async () => {
|
|
287
291
|
for await (const event of ingress)
|
|
288
|
-
client.write(event.data)
|
|
289
|
-
}
|
|
292
|
+
client.write(event.data);
|
|
293
|
+
};
|
|
290
294
|
|
|
291
|
-
dataLoop()
|
|
295
|
+
dataLoop();
|
|
292
296
|
}
|
|
293
297
|
}
|
|
294
298
|
|
|
@@ -305,45 +309,44 @@ export class Gateway {
|
|
|
305
309
|
headers: {
|
|
306
310
|
...req.headers
|
|
307
311
|
}
|
|
308
|
-
})
|
|
312
|
+
});
|
|
309
313
|
|
|
310
|
-
backend.on(
|
|
311
|
-
backend.on(
|
|
312
|
-
inbound.on(
|
|
314
|
+
backend.on("open", () => {
|
|
315
|
+
backend.on("message", (data, isBinary) => inbound.send(isBinary ? data : data.toString()));
|
|
316
|
+
inbound.on("message", data => backend.send(data));
|
|
313
317
|
|
|
314
|
-
backend.on(
|
|
315
|
-
inbound.on(
|
|
316
|
-
})
|
|
318
|
+
backend.on("close", () => inbound.close());
|
|
319
|
+
inbound.on("close", () => backend.close());
|
|
320
|
+
});
|
|
317
321
|
} catch (e) {
|
|
318
|
-
console.error(e)
|
|
322
|
+
console.error(e);
|
|
319
323
|
}
|
|
320
324
|
}
|
|
321
325
|
|
|
322
|
-
|
|
323
326
|
async #buildRouter() {
|
|
324
327
|
if (this.#buildRouterRunning)
|
|
325
|
-
return
|
|
328
|
+
return;
|
|
326
329
|
|
|
327
|
-
console.debug(
|
|
330
|
+
console.debug("gateway.buildRouter");
|
|
328
331
|
|
|
329
|
-
this.#rebuildRouter = false
|
|
330
|
-
this.#buildRouterRunning = true
|
|
332
|
+
this.#rebuildRouter = false;
|
|
333
|
+
this.#buildRouterRunning = true;
|
|
331
334
|
|
|
332
335
|
try {
|
|
333
|
-
const router = Router()
|
|
334
|
-
const entries = Object.values(this.#registry)
|
|
336
|
+
const router = Router();
|
|
337
|
+
const entries = Object.values(this.#registry);
|
|
335
338
|
|
|
336
|
-
const endpoints = []
|
|
339
|
+
const endpoints = [];
|
|
337
340
|
|
|
338
341
|
for (let { entry, backend } of entries) {
|
|
339
342
|
// generate global endpoint list
|
|
340
343
|
for (let e of entry.m) {
|
|
341
344
|
if (endpointMatcher.test(e)) {
|
|
342
|
-
const endpoint = endpointMatcher.exec(e).groups
|
|
345
|
+
const endpoint = endpointMatcher.exec(e).groups;
|
|
343
346
|
let options = {
|
|
344
347
|
order: 100,
|
|
345
348
|
...(endpoint.options ? querystring.parse(endpoint.options) : {})
|
|
346
|
-
}
|
|
349
|
+
};
|
|
347
350
|
|
|
348
351
|
try {
|
|
349
352
|
endpoints.push({
|
|
@@ -352,67 +355,66 @@ export class Gateway {
|
|
|
352
355
|
options,
|
|
353
356
|
endpoint,
|
|
354
357
|
backend: [backend]
|
|
355
|
-
})
|
|
358
|
+
});
|
|
356
359
|
} catch (e) {
|
|
357
|
-
console.error(
|
|
358
|
-
console.error(
|
|
360
|
+
console.error("gateway.buildRouter.error:", entry);
|
|
361
|
+
console.error("gateway.buildRouter.error:", e);
|
|
359
362
|
}
|
|
360
363
|
}
|
|
361
364
|
}
|
|
362
365
|
}
|
|
363
366
|
|
|
364
367
|
// handle duplicates (round-robin)
|
|
365
|
-
let index = endpoints.length
|
|
368
|
+
let index = endpoints.length;
|
|
366
369
|
while (index--) {
|
|
367
|
-
const version = endpoints[index].version
|
|
368
|
-
const url = `${endpoints[index].endpoint.verb} ${endpoints[index].endpoint.url}
|
|
370
|
+
const version = endpoints[index].version;
|
|
371
|
+
const url = `${endpoints[index].endpoint.verb} ${endpoints[index].endpoint.url}`;
|
|
369
372
|
|
|
370
373
|
for (let n = 0; n < index; n++)
|
|
371
374
|
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
|
|
375
|
+
endpoints[n].backend = endpoints[n].backend.concat(endpoints[index].backend);
|
|
376
|
+
endpoints.splice(index, 1);
|
|
377
|
+
break;
|
|
375
378
|
}
|
|
376
379
|
}
|
|
377
380
|
|
|
378
381
|
// 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))
|
|
382
|
+
endpoints.sort((a, b) => semver.rcompare(a.version, b.version));
|
|
383
|
+
endpoints.sort((a, b) => parseInt(a.options.order) - parseInt(b.options.order));
|
|
381
384
|
|
|
382
|
-
this.#endpoints = endpoints
|
|
385
|
+
this.#endpoints = endpoints;
|
|
383
386
|
|
|
384
387
|
// build the router
|
|
385
388
|
for (let endpoint of endpoints) {
|
|
386
|
-
let { verb, url: uri } = endpoint.endpoint
|
|
387
|
-
let backend = endpoint.backend[endpoint.requests++ % endpoint.backend.length]
|
|
389
|
+
let { verb, url: uri } = endpoint.endpoint;
|
|
390
|
+
let backend = endpoint.backend[endpoint.requests++ % endpoint.backend.length];
|
|
388
391
|
|
|
389
|
-
verb = verb.toLowerCase()
|
|
392
|
+
verb = verb.toLowerCase();
|
|
390
393
|
|
|
391
|
-
if (verb ===
|
|
394
|
+
if (verb === "ws") {
|
|
392
395
|
router.ws(uri, (ws, req) => {
|
|
393
|
-
const url = req.originalUrl.replace(/\/\.websocket$/,
|
|
396
|
+
const url = req.originalUrl.replace(/\/\.websocket$/, "");
|
|
394
397
|
|
|
395
|
-
console.debug(
|
|
396
|
-
this.#proxyWebsocketOverNats(`ws://${backend}${url}`, ws, req)
|
|
397
|
-
})
|
|
398
|
+
console.debug("proxy.web.ws.to:", backend + req.originalUrl);
|
|
399
|
+
this.#proxyWebsocketOverNats(`ws://${backend}${url}`, ws, req);
|
|
400
|
+
});
|
|
398
401
|
} else
|
|
399
|
-
router[verb](uri, async (req, res,
|
|
400
|
-
stats.proxied
|
|
401
|
-
backend = endpoint.backend[endpoint.requests++ % endpoint.backend.length]
|
|
402
|
+
router[verb](uri, async (req, res, _next) => {
|
|
403
|
+
stats.proxied++;
|
|
404
|
+
backend = endpoint.backend[endpoint.requests++ % endpoint.backend.length];
|
|
402
405
|
|
|
403
406
|
try {
|
|
404
|
-
console.debug(
|
|
405
|
-
await proxyHttp(`http://${backend}`, req, res)
|
|
407
|
+
console.debug("proxy.web.to:", backend + req.originalUrl);
|
|
408
|
+
await proxyHttp(`http://${backend}`, req, res);
|
|
406
409
|
} catch (e) {
|
|
407
|
-
console.error(
|
|
408
|
-
} finally {
|
|
410
|
+
console.error("proxy.web.error:", e);
|
|
409
411
|
}
|
|
410
|
-
})
|
|
412
|
+
});
|
|
411
413
|
}
|
|
412
414
|
|
|
413
|
-
this.#router = router
|
|
415
|
+
this.#router = router;
|
|
414
416
|
} finally {
|
|
415
|
-
this.#buildRouterRunning = false
|
|
417
|
+
this.#buildRouterRunning = false;
|
|
416
418
|
}
|
|
417
419
|
}
|
|
418
420
|
|