@koeroesi86/node-worker-express 20.10.7-db6bdc3 → 22.3.12-fd2e588
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 +1 -1
- package/constants/index.js +2 -0
- package/examples/public/websocket/worker.js +9 -2
- package/examples/staticWorker.js +46 -7
- package/index.d.ts +4 -1
- package/middleware/bodyParser.js +19 -5
- package/middleware/index.js +194 -269
- package/middleware/workerInvoke.js +17 -1
- package/package.json +6 -6
- package/utils/constructWsMessage.js +27 -0
- package/utils/getClientIp.js +71 -0
- package/utils/isWebSocket.js +16 -0
- package/utils/parseWsMessage.js +25 -0
- package/utils/workerPool.js +22 -24
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Node Worker Express [](https://app.travis-ci.com/Koeroesi86/node-worker-express)
|
|
2
2
|
|
|
3
3
|
Express JS library for [@koeroesi86/node-worker](https://www.npmjs.com/package/@koeroesi86/node-worker)
|
|
4
4
|
|
package/constants/index.js
CHANGED
|
@@ -2,7 +2,9 @@ const WORKER_EVENT = {
|
|
|
2
2
|
REQUEST: 'WORKER_REQUEST',
|
|
3
3
|
REQUEST_ACKNOWLEDGE: 'WORKER_REQUEST_ACK',
|
|
4
4
|
RESPONSE: 'WORKER_RESPONSE',
|
|
5
|
+
RESPONSE_EMIT: 'WORKER_RESPONSE_EMIT',
|
|
5
6
|
RESPONSE_ACKNOWLEDGE: 'WORKER_RESPONSE_ACK',
|
|
7
|
+
WS_MESSAGE_RECEIVE: 'WS_MESSAGE_RECEIVE',
|
|
6
8
|
WS_MESSAGE_SEND: 'WS_MESSAGE_SEND',
|
|
7
9
|
WS_CONNECTION_CLOSE: 'WS_CONNECTION_CLOSE',
|
|
8
10
|
WS_CONNECTION_CLOSE_ACKNOWLEDGE: 'WS_CONNECTION_CLOSE_ACK',
|
|
@@ -9,7 +9,7 @@ const createDigest = key => createHash('sha1')
|
|
|
9
9
|
.digest('base64');
|
|
10
10
|
|
|
11
11
|
module.exports = (event, callback) => {
|
|
12
|
-
if (event.protocol === 'WS') {
|
|
12
|
+
if (event.protocol === 'WS' && event.frame === undefined) {
|
|
13
13
|
const key = (event.headers['sec-websocket-key'] || '').trim();
|
|
14
14
|
const digest = createDigest(key);
|
|
15
15
|
|
|
@@ -32,15 +32,22 @@ module.exports = (event, callback) => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
timers[key] = setInterval(() => {
|
|
35
|
+
const frame = JSON.stringify({ now: Date.now() })
|
|
36
|
+
console.log('outgoing message:', frame);
|
|
35
37
|
callback({
|
|
36
38
|
sendWsMessage: true,
|
|
37
|
-
frame
|
|
39
|
+
frame,
|
|
38
40
|
});
|
|
39
41
|
}, 1000);
|
|
40
42
|
return;
|
|
41
43
|
}
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
if (event.protocol === 'WS' && event.frame !== undefined) {
|
|
47
|
+
console.log('incoming message:', event.frame);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
44
51
|
callback({
|
|
45
52
|
statusCode: 400,
|
|
46
53
|
headers: {
|
package/examples/staticWorker.js
CHANGED
|
@@ -2,9 +2,14 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const crypto = require('crypto');
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
|
+
const { Writable } = require('stream');
|
|
5
6
|
|
|
6
7
|
let timer;
|
|
7
8
|
|
|
9
|
+
const features = {
|
|
10
|
+
enableEmit: false,
|
|
11
|
+
};
|
|
12
|
+
|
|
8
13
|
function debounce(fn = () => {}, timeout = 0) {
|
|
9
14
|
if (timer) clearTimeout(timer);
|
|
10
15
|
timer = setTimeout(() => {
|
|
@@ -133,8 +138,7 @@ function getCharset(body, fileName) {
|
|
|
133
138
|
return 'iso-8859-1';
|
|
134
139
|
|
|
135
140
|
try {
|
|
136
|
-
|
|
137
|
-
return out;
|
|
141
|
+
return execSync(`file -b --mime-encoding ${fileName}`).toString('utf8').trim();
|
|
138
142
|
} catch (e) {
|
|
139
143
|
//
|
|
140
144
|
}
|
|
@@ -169,19 +173,54 @@ const staticWorker = (event, callback = () => {}) => {
|
|
|
169
173
|
isModified = false;
|
|
170
174
|
}
|
|
171
175
|
}
|
|
172
|
-
|
|
173
|
-
callback({
|
|
176
|
+
const response = {
|
|
174
177
|
statusCode: isModified ? 200 : 304,
|
|
175
178
|
headers: {
|
|
176
179
|
'Content-Type': `${contentType}${charset ? `; charset=${charset}` : ''}`,
|
|
177
180
|
'Cache-Control': 'public, max-age=0',
|
|
178
|
-
'Content-Length': bodyBuffer.
|
|
181
|
+
'Content-Length': bodyBuffer.byteLength,
|
|
179
182
|
'ETag': currentEtag,
|
|
180
183
|
...(stats.mtime && { 'Last-Modified': lastModified.toUTCString() }),
|
|
181
184
|
},
|
|
182
|
-
body: bodyBuffer.toString('base64'),
|
|
185
|
+
body: isModified ? bodyBuffer.toString('base64') : '',
|
|
183
186
|
isBase64Encoded: true,
|
|
184
|
-
}
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
if (features.enableEmit && isModified) {
|
|
190
|
+
const writer = new Writable({
|
|
191
|
+
/**
|
|
192
|
+
* @param {Buffer} chunk
|
|
193
|
+
* @param {string} encoding
|
|
194
|
+
* @param {Function} next
|
|
195
|
+
*/
|
|
196
|
+
write(chunk, encoding, next) {
|
|
197
|
+
response.emit = true;
|
|
198
|
+
response.isBase64Encoded = true;
|
|
199
|
+
response.body = chunk.toString('base64');
|
|
200
|
+
// console.log('sending', chunk.length, 'bytes')
|
|
201
|
+
callback(response);
|
|
202
|
+
next();
|
|
203
|
+
},
|
|
204
|
+
destroy() {
|
|
205
|
+
response.isBase64Encoded = false;
|
|
206
|
+
response.emit = true;
|
|
207
|
+
response.body = null;
|
|
208
|
+
callback(response);
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
fs.createReadStream(fileName, {
|
|
213
|
+
// encoding: 'binary',
|
|
214
|
+
// encoding: 'utf-8',
|
|
215
|
+
emitClose: false,
|
|
216
|
+
autoClose: false,
|
|
217
|
+
// highWaterMark: 16,
|
|
218
|
+
start: 0,
|
|
219
|
+
end: Infinity,
|
|
220
|
+
}).pipe(writer);
|
|
221
|
+
} else {
|
|
222
|
+
callback(response);
|
|
223
|
+
}
|
|
185
224
|
} else {
|
|
186
225
|
callback({
|
|
187
226
|
statusCode: 404,
|
package/index.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ declare namespace Middleware {
|
|
|
25
25
|
remoteAddress: string;
|
|
26
26
|
body: string;
|
|
27
27
|
rootPath: string;
|
|
28
|
+
closed?: boolean;
|
|
29
|
+
frame?: string;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
type WorkerInputEvent = {
|
|
@@ -43,6 +45,7 @@ declare namespace Middleware {
|
|
|
43
45
|
statusCode: number;
|
|
44
46
|
headers?: { [key: string]: string };
|
|
45
47
|
isBase64Encoded?: boolean;
|
|
48
|
+
emit?: boolean;
|
|
46
49
|
body?: string;
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -51,7 +54,7 @@ declare namespace Middleware {
|
|
|
51
54
|
frame: string;
|
|
52
55
|
}
|
|
53
56
|
|
|
54
|
-
|
|
57
|
+
type InvokableWorker = (event: RequestEvent, callback: (e: ResponseEvent) => void) => void;
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
declare module '@koeroesi86/node-worker-express' {
|
package/middleware/bodyParser.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
const
|
|
1
|
+
const ignoredMethods = ['get', 'delete', 'options', 'head'];
|
|
2
|
+
|
|
3
|
+
const bodyParser = (config = { limitRequestBody: 1000000, shouldError: true }) => (request, response, next) => {
|
|
2
4
|
let body = '';
|
|
5
|
+
|
|
6
|
+
if (ignoredMethods.includes(request.method.toLowerCase())) {
|
|
7
|
+
request.body = body;
|
|
8
|
+
next();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
request.on('data', data => {
|
|
4
13
|
body += data;
|
|
5
14
|
|
|
@@ -7,12 +16,17 @@ const bodyParser = (config = { limitRequestBody: 1000000 }) => (request, respons
|
|
|
7
16
|
// 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
|
|
8
17
|
if (body.length > (config.limitRequestBody || 1000000)) {
|
|
9
18
|
body = "";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
|
|
20
|
+
if (config.shouldError) {
|
|
21
|
+
response.writeHead(413, { 'Content-Type': 'text/plain' });
|
|
22
|
+
response.end();
|
|
23
|
+
request.connection.destroy();
|
|
24
|
+
} else {
|
|
25
|
+
next();
|
|
26
|
+
}
|
|
14
27
|
}
|
|
15
28
|
});
|
|
29
|
+
|
|
16
30
|
request.on('end', () => {
|
|
17
31
|
request.body = body;
|
|
18
32
|
next();
|
package/middleware/index.js
CHANGED
|
@@ -4,149 +4,22 @@ const fs = require('fs');
|
|
|
4
4
|
const url = require('url');
|
|
5
5
|
const { WORKER_EVENT } = require('../constants');
|
|
6
6
|
const WorkerPool = require('../utils/workerPool');
|
|
7
|
-
const
|
|
7
|
+
const isWebSocket = require('../utils/isWebSocket');
|
|
8
|
+
const parseWsMessage = require('../utils/parseWsMessage');
|
|
9
|
+
const constructWsMessage = require('../utils/constructWsMessage');
|
|
10
|
+
const getClientIp = require('../utils/getClientIp');
|
|
11
|
+
const createBodyParser = require('./bodyParser');
|
|
8
12
|
|
|
9
|
-
const
|
|
13
|
+
const ForbiddenPaths = [
|
|
10
14
|
'..'
|
|
11
15
|
];
|
|
12
16
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
const Protocols = {
|
|
18
|
+
http: 'HTTP',
|
|
19
|
+
websocket: 'WS',
|
|
16
20
|
};
|
|
17
21
|
|
|
18
|
-
const
|
|
19
|
-
if (request.method !== 'GET') return false;
|
|
20
|
-
|
|
21
|
-
const connection = request.headers.connection || '';
|
|
22
|
-
const upgrade = request.headers.upgrade || '';
|
|
23
|
-
|
|
24
|
-
return request.method === 'GET' &&
|
|
25
|
-
connection.toLowerCase().split(/ *, */).indexOf('upgrade') >= 0 &&
|
|
26
|
-
upgrade.toLowerCase() === 'websocket';
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* TODO: improve
|
|
31
|
-
* @param {Buffer} data
|
|
32
|
-
* @returns {string}
|
|
33
|
-
*/
|
|
34
|
-
const parseWsMessage = data => {
|
|
35
|
-
const dl = data[1] & 127;
|
|
36
|
-
let ifm = 2;
|
|
37
|
-
if (dl === 126) {
|
|
38
|
-
ifm = 4;
|
|
39
|
-
} else if (dl === 127) {
|
|
40
|
-
ifm = 10;
|
|
41
|
-
}
|
|
42
|
-
let i = ifm + 4;
|
|
43
|
-
const masks = data.slice(ifm, i);
|
|
44
|
-
let index = 0;
|
|
45
|
-
let output = "";
|
|
46
|
-
const l = data.length;
|
|
47
|
-
while (i < l) {
|
|
48
|
-
output += String.fromCharCode(data[i++] ^ masks[index++ % 4]);
|
|
49
|
-
}
|
|
50
|
-
return output;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* TODO: improve
|
|
55
|
-
* @param {String} text
|
|
56
|
-
* @returns {Buffer}
|
|
57
|
-
*/
|
|
58
|
-
const constructWsMessage = text => {
|
|
59
|
-
const jsonByteLength = Buffer.byteLength(text);
|
|
60
|
-
// Note: we're not supporting > 65535 byte payloads at this stage
|
|
61
|
-
const lengthByteCount = jsonByteLength < 126 ? 0 : 2;
|
|
62
|
-
const payloadLength = lengthByteCount === 0 ? jsonByteLength : 126;
|
|
63
|
-
const buffer = Buffer.alloc(2 + lengthByteCount + jsonByteLength);
|
|
64
|
-
// Write out the first byte, using opcode `1` to indicate that the message
|
|
65
|
-
// payload contains text data
|
|
66
|
-
buffer.writeUInt8(0b10000001, 0);
|
|
67
|
-
buffer.writeUInt8(payloadLength, 1);
|
|
68
|
-
// Write the length of the JSON payload to the second byte
|
|
69
|
-
let payloadOffset = 2;
|
|
70
|
-
if (lengthByteCount > 0) {
|
|
71
|
-
buffer.writeUInt16BE(jsonByteLength, 2);
|
|
72
|
-
payloadOffset += lengthByteCount;
|
|
73
|
-
}
|
|
74
|
-
// Write the JSON data to the data buffer
|
|
75
|
-
buffer.write(text, payloadOffset);
|
|
76
|
-
return buffer;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* @param {module:http~IncomingMessage} request
|
|
81
|
-
* @returns {string}
|
|
82
|
-
*/
|
|
83
|
-
const getClientIp = request => {
|
|
84
|
-
if (request.headers['x-client-ip']) {
|
|
85
|
-
return request.headers['x-client-ip'];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (request.headers['x-forwarded-for']) {
|
|
89
|
-
return request.headers['x-forwarded-for'];
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (request.headers['cf-connecting-ip']) {
|
|
93
|
-
return request.headers['cf-connecting-ip'];
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (request.headers['fastly-client-ip']) {
|
|
97
|
-
return request.headers['fastly-client-ip'];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (request.headers['true-client-ip']) {
|
|
101
|
-
return request.headers['true-client-ip'];
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (request.headers['x-real-ip']) {
|
|
105
|
-
return request.headers['x-real-ip'];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (request.headers['x-cluster-client-ip']) {
|
|
109
|
-
return request.headers['x-cluster-client-ip'];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (request.headers['x-forwarded']) {
|
|
113
|
-
return request.headers['x-forwarded'];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (request.headers['forwarded-for']) {
|
|
117
|
-
return request.headers['forwarded-for'];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (request.headers.forwarded) {
|
|
121
|
-
return request.headers.forwarded;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (request.connection) {
|
|
125
|
-
if (request.connection.remoteAddress) {
|
|
126
|
-
return request.connection.remoteAddress;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (request.connection.socket && request.connection.socket.remoteAddress) {
|
|
130
|
-
return request.connection.socket.remoteAddress;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (request.socket && request.socket.remoteAddress) {
|
|
135
|
-
return request.socket.remoteAddress;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
if (request.info && request.info.remoteAddress) {
|
|
139
|
-
return request.info.remoteAddress;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (request.requestContext && request.requestContext.identity && request.requestContext.identity.sourceIp) {
|
|
143
|
-
return request.requestContext.identity.sourceIp;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return '';
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
const DEFAULT_OPTIONS = {
|
|
22
|
+
const DefaultOptions = {
|
|
150
23
|
root: null,
|
|
151
24
|
limit: 0,
|
|
152
25
|
limitPerPath: 0,
|
|
@@ -159,6 +32,11 @@ const DEFAULT_OPTIONS = {
|
|
|
159
32
|
},
|
|
160
33
|
onExit: () => {
|
|
161
34
|
},
|
|
35
|
+
onForbiddenPath: (request, response) => {
|
|
36
|
+
response.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
37
|
+
response.end();
|
|
38
|
+
request.connection.destroy();
|
|
39
|
+
},
|
|
162
40
|
index: [],
|
|
163
41
|
env: {},
|
|
164
42
|
staticWorker: path.resolve(__dirname, '../examples/staticWorker.js'),
|
|
@@ -172,7 +50,7 @@ const DEFAULT_OPTIONS = {
|
|
|
172
50
|
*/
|
|
173
51
|
const workerMiddleware = (options) => {
|
|
174
52
|
const config = {
|
|
175
|
-
...
|
|
53
|
+
...DefaultOptions,
|
|
176
54
|
...(options && options),
|
|
177
55
|
};
|
|
178
56
|
if (!config.root) {
|
|
@@ -184,39 +62,51 @@ const workerMiddleware = (options) => {
|
|
|
184
62
|
onExit: config.onExit,
|
|
185
63
|
idleCheckTimeout: config.idleCheckTimeout,
|
|
186
64
|
});
|
|
65
|
+
const bodyParser = createBodyParser({ limitRequestBody: config.limitRequestBody });
|
|
66
|
+
const aliasCache = {};
|
|
67
|
+
const workerCache = {};
|
|
187
68
|
|
|
188
|
-
return (request, response, next) => {
|
|
69
|
+
return async (request, response, next) => {
|
|
189
70
|
const {
|
|
190
71
|
query: queryStringParameters,
|
|
191
72
|
pathname
|
|
192
73
|
} = url.parse(request.url, true);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
response.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
206
|
-
response.end();
|
|
207
|
-
request.connection.destroy();
|
|
208
|
-
return next();
|
|
74
|
+
request.pathFragments = pathname.split(/\//gi).filter(Boolean);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
let isWorker = false;
|
|
78
|
+
const pathFragments = pathname.split(/\//gi).filter(Boolean);
|
|
79
|
+
let currentPathFragments = pathFragments.slice(0);
|
|
80
|
+
let pathExists = false;
|
|
81
|
+
|
|
82
|
+
await new Promise((resolve, reject) => {
|
|
83
|
+
if (request.pathFragments.find(p => ForbiddenPaths.includes(p))) {
|
|
84
|
+
config.onForbiddenPath(request, response);
|
|
85
|
+
reject();
|
|
209
86
|
}
|
|
87
|
+
resolve();
|
|
88
|
+
});
|
|
210
89
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
90
|
+
await Promise.race([
|
|
91
|
+
new Promise((res, rej) => setTimeout(rej, config.limitRequestTimeout)),
|
|
92
|
+
new Promise(res => bodyParser(request, response, res)),
|
|
93
|
+
]);
|
|
94
|
+
let indexPath;
|
|
95
|
+
|
|
96
|
+
if (aliasCache[pathname] && fs.existsSync(aliasCache[pathname])) {
|
|
97
|
+
isWorker = true;
|
|
98
|
+
indexPath = aliasCache[pathname];
|
|
99
|
+
} else if (workerCache[pathname] && fs.existsSync(workerCache[pathname])) {
|
|
100
|
+
isWorker = true;
|
|
101
|
+
indexPath = workerCache[pathname];
|
|
102
|
+
} else {
|
|
103
|
+
indexPath = path.join(rootPath, ...currentPathFragments)
|
|
104
|
+
|
|
105
|
+
for (let i = currentPathFragments.length; i >= 0 && !pathExists; i--) {
|
|
215
106
|
if (pathExists) continue;
|
|
216
107
|
currentPathFragments.splice(i);
|
|
217
108
|
const currentPath = path.join(rootPath, ...currentPathFragments);
|
|
218
109
|
pathExists = fs.existsSync(currentPath);
|
|
219
|
-
|
|
220
110
|
if (!pathExists) continue;
|
|
221
111
|
const currentStats = fs.statSync(currentPath);
|
|
222
112
|
|
|
@@ -225,138 +115,173 @@ const workerMiddleware = (options) => {
|
|
|
225
115
|
pathExists = !!config.index.find(indexFile => {
|
|
226
116
|
const checkIndexFilePath = path.join(currentPath, indexFile);
|
|
227
117
|
if (fs.existsSync(checkIndexFilePath)) {
|
|
228
|
-
|
|
118
|
+
isWorker = true;
|
|
229
119
|
indexPath = checkIndexFilePath;
|
|
120
|
+
aliasCache[pathname] = checkIndexFilePath;
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
aliasCache[pathname] = null;
|
|
123
|
+
delete aliasCache[pathname];
|
|
124
|
+
}, 5000);
|
|
230
125
|
return true;
|
|
231
126
|
}
|
|
232
127
|
});
|
|
233
128
|
if (pathExists) break;
|
|
234
129
|
} else if (pathExists && currentStats.isFile()) {
|
|
235
130
|
if (config.index.includes(currentPathFragments[currentPathFragments.length - 1])) {
|
|
236
|
-
|
|
131
|
+
isWorker = true;
|
|
132
|
+
workerCache[pathname] = currentPath;
|
|
133
|
+
setTimeout(() => {
|
|
134
|
+
workerCache[pathname] = null;
|
|
135
|
+
delete workerCache[pathname];
|
|
136
|
+
}, 5000);
|
|
237
137
|
}
|
|
238
138
|
}
|
|
239
139
|
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @type {RequestEvent} */
|
|
143
|
+
const event = {
|
|
144
|
+
httpMethod: request.method.toUpperCase(),
|
|
145
|
+
protocol: isWebSocket(request) ? Protocols.websocket : Protocols.http,
|
|
146
|
+
path: pathname,
|
|
147
|
+
pathFragments: pathFragments,
|
|
148
|
+
queryStringParameters: JSON.parse(JSON.stringify(queryStringParameters)),
|
|
149
|
+
headers: request.headers,
|
|
150
|
+
remoteAddress: getClientIp(request),
|
|
151
|
+
body: `${request.body}`,
|
|
152
|
+
rootPath: rootPath,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const worker = isWorker ? await workerPool.getWorker(
|
|
156
|
+
indexPath,
|
|
157
|
+
{
|
|
158
|
+
stdio: [ 'pipe', 'pipe', 'pipe', 'ipc' ],
|
|
159
|
+
env: { ...process.env, ...config.env },
|
|
160
|
+
cwd: config.cwd,
|
|
161
|
+
},
|
|
162
|
+
typeof config.limitPerPath === "function" ? config.limitPerPath(indexPath) : config.limitPerPath
|
|
163
|
+
) : await workerPool.getWorker(
|
|
164
|
+
config.staticWorker,
|
|
165
|
+
{
|
|
166
|
+
cwd: process.cwd(),
|
|
167
|
+
stdio: [ 'pipe', 'pipe', 'pipe', 'ipc' ],
|
|
168
|
+
},
|
|
169
|
+
1
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
const requestId = uuid();
|
|
173
|
+
|
|
174
|
+
let firstReceived = false;
|
|
175
|
+
const requestSocketListener = data => {
|
|
176
|
+
const frame = parseWsMessage(data);
|
|
177
|
+
|
|
178
|
+
// TODO: filter open frame better
|
|
179
|
+
if (data.length === 8 && !firstReceived) {
|
|
180
|
+
firstReceived = true;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
240
183
|
|
|
241
|
-
|
|
242
|
-
const event = {
|
|
243
|
-
httpMethod: request.method.toUpperCase(),
|
|
244
|
-
protocol: isWebSocket(request) ? PROTOCOLS.WEBSOCKET : PROTOCOLS.HTTP,
|
|
245
|
-
path: pathname,
|
|
246
|
-
pathFragments: pathFragments,
|
|
247
|
-
queryStringParameters: JSON.parse(JSON.stringify(queryStringParameters)),
|
|
248
|
-
headers: request.headers,
|
|
249
|
-
remoteAddress: getClientIp(request),
|
|
250
|
-
body: `${request.body}`,
|
|
251
|
-
rootPath: rootPath,
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
Promise.resolve()
|
|
255
|
-
.then(() => isIndex
|
|
256
|
-
? workerPool.getWorker(
|
|
257
|
-
indexPath,
|
|
258
|
-
{
|
|
259
|
-
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
260
|
-
env: { PATH: process.env.PATH, ...config.env },
|
|
261
|
-
cwd: config.cwd,
|
|
262
|
-
},
|
|
263
|
-
typeof config.limitPerPath === "function" ? config.limitPerPath(indexPath) : config.limitPerPath
|
|
264
|
-
)
|
|
265
|
-
: workerPool.getWorker(
|
|
266
|
-
config.staticWorker,
|
|
267
|
-
{
|
|
268
|
-
cwd: process.cwd(),
|
|
269
|
-
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
270
|
-
},
|
|
271
|
-
1
|
|
272
|
-
)
|
|
273
|
-
)
|
|
274
|
-
.then(worker => {
|
|
275
|
-
// worker.busy = true;
|
|
276
|
-
const requestId = uuid();
|
|
277
|
-
|
|
278
|
-
const requestSocketListener = data => {
|
|
279
|
-
console.info(`[${requestId}] [ws data in] ${parseWsMessage(data)}`);
|
|
280
|
-
};
|
|
281
|
-
if (event.protocol === PROTOCOLS.WEBSOCKET) {
|
|
282
|
-
request.socket.on('data', requestSocketListener);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
worker.instance.stdout.off('data', config.onStdout);
|
|
286
|
-
worker.instance.stdout.on('data', config.onStdout);
|
|
287
|
-
|
|
288
|
-
worker.instance.stderr.off('data', config.onStderr);
|
|
289
|
-
worker.instance.stderr.on('data', config.onStderr);
|
|
184
|
+
firstReceived = true;
|
|
290
185
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const bufferEncoding = event.isBase64Encoded ? 'base64' : 'utf8';
|
|
186
|
+
worker.postMessage({
|
|
187
|
+
type: WORKER_EVENT.WS_MESSAGE_RECEIVE,
|
|
188
|
+
requestId,
|
|
189
|
+
event: { ...event, frame }
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
if (event.protocol === Protocols.websocket) {
|
|
193
|
+
request.socket.on('data', requestSocketListener);
|
|
194
|
+
}
|
|
301
195
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
response.end();
|
|
305
|
-
}
|
|
196
|
+
worker.instance.stdout.off('data', config.onStdout);
|
|
197
|
+
worker.instance.stdout.on('data', config.onStdout);
|
|
306
198
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
request.socket.write(constructWsMessage(responseEvent.event.frame));
|
|
310
|
-
}
|
|
199
|
+
worker.instance.stderr.off('data', config.onStderr);
|
|
200
|
+
worker.instance.stderr.on('data', config.onStderr);
|
|
311
201
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
202
|
+
const messageListener = responseEvent => {
|
|
203
|
+
if (responseEvent.requestId === requestId) {
|
|
204
|
+
if (responseEvent.type === WORKER_EVENT.RESPONSE) {
|
|
205
|
+
worker.postMessage({
|
|
206
|
+
type: WORKER_EVENT.RESPONSE_ACKNOWLEDGE,
|
|
207
|
+
requestId,
|
|
208
|
+
});
|
|
209
|
+
/** @type {ResponseEvent|WSFrameEvent} event */
|
|
210
|
+
const { event } = responseEvent;
|
|
211
|
+
const bufferEncoding = event.isBase64Encoded ? 'base64' : 'utf8';
|
|
317
212
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
event
|
|
323
|
-
});
|
|
324
|
-
if (request.socket && request.socket.off) request.socket.off('close', requestCloseListener);
|
|
325
|
-
worker.removeEventListener('message', messageListener);
|
|
326
|
-
};
|
|
327
|
-
request.socket.on('close', requestCloseListener);
|
|
213
|
+
response.writeHead(event.statusCode, event.headers);
|
|
214
|
+
response.write(Buffer.from(event.body, bufferEncoding));
|
|
215
|
+
response.end();
|
|
216
|
+
}
|
|
328
217
|
|
|
329
|
-
|
|
218
|
+
if (responseEvent.type === WORKER_EVENT.RESPONSE_EMIT) {
|
|
330
219
|
worker.postMessage({
|
|
331
|
-
type: WORKER_EVENT.
|
|
220
|
+
type: WORKER_EVENT.RESPONSE_ACKNOWLEDGE,
|
|
332
221
|
requestId,
|
|
333
|
-
event,
|
|
334
222
|
});
|
|
223
|
+
/** @type {ResponseEvent|WSFrameEvent} event */
|
|
224
|
+
const { event } = responseEvent;
|
|
225
|
+
const bufferEncoding = event.isBase64Encoded ? 'base64' : 'utf8';
|
|
335
226
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
if (request && request.off) {
|
|
339
|
-
request.off('close', cleanupConnection);
|
|
340
|
-
request.off('aborted', cleanupConnection);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (request.socket && request.socket.off) {
|
|
344
|
-
request.socket.off('data', requestSocketListener);
|
|
345
|
-
request.socket.off('close', requestCloseListener);
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
request.on('aborted', cleanupConnection);
|
|
349
|
-
if (event.protocol === PROTOCOLS.HTTP) {
|
|
350
|
-
request.on('close', cleanupConnection);
|
|
351
|
-
response.on('finish', cleanupConnection)
|
|
227
|
+
if (!response.headersSent) {
|
|
228
|
+
response.writeHead(event.statusCode, event.headers);
|
|
352
229
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
230
|
+
if (event.body !== null) {
|
|
231
|
+
response.write(Buffer.from(event.body, bufferEncoding).toString());
|
|
232
|
+
} else {
|
|
233
|
+
response.end();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (responseEvent.type === WORKER_EVENT.WS_MESSAGE_SEND) {
|
|
238
|
+
request.socket.write(constructWsMessage(responseEvent.event.frame));
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (responseEvent.type === WORKER_EVENT.REQUEST_ACKNOWLEDGE) {
|
|
242
|
+
worker.busy = false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const requestCloseListener = () => {
|
|
248
|
+
worker.postMessage({
|
|
249
|
+
type: WORKER_EVENT.WS_CONNECTION_CLOSE,
|
|
250
|
+
requestId,
|
|
251
|
+
event
|
|
252
|
+
});
|
|
253
|
+
if (request.socket && request.socket.off) request.socket.off('close', requestCloseListener);
|
|
254
|
+
worker.removeEventListener('message', messageListener);
|
|
255
|
+
};
|
|
256
|
+
request.socket.on('close', requestCloseListener);
|
|
257
|
+
|
|
258
|
+
worker.addEventListener('message', messageListener);
|
|
259
|
+
worker.postMessage({
|
|
260
|
+
type: WORKER_EVENT.REQUEST,
|
|
261
|
+
requestId,
|
|
262
|
+
event,
|
|
359
263
|
});
|
|
264
|
+
|
|
265
|
+
const cleanupConnection = () => {
|
|
266
|
+
worker.removeEventListener('message', messageListener);
|
|
267
|
+
if (request && request.off) {
|
|
268
|
+
request.off('close', cleanupConnection);
|
|
269
|
+
request.off('aborted', cleanupConnection);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (request.socket && request.socket.off) {
|
|
273
|
+
request.socket.off('data', requestSocketListener);
|
|
274
|
+
request.socket.off('close', requestCloseListener);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
request.on('aborted', cleanupConnection);
|
|
278
|
+
if (event.protocol === Protocols.http) {
|
|
279
|
+
request.on('close', cleanupConnection);
|
|
280
|
+
response.on('finish', cleanupConnection)
|
|
281
|
+
}
|
|
282
|
+
} catch (e) {
|
|
283
|
+
next(e);
|
|
284
|
+
}
|
|
360
285
|
};
|
|
361
286
|
};
|
|
362
287
|
|
|
@@ -15,7 +15,22 @@ function messageListener(message) {
|
|
|
15
15
|
const callback = responseEvent => {
|
|
16
16
|
/** @type {WorkerOutputEvent} e */
|
|
17
17
|
const e = {
|
|
18
|
-
type:
|
|
18
|
+
type: WORKER_EVENT.RESPONSE,
|
|
19
|
+
requestId: message.requestId,
|
|
20
|
+
event: responseEvent,
|
|
21
|
+
};
|
|
22
|
+
if (responseEvent.sendWsMessage) e.type = WORKER_EVENT.WS_MESSAGE_SEND;
|
|
23
|
+
if (responseEvent.emit) e.type = WORKER_EVENT.RESPONSE_EMIT;
|
|
24
|
+
process.send(e);
|
|
25
|
+
};
|
|
26
|
+
worker(message.event, callback);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (message.type === WORKER_EVENT.WS_MESSAGE_RECEIVE) {
|
|
30
|
+
const callback = responseEvent => {
|
|
31
|
+
/** @type {WorkerOutputEvent} e */
|
|
32
|
+
const e = {
|
|
33
|
+
type: WORKER_EVENT.WS_MESSAGE_SEND,
|
|
19
34
|
requestId: message.requestId,
|
|
20
35
|
event: responseEvent,
|
|
21
36
|
}
|
|
@@ -23,6 +38,7 @@ function messageListener(message) {
|
|
|
23
38
|
};
|
|
24
39
|
worker(message.event, callback);
|
|
25
40
|
}
|
|
41
|
+
|
|
26
42
|
if (message.type === WORKER_EVENT.WS_CONNECTION_CLOSE) {
|
|
27
43
|
/** @type {ResponseEvent} responseEvent */
|
|
28
44
|
worker({ ...message.event, closed: true }, responseEvent => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koeroesi86/node-worker-express",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "v22.03.12-fd2e588",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"author": {
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
"license": "MIT",
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@koeroesi86/node-worker": "^1.0.3-6bc3bc7",
|
|
22
|
-
"uuid": "^
|
|
22
|
+
"uuid": "^8.3.2"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/express": "^4.17.
|
|
26
|
-
"@types/node": "^
|
|
27
|
-
"express": "^4.17.
|
|
28
|
-
"nodemon": "^2.0.
|
|
25
|
+
"@types/express": "^4.17.13",
|
|
26
|
+
"@types/node": "^17.0.21",
|
|
27
|
+
"express": "^4.17.3",
|
|
28
|
+
"nodemon": "^2.0.15"
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO: improve
|
|
3
|
+
* @param {String} text
|
|
4
|
+
* @returns {Buffer}
|
|
5
|
+
*/
|
|
6
|
+
const constructWsMessage = text => {
|
|
7
|
+
const jsonByteLength = Buffer.byteLength(text);
|
|
8
|
+
// Note: we're not supporting > 65535 byte payloads at this stage
|
|
9
|
+
const lengthByteCount = jsonByteLength < 126 ? 0 : 2;
|
|
10
|
+
const payloadLength = lengthByteCount === 0 ? jsonByteLength : 126;
|
|
11
|
+
const buffer = Buffer.alloc(2 + lengthByteCount + jsonByteLength);
|
|
12
|
+
// Write out the first byte, using opcode `1` to indicate that the message
|
|
13
|
+
// payload contains text data
|
|
14
|
+
buffer.writeUInt8(0b10000001, 0);
|
|
15
|
+
buffer.writeUInt8(payloadLength, 1);
|
|
16
|
+
// Write the length of the JSON payload to the second byte
|
|
17
|
+
let payloadOffset = 2;
|
|
18
|
+
if (lengthByteCount > 0) {
|
|
19
|
+
buffer.writeUInt16BE(jsonByteLength, 2);
|
|
20
|
+
payloadOffset += lengthByteCount;
|
|
21
|
+
}
|
|
22
|
+
// Write the JSON data to the data buffer
|
|
23
|
+
buffer.write(text, payloadOffset);
|
|
24
|
+
return buffer;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = constructWsMessage;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {module:http~IncomingMessage} request
|
|
3
|
+
* @returns {string}
|
|
4
|
+
*/
|
|
5
|
+
const getClientIp = request => {
|
|
6
|
+
if (request.headers['x-client-ip']) {
|
|
7
|
+
return request.headers['x-client-ip'];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (request.headers['x-forwarded-for']) {
|
|
11
|
+
return request.headers['x-forwarded-for'];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (request.headers['cf-connecting-ip']) {
|
|
15
|
+
return request.headers['cf-connecting-ip'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (request.headers['fastly-client-ip']) {
|
|
19
|
+
return request.headers['fastly-client-ip'];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (request.headers['true-client-ip']) {
|
|
23
|
+
return request.headers['true-client-ip'];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (request.headers['x-real-ip']) {
|
|
27
|
+
return request.headers['x-real-ip'];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (request.headers['x-cluster-client-ip']) {
|
|
31
|
+
return request.headers['x-cluster-client-ip'];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (request.headers['x-forwarded']) {
|
|
35
|
+
return request.headers['x-forwarded'];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (request.headers['forwarded-for']) {
|
|
39
|
+
return request.headers['forwarded-for'];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (request.headers.forwarded) {
|
|
43
|
+
return request.headers.forwarded;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (request.connection) {
|
|
47
|
+
if (request.connection.remoteAddress) {
|
|
48
|
+
return request.connection.remoteAddress;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (request.connection.socket && request.connection.socket.remoteAddress) {
|
|
52
|
+
return request.connection.socket.remoteAddress;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (request.socket && request.socket.remoteAddress) {
|
|
57
|
+
return request.socket.remoteAddress;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (request.info && request.info.remoteAddress) {
|
|
61
|
+
return request.info.remoteAddress;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (request.requestContext && request.requestContext.identity && request.requestContext.identity.sourceIp) {
|
|
65
|
+
return request.requestContext.identity.sourceIp;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return '';
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
module.exports = getClientIp;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {module:http~IncomingMessage} request
|
|
3
|
+
* @returns {boolean}
|
|
4
|
+
*/
|
|
5
|
+
const isWebSocket = request => {
|
|
6
|
+
if (request.method !== 'GET') return false;
|
|
7
|
+
|
|
8
|
+
const connection = request.headers.connection || '';
|
|
9
|
+
const upgrade = request.headers.upgrade || '';
|
|
10
|
+
|
|
11
|
+
return request.method === 'GET' &&
|
|
12
|
+
connection.toLowerCase().split(/ *, */).indexOf('upgrade') >= 0 &&
|
|
13
|
+
upgrade.toLowerCase() === 'websocket';
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
module.exports = isWebSocket;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TODO: improve
|
|
3
|
+
* @param {Buffer} data
|
|
4
|
+
* @returns {string}
|
|
5
|
+
*/
|
|
6
|
+
const parseWsMessage = data => {
|
|
7
|
+
const dl = data[1] & 127;
|
|
8
|
+
let ifm = 2;
|
|
9
|
+
if (dl === 126) {
|
|
10
|
+
ifm = 4;
|
|
11
|
+
} else if (dl === 127) {
|
|
12
|
+
ifm = 10;
|
|
13
|
+
}
|
|
14
|
+
let i = ifm + 4;
|
|
15
|
+
const masks = data.slice(ifm, i);
|
|
16
|
+
let index = 0;
|
|
17
|
+
let output = "";
|
|
18
|
+
const l = data.length;
|
|
19
|
+
while (i < l) {
|
|
20
|
+
output += String.fromCharCode(data[i++] ^ masks[index++ % 4]);
|
|
21
|
+
}
|
|
22
|
+
return output;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = parseWsMessage;
|
package/utils/workerPool.js
CHANGED
|
@@ -14,7 +14,7 @@ const createWorkerCommand = workerPath => {
|
|
|
14
14
|
switch (ext) {
|
|
15
15
|
case '.js':
|
|
16
16
|
default:
|
|
17
|
-
return `node ${path.resolve(__dirname, '../middleware/workerInvoke.js')} ${workerPath}`;
|
|
17
|
+
return `node --expose-gc ${path.resolve(__dirname, '../middleware/workerInvoke.js')} ${workerPath}`;
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
20
|
|
|
@@ -67,36 +67,34 @@ class WorkerPool {
|
|
|
67
67
|
|| (this.overallLimit > 0 && this.getWorkerCount() >= this.overallLimit);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
getWorker(workerPath, options = {}, limit = 0) {
|
|
70
|
+
async getWorker(workerPath, options = {}, limit = 0) {
|
|
71
71
|
const nonBusyId = this.getNonBusyId(workerPath);
|
|
72
72
|
// TODO: tidy up
|
|
73
73
|
if (nonBusyId !== undefined) {
|
|
74
|
-
return
|
|
74
|
+
return this.workers[workerPath][nonBusyId];
|
|
75
75
|
} else if (this.isBeyondLimit(workerPath, limit) || this._creating) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.then(() => this.getWorker(workerPath, options, limit));
|
|
76
|
+
await new Promise(r => setTimeout(r, this.idleCheckTimeout));
|
|
77
|
+
return this.getWorker(workerPath, options, limit);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
this._creating = true;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
});
|
|
81
|
+
const id = uuid();
|
|
82
|
+
const instance = new Worker(createWorkerCommand(workerPath), options);
|
|
83
|
+
|
|
84
|
+
if (!this.workers[workerPath]) {
|
|
85
|
+
this.workers[workerPath] = {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
instance.addEventListenerOnce('close', code => {
|
|
89
|
+
this.workers[workerPath][id] = null;
|
|
90
|
+
delete this.workers[workerPath][id];
|
|
91
|
+
this.onExit(code, workerPath, id);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.workers[workerPath][id] = instance;
|
|
95
|
+
|
|
96
|
+
this._creating = false;
|
|
97
|
+
return instance;
|
|
100
98
|
}
|
|
101
99
|
}
|
|
102
100
|
|