@koeroesi86/node-worker-express 26.10.36903641712-master → 26.10.37633705232-master
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 +44 -0
- package/dist/constants/index.d.ts +24 -0
- package/dist/index-9a1547f9.js +76 -0
- package/dist/index-9a1547f9.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +580 -180
- package/dist/index.js.map +1 -1
- package/dist/staticWorker.js +114 -74
- package/dist/staticWorker.js.map +1 -1
- package/dist/streamResponse.d.ts +21 -0
- package/dist/streamResponse.js +47 -0
- package/dist/streamResponse.js.map +1 -0
- package/dist/types/index.d.ts +59 -4
- package/dist/utils/createProbe.d.ts +14 -0
- package/dist/utils/getFileInfo.d.ts +12 -0
- package/dist/utils/hasBody.d.ts +4 -0
- package/dist/utils/isInside.d.ts +3 -0
- package/dist/utils/isNotModified.d.ts +3 -0
- package/dist/utils/metrics.d.ts +34 -0
- package/dist/utils/resolvePath.d.ts +2 -1
- package/dist/utils/takeSmallBody.d.ts +10 -0
- package/dist/utils/ttlCache.d.ts +15 -0
- package/dist/utils/workerBusyError.d.ts +6 -0
- package/dist/utils/workerPool.d.ts +102 -8
- package/dist/utils/workerUnavailableError.d.ts +10 -0
- package/dist/workerInvoke.js +154 -9
- package/dist/workerInvoke.js.map +1 -1
- package/package.json +7 -2
- package/dist/fileExists-a913e146.js +0 -11
- package/dist/fileExists-a913e146.js.map +0 -1
- package/dist/index-749bd23e.js +0 -47
- package/dist/index-749bd23e.js.map +0 -1
- package/dist/middleware/bodyParser.d.ts +0 -6
- package/dist/utils/getEtag.d.ts +0 -2
package/README.md
CHANGED
|
@@ -21,3 +21,47 @@ pnpm --filter @koeroesi86/node-worker-express start
|
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
For all options see [types](https://github.com/Koeroesi86/node-webserver/blob/master/packages/node-worker-express/src/types/index.ts).
|
|
24
|
+
|
|
25
|
+
## Streaming a response
|
|
26
|
+
|
|
27
|
+
A worker answers a request by calling `callback` once. To send a body that is generated while it goes out, or is too big to hold in memory, use `streamResponse`:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
const { streamResponse } = require('@koeroesi86/node-worker-express');
|
|
31
|
+
|
|
32
|
+
module.exports = async (event, callback) => {
|
|
33
|
+
async function* lines() {
|
|
34
|
+
for (let index = 0; index < 1000; index += 1) yield `line ${index}\n`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
await streamResponse(callback, { headers: { 'Content-Type': 'text/plain' } }, lines());
|
|
38
|
+
};
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The body can be a `Readable` or any async iterable of `Buffer`, `Uint8Array` or `string`. Parts are sent a few at a time and the next ones only
|
|
42
|
+
once the client took the earlier ones, so a slow client slows the source down instead of filling the memory. When the client goes away the source is
|
|
43
|
+
stopped (an async generator runs its `finally`, a `Readable` is destroyed) and `streamResponse` resolves with `false`. The response is sent chunked unless
|
|
44
|
+
you set a `Content-Length`. Static files above 1 MiB are streamed the same way.
|
|
45
|
+
|
|
46
|
+
Producing very small chunks is wasteful, as every part takes a message to the main process: collect them into parts of some KiB.
|
|
47
|
+
|
|
48
|
+
## Reading the body of a request
|
|
49
|
+
|
|
50
|
+
The body is not part of the event a worker is called with: the worker is called at once, and reads the body from `event.bodyStream`, a `Readable` (empty for requests without a body):
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
module.exports = async (event, callback) => {
|
|
54
|
+
let size = 0;
|
|
55
|
+
for await (const chunk of event.bodyStream) size += chunk.length;
|
|
56
|
+
callback({ statusCode: 200, headers: {}, body: String(size), isBase64Encoded: false });
|
|
57
|
+
};
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Only a few parts are on their way to the worker before it has read the earlier ones, so uploads of any size take a flat amount of memory. A worker that needs all of it uses `stream/consumers`:
|
|
61
|
+
`const body = await text(event.bodyStream)` (also `json` and `buffer`). **`event.body` does not exist any more.** Read the body before you answer, as the rest of it is dropped once the response is complete.
|
|
62
|
+
A client that goes away during the upload destroys the stream with an error. `limitRequestBody` (bytes, 0 for none, the default) answers a bigger body with 413. Websockets and static files never have a body.
|
|
63
|
+
|
|
64
|
+
## Metrics
|
|
65
|
+
|
|
66
|
+
`await event.getMetrics()` in a worker gives a snapshot of the server (uptime, memory, event loop delay, request counts, worker pools) for health and metrics endpoints, see the README of `@koeroesi86/node-webserver`.
|
|
67
|
+
In the server process `getServerMetrics()` gives the same, and `registerMetricsSource(name, read)` adds a source to it.
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { MiddlewareOptions } from '../types';
|
|
2
2
|
export declare enum WORKER_EVENT {
|
|
3
3
|
REQUEST = "WORKER_REQUEST",
|
|
4
|
+
/** not sent any more, nothing listened to it */
|
|
4
5
|
REQUEST_ACKNOWLEDGE = "WORKER_REQUEST_ACK",
|
|
6
|
+
/** the client went away before the response was complete, a worker streaming it should stop */
|
|
7
|
+
REQUEST_ABORT = "WORKER_REQUEST_ABORT",
|
|
8
|
+
/** a part of a request body that is streamed to the worker, the last one has no body */
|
|
9
|
+
REQUEST_BODY = "WORKER_REQUEST_BODY",
|
|
10
|
+
/** the worker took a part of the streamed request body, the server may send another one */
|
|
11
|
+
REQUEST_BODY_ACKNOWLEDGE = "WORKER_REQUEST_BODY_ACK",
|
|
12
|
+
/** a worker asks for the metrics of the server */
|
|
13
|
+
METRICS_REQUEST = "WORKER_METRICS_REQUEST",
|
|
14
|
+
METRICS = "WORKER_METRICS",
|
|
5
15
|
RESPONSE = "WORKER_RESPONSE",
|
|
6
16
|
RESPONSE_EMIT = "WORKER_RESPONSE_EMIT",
|
|
7
17
|
RESPONSE_ACKNOWLEDGE = "WORKER_RESPONSE_ACK",
|
|
@@ -10,6 +20,20 @@ export declare enum WORKER_EVENT {
|
|
|
10
20
|
WS_CONNECTION_CLOSE = "WS_CONNECTION_CLOSE",
|
|
11
21
|
WS_CONNECTION_CLOSE_ACKNOWLEDGE = "WS_CONNECTION_CLOSE_ACK"
|
|
12
22
|
}
|
|
23
|
+
/** the static worker sends files up to this size in one part, bigger ones are streamed so they do not have to fit into the memory */
|
|
24
|
+
export declare const StaticStreamThreshold: number;
|
|
25
|
+
/** how many parts of a streamed request body may be on their way to the worker before it takes one */
|
|
26
|
+
export declare const RequestBodyWindow = 4;
|
|
27
|
+
/** a worker that stops sooner than this after it started, or with an error, has crashed, in milliseconds */
|
|
28
|
+
export declare const WorkerMinUptime = 5000;
|
|
29
|
+
/**
|
|
30
|
+
* Starting a worker for a path that crashed twice in a row is refused for this long, doubling with every crash until the maximum,
|
|
31
|
+
* so that a worker that fails at start does not cost a process for every request. In milliseconds.
|
|
32
|
+
*/
|
|
33
|
+
export declare const WorkerRestartBackoff: {
|
|
34
|
+
base: number;
|
|
35
|
+
max: number;
|
|
36
|
+
};
|
|
13
37
|
export declare const ForbiddenPaths: readonly string[];
|
|
14
38
|
export declare enum Protocols {
|
|
15
39
|
http = "HTTP",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var os = require('os');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
|
|
6
|
+
exports.WORKER_EVENT = void 0;
|
|
7
|
+
(function (WORKER_EVENT) {
|
|
8
|
+
WORKER_EVENT["REQUEST"] = "WORKER_REQUEST";
|
|
9
|
+
/** not sent any more, nothing listened to it */
|
|
10
|
+
WORKER_EVENT["REQUEST_ACKNOWLEDGE"] = "WORKER_REQUEST_ACK";
|
|
11
|
+
/** the client went away before the response was complete, a worker streaming it should stop */
|
|
12
|
+
WORKER_EVENT["REQUEST_ABORT"] = "WORKER_REQUEST_ABORT";
|
|
13
|
+
/** a part of a request body that is streamed to the worker, the last one has no body */
|
|
14
|
+
WORKER_EVENT["REQUEST_BODY"] = "WORKER_REQUEST_BODY";
|
|
15
|
+
/** the worker took a part of the streamed request body, the server may send another one */
|
|
16
|
+
WORKER_EVENT["REQUEST_BODY_ACKNOWLEDGE"] = "WORKER_REQUEST_BODY_ACK";
|
|
17
|
+
/** a worker asks for the metrics of the server */
|
|
18
|
+
WORKER_EVENT["METRICS_REQUEST"] = "WORKER_METRICS_REQUEST";
|
|
19
|
+
WORKER_EVENT["METRICS"] = "WORKER_METRICS";
|
|
20
|
+
WORKER_EVENT["RESPONSE"] = "WORKER_RESPONSE";
|
|
21
|
+
WORKER_EVENT["RESPONSE_EMIT"] = "WORKER_RESPONSE_EMIT";
|
|
22
|
+
WORKER_EVENT["RESPONSE_ACKNOWLEDGE"] = "WORKER_RESPONSE_ACK";
|
|
23
|
+
WORKER_EVENT["WS_MESSAGE_RECEIVE"] = "WS_MESSAGE_RECEIVE";
|
|
24
|
+
WORKER_EVENT["WS_MESSAGE_SEND"] = "WS_MESSAGE_SEND";
|
|
25
|
+
WORKER_EVENT["WS_CONNECTION_CLOSE"] = "WS_CONNECTION_CLOSE";
|
|
26
|
+
WORKER_EVENT["WS_CONNECTION_CLOSE_ACKNOWLEDGE"] = "WS_CONNECTION_CLOSE_ACK";
|
|
27
|
+
})(exports.WORKER_EVENT || (exports.WORKER_EVENT = {}));
|
|
28
|
+
/** the static worker sends files up to this size in one part, bigger ones are streamed so they do not have to fit into the memory */
|
|
29
|
+
const StaticStreamThreshold = 1024 * 1024;
|
|
30
|
+
/** how many parts of a streamed request body may be on their way to the worker before it takes one */
|
|
31
|
+
const RequestBodyWindow = 4;
|
|
32
|
+
/** a worker that stops sooner than this after it started, or with an error, has crashed, in milliseconds */
|
|
33
|
+
const WorkerMinUptime = 5000;
|
|
34
|
+
/**
|
|
35
|
+
* Starting a worker for a path that crashed twice in a row is refused for this long, doubling with every crash until the maximum,
|
|
36
|
+
* so that a worker that fails at start does not cost a process for every request. In milliseconds.
|
|
37
|
+
*/
|
|
38
|
+
const WorkerRestartBackoff = { base: 100, max: 10000 };
|
|
39
|
+
const ForbiddenPaths = ['..'];
|
|
40
|
+
exports.Protocols = void 0;
|
|
41
|
+
(function (Protocols) {
|
|
42
|
+
Protocols["http"] = "HTTP";
|
|
43
|
+
Protocols["websocket"] = "WS";
|
|
44
|
+
})(exports.Protocols || (exports.Protocols = {}));
|
|
45
|
+
const DefaultOptions = {
|
|
46
|
+
root: '',
|
|
47
|
+
limit: 0,
|
|
48
|
+
limitPerPath: os.availableParallelism(),
|
|
49
|
+
warmStaticWorker: true,
|
|
50
|
+
limitRequestBody: 0,
|
|
51
|
+
inlineRequestBody: 64 * 1024,
|
|
52
|
+
limitRequestTimeout: 5000,
|
|
53
|
+
limitResponseTimeout: 30000,
|
|
54
|
+
limitQueue: 1000,
|
|
55
|
+
onStdout: () => { },
|
|
56
|
+
onStderr: () => { },
|
|
57
|
+
onExit: () => { },
|
|
58
|
+
onForbiddenPath: (request, response) => {
|
|
59
|
+
response.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
60
|
+
response.end();
|
|
61
|
+
request.connection.destroy();
|
|
62
|
+
throw new Error('Forbidden path');
|
|
63
|
+
},
|
|
64
|
+
index: [],
|
|
65
|
+
env: {},
|
|
66
|
+
staticWorker: path.resolve(__dirname, './staticWorker.js'),
|
|
67
|
+
cwd: process.cwd(),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
exports.DefaultOptions = DefaultOptions;
|
|
71
|
+
exports.ForbiddenPaths = ForbiddenPaths;
|
|
72
|
+
exports.RequestBodyWindow = RequestBodyWindow;
|
|
73
|
+
exports.StaticStreamThreshold = StaticStreamThreshold;
|
|
74
|
+
exports.WorkerMinUptime = WorkerMinUptime;
|
|
75
|
+
exports.WorkerRestartBackoff = WorkerRestartBackoff;
|
|
76
|
+
//# sourceMappingURL=index-9a1547f9.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-9a1547f9.js","sources":["../src/constants/index.ts"],"sourcesContent":[null],"names":["WORKER_EVENT","Protocols"],"mappings":";;;;;AAIYA,8BAoBX;AApBD,CAAA,UAAY,YAAY,EAAA;AACtB,IAAA,YAAA,CAAA,SAAA,CAAA,GAAA,gBAA0B,CAAA;;AAE1B,IAAA,YAAA,CAAA,qBAAA,CAAA,GAAA,oBAA0C,CAAA;;AAE1C,IAAA,YAAA,CAAA,eAAA,CAAA,GAAA,sBAAsC,CAAA;;AAEtC,IAAA,YAAA,CAAA,cAAA,CAAA,GAAA,qBAAoC,CAAA;;AAEpC,IAAA,YAAA,CAAA,0BAAA,CAAA,GAAA,yBAAoD,CAAA;;AAEpD,IAAA,YAAA,CAAA,iBAAA,CAAA,GAAA,wBAA0C,CAAA;AAC1C,IAAA,YAAA,CAAA,SAAA,CAAA,GAAA,gBAA0B,CAAA;AAC1B,IAAA,YAAA,CAAA,UAAA,CAAA,GAAA,iBAA4B,CAAA;AAC5B,IAAA,YAAA,CAAA,eAAA,CAAA,GAAA,sBAAsC,CAAA;AACtC,IAAA,YAAA,CAAA,sBAAA,CAAA,GAAA,qBAA4C,CAAA;AAC5C,IAAA,YAAA,CAAA,oBAAA,CAAA,GAAA,oBAAyC,CAAA;AACzC,IAAA,YAAA,CAAA,iBAAA,CAAA,GAAA,iBAAmC,CAAA;AACnC,IAAA,YAAA,CAAA,qBAAA,CAAA,GAAA,qBAA2C,CAAA;AAC3C,IAAA,YAAA,CAAA,iCAAA,CAAA,GAAA,yBAA2D,CAAA;AAC7D,CAAC,EApBWA,oBAAY,KAAZA,oBAAY,GAoBvB,EAAA,CAAA,CAAA,CAAA;AAED;AACa,MAAA,qBAAqB,GAAG,IAAI,GAAG,KAAK;AAEjD;AACO,MAAM,iBAAiB,GAAG,EAAE;AAEnC;AACO,MAAM,eAAe,GAAG,KAAK;AAEpC;;;AAGG;AACI,MAAM,oBAAoB,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,GAAG;AAEjD,MAAA,cAAc,GAAsB,CAAC,IAAI,EAAW;AAErDC,2BAGX;AAHD,CAAA,UAAY,SAAS,EAAA;AACnB,IAAA,SAAA,CAAA,MAAA,CAAA,GAAA,MAAa,CAAA;AACb,IAAA,SAAA,CAAA,WAAA,CAAA,GAAA,IAAgB,CAAA;AAClB,CAAC,EAHWA,iBAAS,KAATA,iBAAS,GAGpB,EAAA,CAAA,CAAA,CAAA;AAEY,MAAA,cAAc,GAAsB;AAC/C,IAAA,IAAI,EAAE,EAAE;AACR,IAAA,KAAK,EAAE,CAAC;AACR,IAAA,YAAY,EAAE,EAAE,CAAC,oBAAoB,EAAE;AACvC,IAAA,gBAAgB,EAAE,IAAI;AACtB,IAAA,gBAAgB,EAAE,CAAC;IACnB,iBAAiB,EAAE,EAAE,GAAG,IAAI;AAC5B,IAAA,mBAAmB,EAAE,IAAI;AACzB,IAAA,oBAAoB,EAAE,KAAK;AAC3B,IAAA,UAAU,EAAE,IAAI;AAChB,IAAA,QAAQ,EAAE,MAAK,GAAG;AAClB,IAAA,QAAQ,EAAE,MAAK,GAAG;AAClB,IAAA,MAAM,EAAE,MAAK,GAAG;AAChB,IAAA,eAAe,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAI;QACrC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,CAAC,CAAC;QAC1D,QAAQ,CAAC,GAAG,EAAE,CAAC;AACf,QAAA,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;AAC7B,QAAA,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;KACnC;AACD,IAAA,KAAK,EAAE,EAAE;AACT,IAAA,GAAG,EAAE,EAAE;IACP,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,mBAAmB,CAAC;AAC1D,IAAA,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;;;;;;;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export { default as middleware } from './middleware';
|
|
2
2
|
export * from './types';
|
|
3
|
+
export { default as streamResponse } from './streamResponse';
|
|
4
|
+
export type { StreamBody, StreamResponseOptions } from './streamResponse';
|
|
5
|
+
export { getServerMetrics, registerMetricsSource } from './utils/metrics';
|
|
6
|
+
export type { ServerMetrics } from './utils/metrics';
|