geonix 1.12.2 → 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 +20 -20
- package/package.json +10 -6
- package/src/Connection.js +78 -55
- package/src/Gateway.js +214 -198
- 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/Service.js
CHANGED
|
@@ -1,57 +1,53 @@
|
|
|
1
|
-
import { codec, connection } from
|
|
2
|
-
import { picoid, sleep, hash, getSecondsSinceMidnight, OverlayObject, GeonixVersion,
|
|
3
|
-
import { webserver } from
|
|
4
|
-
import { createConnection } from
|
|
5
|
-
import { EOL } from
|
|
6
|
-
import cookieParser from
|
|
7
|
-
import express from
|
|
8
|
-
import { isStream, streamToString } from
|
|
9
|
-
|
|
10
|
-
const protectedMethodNames = [
|
|
11
|
-
const endpointMatcher = /^((?<options>.+)\|)?(?<verb>WS|SUB|GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS|ALL)\s(?<url>.*)
|
|
12
|
-
const isEndpointFilter = methodName => endpointMatcher.test(methodName)
|
|
13
|
-
const ERROR_BEGIN_DELIMITER =
|
|
14
|
-
const ERROR_END_DELIMITER =
|
|
15
|
-
|
|
16
|
-
const json = express.json({ limit:
|
|
17
|
-
const raw = express.raw({ type:
|
|
1
|
+
import { codec, connection } from "./Connection.js";
|
|
2
|
+
import { picoid, sleep, hash, getSecondsSinceMidnight, OverlayObject, GeonixVersion, getFirstItemFromAsyncIterable } from "./Util.js";
|
|
3
|
+
import { webserver } from "./WebServer.js";
|
|
4
|
+
import { createConnection } from "net";
|
|
5
|
+
import { EOL } from "os";
|
|
6
|
+
import cookieParser from "cookie-parser";
|
|
7
|
+
import express from "express";
|
|
8
|
+
import { isStream, streamToString } from "./Stream.js";
|
|
9
|
+
|
|
10
|
+
const protectedMethodNames = ["constructor", "onStart"];
|
|
11
|
+
const endpointMatcher = /^((?<options>.+)\|)?(?<verb>WS|SUB|GET|POST|PATCH|PUT|DELETE|HEAD|OPTIONS|ALL)\s(?<url>.*)/;
|
|
12
|
+
const isEndpointFilter = methodName => endpointMatcher.test(methodName);
|
|
13
|
+
const ERROR_BEGIN_DELIMITER = "-".repeat(10);
|
|
14
|
+
const ERROR_END_DELIMITER = "-".repeat(40);
|
|
15
|
+
|
|
16
|
+
const json = express.json({ limit: "100mb" });
|
|
17
|
+
const raw = express.raw({ type: "*/*", limit: "100mb" });
|
|
18
18
|
|
|
19
19
|
export class Service {
|
|
20
20
|
|
|
21
|
-
static serviceClasses = []
|
|
21
|
+
static serviceClasses = [];
|
|
22
22
|
|
|
23
23
|
static start(options = {}) {
|
|
24
24
|
if (!this.serviceClasses.includes(this.prototype.constructor.name))
|
|
25
|
-
this.serviceClasses.push(this.prototype.constructor.name)
|
|
26
|
-
|
|
27
|
-
const instance = new this()
|
|
28
|
-
instance.#start(options)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
static static() {
|
|
25
|
+
this.serviceClasses.push(this.prototype.constructor.name);
|
|
32
26
|
|
|
27
|
+
const instance = new this();
|
|
28
|
+
instance.#start(options);
|
|
33
29
|
}
|
|
34
30
|
|
|
35
31
|
// ---------------------------------------------------------------------------------------------
|
|
36
32
|
|
|
37
|
-
#me = {}
|
|
38
|
-
#options = {}
|
|
33
|
+
#me = {};
|
|
34
|
+
#options = {};
|
|
39
35
|
|
|
40
36
|
async #start(options = {}) {
|
|
41
|
-
this.#options = options
|
|
37
|
+
this.#options = options;
|
|
42
38
|
|
|
43
|
-
await webserver.waitUntilReady()
|
|
44
|
-
await connection.waitUntilReady()
|
|
39
|
+
await webserver.waitUntilReady();
|
|
40
|
+
await connection.waitUntilReady();
|
|
45
41
|
|
|
46
|
-
const fields = Object.getOwnPropertyNames(this).filter(isEndpointFilter).concat(Object.getOwnPropertyNames(this.constructor.prototype))
|
|
42
|
+
const fields = Object.getOwnPropertyNames(this).filter(isEndpointFilter).concat(Object.getOwnPropertyNames(this.constructor.prototype));
|
|
47
43
|
|
|
48
44
|
// preserve order of endpoints as defined in the source
|
|
49
|
-
const serviceSource = this.constructor.toString().split(
|
|
45
|
+
const serviceSource = this.constructor.toString().split("\n");
|
|
50
46
|
fields.sort((a, b, ia = -1, ib = -1) => {
|
|
51
|
-
for (let line = 0; line < serviceSource.length; line++) ia = serviceSource[line].includes(a) ? line : ia
|
|
52
|
-
for (let line = 0; line < serviceSource.length; line++) ib = serviceSource[line].includes(b) ? line : ib
|
|
53
|
-
return ia - ib
|
|
54
|
-
})
|
|
47
|
+
for (let line = 0; line < serviceSource.length; line++) ia = serviceSource[line].includes(a) ? line : ia;
|
|
48
|
+
for (let line = 0; line < serviceSource.length; line++) ib = serviceSource[line].includes(b) ? line : ib;
|
|
49
|
+
return ia - ib;
|
|
50
|
+
});
|
|
55
51
|
|
|
56
52
|
this.#me = {
|
|
57
53
|
id: options?.id,
|
|
@@ -59,33 +55,36 @@ export class Service {
|
|
|
59
55
|
// instance
|
|
60
56
|
i: picoid(),
|
|
61
57
|
// name
|
|
62
|
-
n: this.constructor.name,
|
|
58
|
+
n: options.name ?? this.constructor.name,
|
|
63
59
|
// version
|
|
64
60
|
v: process.env.VERSION || process.env.version || options?.version || this.version || `999.999.${getSecondsSinceMidnight()}`,
|
|
65
61
|
// methods
|
|
66
62
|
m: fields
|
|
67
63
|
.filter(methodName => !protectedMethodNames.includes(methodName))
|
|
68
|
-
.filter(methodName => !methodName.startsWith(
|
|
64
|
+
.filter(methodName => !methodName.startsWith("$")),
|
|
65
|
+
// geonix version
|
|
66
|
+
gx: GeonixVersion,
|
|
69
67
|
// IP addresses
|
|
70
|
-
a:
|
|
71
|
-
}
|
|
68
|
+
a: webserver.getAddresses().map(address => `${address}:${webserver.getPort()}`)
|
|
69
|
+
};
|
|
72
70
|
|
|
73
71
|
// check if method takes context as first argument
|
|
74
72
|
for (let methodName of this.#me.m) {
|
|
75
|
-
const method = this[methodName]
|
|
76
|
-
method.takesContext = method.toString()?.match(/\((?<args>.*)\)/)?.groups?.args.startsWith(
|
|
73
|
+
const method = this[methodName];
|
|
74
|
+
method.takesContext = method.toString()?.match(/\((?<args>.*)\)/)?.groups?.args.startsWith("$");
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
this.#beacon()
|
|
80
|
-
this.#queueListener()
|
|
81
|
-
this.#directListener()
|
|
82
|
-
this.#webserver()
|
|
77
|
+
this.#beacon();
|
|
78
|
+
this.#queueListener();
|
|
79
|
+
this.#directListener();
|
|
80
|
+
this.#webserver();
|
|
83
81
|
|
|
84
|
-
console.log(
|
|
82
|
+
console.log("gx.service.start", this.#me.n, this.#me.v);
|
|
85
83
|
|
|
86
84
|
// execute onStart method, if present
|
|
87
|
-
if (this.onStart)
|
|
88
|
-
this.onStart()
|
|
85
|
+
if (this.onStart) {
|
|
86
|
+
this.onStart();
|
|
87
|
+
}
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
/**
|
|
@@ -94,8 +93,8 @@ export class Service {
|
|
|
94
93
|
*/
|
|
95
94
|
async #beacon() {
|
|
96
95
|
while (true) {
|
|
97
|
-
connection.publish(
|
|
98
|
-
await sleep(1000)
|
|
96
|
+
connection.publish("gx2.beacon", { i: this.#me.i });
|
|
97
|
+
await sleep(1000);
|
|
99
98
|
}
|
|
100
99
|
}
|
|
101
100
|
|
|
@@ -103,17 +102,17 @@ export class Service {
|
|
|
103
102
|
* Wait and respond to remote call events (queue)
|
|
104
103
|
*/
|
|
105
104
|
async #queueListener() {
|
|
106
|
-
const identifier = `${this.#me.n}@${this.#me.v}
|
|
107
|
-
const subscription = await connection.subscribe(`gx2.service.${hash(identifier)}`, { queue: identifier })
|
|
105
|
+
const identifier = `${this.#me.n}@${this.#me.v}`;
|
|
106
|
+
const subscription = await connection.subscribe(`gx2.service.${hash(identifier)}`, { queue: identifier });
|
|
108
107
|
|
|
109
108
|
for await (let event of subscription) {
|
|
110
|
-
let call = codec.decode(event.data)
|
|
109
|
+
let call = codec.decode(event.data);
|
|
111
110
|
|
|
112
111
|
if (isStream(call))
|
|
113
|
-
call = JSON.parse(await streamToString(call))
|
|
112
|
+
call = JSON.parse(await streamToString(call));
|
|
114
113
|
|
|
115
114
|
if (call.$r && call.p)
|
|
116
|
-
this.#onCall(call.p, (json) => connection.publish(call.$r, json))
|
|
115
|
+
this.#onCall(call.p, (json) => connection.publish(call.$r, json));
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
|
|
@@ -121,17 +120,17 @@ export class Service {
|
|
|
121
120
|
* Wait and respond to remote call events (queue)
|
|
122
121
|
*/
|
|
123
122
|
async #directListener() {
|
|
124
|
-
const identifier = this.#me.i
|
|
125
|
-
const subscription = await connection.subscribe(`gx2.service.${hash(identifier)}`, { queue: identifier })
|
|
123
|
+
const identifier = this.#me.i;
|
|
124
|
+
const subscription = await connection.subscribe(`gx2.service.${hash(identifier)}`, { queue: identifier });
|
|
126
125
|
|
|
127
126
|
for await (let event of subscription) {
|
|
128
|
-
let call = codec.decode(event.data)
|
|
127
|
+
let call = codec.decode(event.data);
|
|
129
128
|
|
|
130
129
|
if (isStream(call))
|
|
131
|
-
call = JSON.parse(await streamToString(call))
|
|
130
|
+
call = JSON.parse(await streamToString(call));
|
|
132
131
|
|
|
133
132
|
if (call.$r && call.p)
|
|
134
|
-
this.#onCall(call.p, (json) => connection.publish(call.$r, json))
|
|
133
|
+
this.#onCall(call.p, (json) => connection.publish(call.$r, json));
|
|
135
134
|
}
|
|
136
135
|
}
|
|
137
136
|
|
|
@@ -141,48 +140,52 @@ export class Service {
|
|
|
141
140
|
*/
|
|
142
141
|
async #webserver() {
|
|
143
142
|
const endpoints = this.#me.m
|
|
144
|
-
.filter(isEndpointFilter)
|
|
143
|
+
.filter(isEndpointFilter);
|
|
145
144
|
|
|
146
145
|
if (!endpoints || endpoints.length === 0)
|
|
147
|
-
return
|
|
146
|
+
return;
|
|
148
147
|
|
|
149
|
-
const router = webserver.router()
|
|
150
|
-
router.use(json, raw, cookieParser())
|
|
148
|
+
const router = webserver.router();
|
|
149
|
+
router.use(json, raw, cookieParser());
|
|
151
150
|
for (let endpoint of endpoints) {
|
|
152
|
-
let { verb, url: uri } = endpointMatcher.exec(endpoint)?.groups || {}
|
|
153
|
-
verb = verb.toLowerCase()
|
|
151
|
+
let { verb, url: uri } = endpointMatcher.exec(endpoint)?.groups || {};
|
|
152
|
+
verb = verb.toLowerCase();
|
|
154
153
|
|
|
155
|
-
let handlers = (Array.isArray(this[endpoint]) ? this[endpoint] : [this[endpoint]])
|
|
154
|
+
let handlers = (Array.isArray(this[endpoint]) ? this[endpoint] : [this[endpoint]]);
|
|
156
155
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
const handlersBefore = this.#options?.handlers?.before ?? [];
|
|
157
|
+
const handlersAfter = this.#options?.handlers?.after ?? [];
|
|
158
|
+
|
|
159
|
+
handlers = [...handlersBefore, ...handlers, ...handlersAfter];
|
|
161
160
|
|
|
162
161
|
for (let n = 0; n < handlers.length; n++)
|
|
163
|
-
handlers[n] = handlers[n].bind(this)
|
|
162
|
+
handlers[n] = handlers[n].bind(this);
|
|
164
163
|
|
|
165
164
|
switch (verb) {
|
|
166
|
-
case
|
|
167
|
-
router.ws(uri, this[endpoint].bind(this))
|
|
168
|
-
break
|
|
169
|
-
|
|
170
|
-
case
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
for await (const event of subscription)
|
|
174
|
-
this[endpoint](event.data)
|
|
175
|
-
}
|
|
176
|
-
processor()
|
|
177
|
-
break
|
|
165
|
+
case "ws":
|
|
166
|
+
router.ws(uri, this[endpoint].bind(this));
|
|
167
|
+
break;
|
|
168
|
+
|
|
169
|
+
case "sub":
|
|
170
|
+
this.#sub(uri, this[endpoint]);
|
|
171
|
+
break;
|
|
178
172
|
|
|
179
173
|
default:
|
|
180
|
-
router[verb](uri, ...handlers)
|
|
181
|
-
break
|
|
174
|
+
router[verb](uri, ...handlers);
|
|
175
|
+
break;
|
|
182
176
|
}
|
|
183
177
|
}
|
|
184
178
|
}
|
|
185
179
|
|
|
180
|
+
async #sub(subject, handler) {
|
|
181
|
+
const subscription = await connection.subscribe(`gx.sub.${subject}`);
|
|
182
|
+
const processor = async () => {
|
|
183
|
+
for await (const event of subscription)
|
|
184
|
+
handler(event.data);
|
|
185
|
+
};
|
|
186
|
+
processor();
|
|
187
|
+
}
|
|
188
|
+
|
|
186
189
|
/**
|
|
187
190
|
* Handle individual call
|
|
188
191
|
* @param {Object} call
|
|
@@ -190,69 +193,69 @@ export class Service {
|
|
|
190
193
|
* @returns
|
|
191
194
|
*/
|
|
192
195
|
async #onCall(call, respond) {
|
|
193
|
-
const { m: methodName, a: args, c: context, o: caller } = call
|
|
196
|
+
const { m: methodName, a: args, c: context, o: caller } = call;
|
|
194
197
|
|
|
195
|
-
const method = this[methodName]
|
|
196
|
-
let _args = args
|
|
198
|
+
const method = this[methodName];
|
|
199
|
+
let _args = args;
|
|
197
200
|
|
|
198
201
|
if (!method)
|
|
199
|
-
return respond({ e: `unknown method (${this.#me.n}.${methodName})` })
|
|
202
|
+
return respond({ e: `unknown method (${this.#me.n}.${methodName})` });
|
|
200
203
|
|
|
201
204
|
try {
|
|
202
205
|
// inject context as first argument
|
|
203
206
|
if (method.takesContext)
|
|
204
|
-
_args = [OverlayObject(context, { caller, me: this.#me }), ..._args]
|
|
207
|
+
_args = [OverlayObject(context, { caller, me: this.#me }), ..._args];
|
|
205
208
|
|
|
206
|
-
respond({ r: await method.apply(this, _args) })
|
|
209
|
+
respond({ r: await method.apply(this, _args) });
|
|
207
210
|
} catch (e) {
|
|
208
|
-
respond({ e: `${EOL}${ERROR_BEGIN_DELIMITER} ${this.#me.n}${EOL}${e.stack}${EOL}${ERROR_END_DELIMITER}` })
|
|
211
|
+
respond({ e: `${EOL}${ERROR_BEGIN_DELIMITER} ${this.#me.n}${EOL}${e.stack}${EOL}${ERROR_END_DELIMITER}` });
|
|
209
212
|
}
|
|
210
213
|
}
|
|
211
214
|
|
|
212
|
-
connections = new Map()
|
|
213
|
-
async
|
|
214
|
-
const ingress = await connection.subscribe(`gx2.stream.${streamId}.b`)
|
|
215
|
-
const control = await connection.subscribe(`gx2.stream.${streamId}.c`)
|
|
215
|
+
connections = new Map();
|
|
216
|
+
async $createConnection(streamId) {
|
|
217
|
+
const ingress = await connection.subscribe(`gx2.stream.${streamId}.b`);
|
|
218
|
+
const control = await connection.subscribe(`gx2.stream.${streamId}.c`);
|
|
216
219
|
|
|
217
|
-
const client = createConnection({ port: webserver.getPort() })
|
|
218
|
-
client.on(
|
|
219
|
-
client.on(
|
|
220
|
+
const client = createConnection({ port: webserver.getPort() });
|
|
221
|
+
client.on("data", (chunk) => connection.publishRaw(`gx2.stream.${streamId}.a`, chunk));
|
|
222
|
+
client.on("close", () => connection.unsubscribe(ingress));
|
|
220
223
|
|
|
221
224
|
this.connections.set(streamId, {
|
|
222
225
|
client,
|
|
223
226
|
sub: ingress
|
|
224
|
-
})
|
|
227
|
+
});
|
|
225
228
|
|
|
226
229
|
const incomingLoop = async () => {
|
|
227
230
|
for await (const event of ingress) {
|
|
228
|
-
client.write(Buffer.from(event.data))
|
|
231
|
+
client.write(Buffer.from(event.data));
|
|
229
232
|
}
|
|
230
|
-
}
|
|
233
|
+
};
|
|
231
234
|
|
|
232
235
|
const controlLoop = async () => {
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
const _connection = this.connections.get(streamId)
|
|
236
|
-
if (!_connection) {
|
|
237
|
-
return
|
|
238
|
-
}
|
|
236
|
+
// wait for first control message to arrive
|
|
237
|
+
await getFirstItemFromAsyncIterable(control);
|
|
239
238
|
|
|
240
|
-
|
|
241
|
-
|
|
239
|
+
const _connection = this.connections.get(streamId);
|
|
240
|
+
if (!_connection) {
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
242
243
|
|
|
243
|
-
|
|
244
|
+
connection.unsubscribe(ingress);
|
|
245
|
+
connection.unsubscribe(control);
|
|
244
246
|
|
|
245
|
-
|
|
246
|
-
}
|
|
247
|
-
}
|
|
247
|
+
_connection.client.destroy();
|
|
248
248
|
|
|
249
|
-
|
|
250
|
-
|
|
249
|
+
this.connections.delete(streamId);
|
|
250
|
+
};
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
incomingLoop();
|
|
253
|
+
controlLoop();
|
|
254
|
+
|
|
255
|
+
return true;
|
|
253
256
|
}
|
|
254
257
|
|
|
255
|
-
|
|
258
|
+
$getEnv() {
|
|
256
259
|
return {
|
|
257
260
|
geonix: GeonixVersion,
|
|
258
261
|
node: {
|
|
@@ -264,7 +267,11 @@ export class Service {
|
|
|
264
267
|
mem: process.memoryUsage(),
|
|
265
268
|
rss: process.memoryUsage.rss(),
|
|
266
269
|
cpu: process.cpuUsage()
|
|
267
|
-
}
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
$getServiceInfo() {
|
|
274
|
+
return this.#me;
|
|
268
275
|
}
|
|
269
276
|
|
|
270
277
|
}
|
package/src/Stream.js
CHANGED
|
@@ -1,189 +1,98 @@
|
|
|
1
|
-
import { Readable } from
|
|
2
|
-
import { connection } from
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
export const stats = {}
|
|
10
|
-
|
|
11
|
-
// HTTP stream server
|
|
12
|
-
const streams = {}
|
|
13
|
-
const { port: streamServerPort } = await createServerAtFreePort(http, (req, res) => {
|
|
14
|
-
const stream = streams[decodeURIComponent(req.url.substring(1))]
|
|
15
|
-
if (stream) {
|
|
16
|
-
stream.pipe(res)
|
|
17
|
-
} else {
|
|
18
|
-
res.statusCode = 404
|
|
19
|
-
res.end()
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
}, 40000)
|
|
1
|
+
import { Readable } from "stream";
|
|
2
|
+
import { connection } from "./Connection.js";
|
|
3
|
+
import { picoid, StreamChunker } from "./Util.js";
|
|
4
|
+
|
|
5
|
+
const CHUNK_SIZE = 1024 * 128;
|
|
6
|
+
|
|
7
|
+
export const stats = {};
|
|
23
8
|
|
|
24
9
|
/**
|
|
25
|
-
* Converts
|
|
10
|
+
* Converts data to stream
|
|
26
11
|
*
|
|
27
12
|
* @param {*} data
|
|
28
13
|
* @param {*} automated
|
|
29
14
|
* @returns
|
|
30
15
|
*/
|
|
31
|
-
export function Stream(data, tag =
|
|
16
|
+
export function Stream(data, tag = "_") {
|
|
32
17
|
if (isStream(data))
|
|
33
|
-
return data
|
|
18
|
+
return data;
|
|
34
19
|
|
|
35
|
-
const id = picoid()
|
|
36
|
-
|
|
37
|
-
let readable = data
|
|
20
|
+
const id = picoid();
|
|
21
|
+
let readable = data;
|
|
38
22
|
|
|
39
23
|
// convert Buffer or string to a Readable
|
|
40
24
|
if (!(readable.pipe && readable.readable))
|
|
41
|
-
readable = Readable.from(Buffer.from(data))
|
|
25
|
+
readable = Readable.from(Buffer.from(data));
|
|
42
26
|
|
|
43
27
|
// split the stream is smaller chunks
|
|
44
|
-
const transform = StreamChunker(Math.min(connection.getMaxPayloadSize(), CHUNK_SIZE))
|
|
45
|
-
readable.pipe(transform)
|
|
46
|
-
readable = transform
|
|
47
|
-
|
|
48
|
-
stats[tag] = stats[tag] !== undefined ? stats[tag] + 1 : 1
|
|
49
|
-
|
|
50
|
-
const acDeliveryOverNATS = new AbortController();
|
|
28
|
+
const transform = StreamChunker(Math.min(connection.getMaxPayloadSize(), CHUNK_SIZE));
|
|
29
|
+
readable.pipe(transform);
|
|
30
|
+
readable = transform;
|
|
51
31
|
|
|
52
|
-
|
|
53
|
-
const control = await connection.subscribe(`gx2.stream.${id}.a`, { max: 1 })
|
|
32
|
+
stats[tag] = stats[tag] !== undefined ? stats[tag] + 1 : 1;
|
|
54
33
|
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
acDeliveryOverNATS.abort()
|
|
58
|
-
}, STREAM_TIMEOUT)
|
|
34
|
+
const controlHandler = async () => {
|
|
35
|
+
const control = await connection.subscribe(`gx2.stream.${id}.a`, { max: 1 });
|
|
59
36
|
|
|
60
|
-
|
|
61
|
-
for await (const event of abortableAsyncGenerator(control, acDeliveryOverNATS.signal)) {
|
|
37
|
+
for await (const event of control)
|
|
62
38
|
if (event.data.length === 0) {
|
|
63
|
-
readable.on(
|
|
64
|
-
readable.on(
|
|
65
|
-
connection.publishRaw(`gx2.stream.${id}.b`)
|
|
66
|
-
stats[tag]
|
|
67
|
-
})
|
|
39
|
+
readable.on("data", chunk => connection.publishRaw(`gx2.stream.${id}.b`, chunk));
|
|
40
|
+
readable.on("close", () => {
|
|
41
|
+
connection.publishRaw(`gx2.stream.${id}.b`);
|
|
42
|
+
stats[tag]--;
|
|
43
|
+
});
|
|
68
44
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// cleanup
|
|
72
|
-
clearTimeout(timeout)
|
|
73
|
-
await control.drain()
|
|
74
|
-
await connection.unsubscribe(control)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const deliverOverHTTP = async () => {
|
|
78
|
-
// stop listening for NATS request
|
|
79
|
-
acDeliveryOverNATS.abort()
|
|
80
|
-
|
|
81
|
-
// register stream as available over HTTP
|
|
82
|
-
streams[id] = readable
|
|
45
|
+
};
|
|
83
46
|
|
|
84
|
-
|
|
85
|
-
readable.on('finish', () => {
|
|
86
|
-
delete streams[id]
|
|
87
|
-
stats[tag]--
|
|
88
|
-
})
|
|
89
|
-
}
|
|
47
|
+
controlHandler();
|
|
90
48
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (streamServerPort != -1) {
|
|
94
|
-
result.a = getNetworkAddresses()
|
|
95
|
-
result.p = streamServerPort;
|
|
96
|
-
deliverOverHTTP()
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return result
|
|
49
|
+
return { $: "stream", id };
|
|
100
50
|
}
|
|
101
51
|
|
|
102
52
|
export function isStream(object) {
|
|
103
|
-
return object && typeof object ===
|
|
53
|
+
return object && typeof object === "object" && object.$ === "stream";
|
|
104
54
|
}
|
|
105
55
|
|
|
106
|
-
export async function getReadable(object
|
|
56
|
+
export async function getReadable(object) {
|
|
107
57
|
if (!isStream(object))
|
|
108
|
-
return object
|
|
109
|
-
|
|
110
|
-
if (forceNats) {
|
|
111
|
-
return getReadableOverNATS(object);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
return await getReadableOverHTTP(object);
|
|
116
|
-
} catch (e) {
|
|
117
|
-
return getReadableOverNATS(object);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export async function getReadableOverHTTP(object) {
|
|
122
|
-
if (!object.a) {
|
|
123
|
-
throw new Error('Stream is not available over HTTP')
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
for (const address of object.a) {
|
|
127
|
-
try {
|
|
128
|
-
const response = await new Promise((resolve, reject) => {
|
|
129
|
-
const request = http.request(`http://${address}:${object.p}/${object.id}`, { method: 'GET', timeout: 5000 })
|
|
130
|
-
request.on('response', (response) => {
|
|
131
|
-
resolve(response)
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
request.on('error', (e) => {
|
|
135
|
-
reject(e)
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
request.end()
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
return response
|
|
142
|
-
} catch (e) {
|
|
143
|
-
console.error(`Stream.getReadableOverHTTP.error:`, e)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
throw new Error('No data')
|
|
148
|
-
}
|
|
58
|
+
return object;
|
|
149
59
|
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
const subscription = await connection.subscribe(`gx2.stream.${object.id}.b`)
|
|
60
|
+
const readable = new Readable({ read: () => null });
|
|
61
|
+
const subscription = await connection.subscribe(`gx2.stream.${object.id}.b`);
|
|
153
62
|
|
|
154
63
|
const dataHandler = async () => {
|
|
155
64
|
for await (const event of subscription)
|
|
156
65
|
try {
|
|
157
66
|
if (event.data.length === 0) {
|
|
158
|
-
readable.push(null)
|
|
159
|
-
subscription.drain()
|
|
67
|
+
readable.push(null);
|
|
68
|
+
subscription.drain();
|
|
160
69
|
} else
|
|
161
|
-
readable.push(event.data)
|
|
70
|
+
readable.push(event.data);
|
|
162
71
|
} catch (e) {
|
|
163
|
-
console.error(
|
|
72
|
+
console.error("Stream.getReadable.dataHandler.error:", e);
|
|
164
73
|
}
|
|
165
|
-
}
|
|
166
|
-
dataHandler()
|
|
74
|
+
};
|
|
75
|
+
dataHandler();
|
|
167
76
|
|
|
168
77
|
// kickstart remote stream with a blank message
|
|
169
|
-
await connection.publishRaw(`gx2.stream.${object.id}.a`)
|
|
78
|
+
await connection.publishRaw(`gx2.stream.${object.id}.a`);
|
|
170
79
|
|
|
171
|
-
return readable
|
|
80
|
+
return readable;
|
|
172
81
|
}
|
|
173
82
|
|
|
174
|
-
export async function streamToBuffer(object
|
|
175
|
-
let readable = object
|
|
83
|
+
export async function streamToBuffer(object) {
|
|
84
|
+
let readable = object;
|
|
176
85
|
if (isStream(readable))
|
|
177
|
-
readable = await getReadable(readable
|
|
86
|
+
readable = await getReadable(readable);
|
|
178
87
|
|
|
179
|
-
return Buffer.concat(await readable.toArray())
|
|
88
|
+
return Buffer.concat(await readable.toArray());
|
|
180
89
|
}
|
|
181
90
|
|
|
182
91
|
export async function streamToString(object) {
|
|
183
|
-
let readable = object
|
|
92
|
+
let readable = object;
|
|
184
93
|
if (isStream(readable))
|
|
185
|
-
readable = await getReadable(readable)
|
|
94
|
+
readable = await getReadable(readable);
|
|
186
95
|
|
|
187
|
-
return Buffer.concat(await readable.toArray()).toString()
|
|
96
|
+
return Buffer.concat(await readable.toArray()).toString();
|
|
188
97
|
}
|
|
189
98
|
|