@jsenv/server 10.0.0 → 10.0.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/server",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.4",
|
|
4
4
|
"description": "Write your Node.js server using pure functions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"postpublish": "node ./script/publish/restore_postinstall.mjs"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@jsenv/abort": "
|
|
49
|
+
"@jsenv/abort": "4.1.1",
|
|
50
50
|
"@jsenv/logger": "4.0.1",
|
|
51
51
|
"node-fetch": "2.6.2"
|
|
52
52
|
},
|
|
@@ -55,15 +55,15 @@
|
|
|
55
55
|
"@babel/eslint-parser": "7.16.0",
|
|
56
56
|
"@jsenv/assert": "2.3.2",
|
|
57
57
|
"@jsenv/codecov-upload": "3.5.0",
|
|
58
|
-
"@jsenv/core": "23.2
|
|
58
|
+
"@jsenv/core": "23.4.2",
|
|
59
59
|
"@jsenv/eslint-config": "16.0.8",
|
|
60
|
-
"@jsenv/filesystem": "2.
|
|
60
|
+
"@jsenv/filesystem": "2.5.1",
|
|
61
61
|
"@jsenv/github-release-package": "1.2.3",
|
|
62
62
|
"@jsenv/https-local": "1.0.2",
|
|
63
63
|
"@jsenv/importmap-eslint-resolver": "5.1.2",
|
|
64
64
|
"@jsenv/importmap-node-module": "2.4.1",
|
|
65
65
|
"@jsenv/package-publish": "1.6.2",
|
|
66
|
-
"@jsenv/performance-impact": "2.2.
|
|
66
|
+
"@jsenv/performance-impact": "2.2.1",
|
|
67
67
|
"@jsenv/prettier-check-project": "5.6.1",
|
|
68
68
|
"eslint": "7.32.0",
|
|
69
69
|
"eslint-plugin-import": "2.25.2",
|
package/src/fetch_url.js
CHANGED
|
@@ -4,10 +4,7 @@ import { Agent } from "node:https"
|
|
|
4
4
|
import nodeFetch, { Response } from "node-fetch"
|
|
5
5
|
|
|
6
6
|
import { fetchFileSystem } from "./fetch_filesystem.js"
|
|
7
|
-
import {
|
|
8
|
-
isFileHandle,
|
|
9
|
-
fileHandleToReadableStream,
|
|
10
|
-
} from "./internal/responseBody.js"
|
|
7
|
+
import { isFileHandle, fileHandleToReadableStream } from "./internal/body.js"
|
|
11
8
|
|
|
12
9
|
export const fetchUrl = async (
|
|
13
10
|
url,
|
|
@@ -4,7 +4,7 @@ import { createReadStream } from "node:fs"
|
|
|
4
4
|
import { isObservable, observableFromValue } from "./observable.js"
|
|
5
5
|
import { nodeStreamToObservable } from "./nodeStreamToObservable.js"
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const normalizeBodyMethods = (body) => {
|
|
8
8
|
if (isObservable(body)) {
|
|
9
9
|
return {
|
|
10
10
|
asObservable: () => body,
|
|
@@ -6,21 +6,7 @@ import { createObservable } from "./observable.js"
|
|
|
6
6
|
const readableStreamLifetimeInSeconds = 120
|
|
7
7
|
|
|
8
8
|
export const nodeStreamToObservable = (nodeStream) => {
|
|
9
|
-
|
|
10
|
-
// safe measure, ensure the readable stream gets
|
|
11
|
-
// used in the next ${readableStreamLifetimeInSeconds} otherwise destroys it
|
|
12
|
-
const timeout = setTimeout(() => {
|
|
13
|
-
console.warn(
|
|
14
|
-
`readable stream still unused after ${readableStreamLifetimeInSeconds} seconds -> destroying it to release file handle`,
|
|
15
|
-
)
|
|
16
|
-
nodeStream.destroy()
|
|
17
|
-
}, readableStreamLifetimeInSeconds * 1000)
|
|
18
|
-
onceReadableStreamUsedOrClosed(nodeStream, () => {
|
|
19
|
-
clearTimeout(timeout)
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return createObservable(({ next, error, complete }) => {
|
|
9
|
+
const observable = createObservable(({ next, error, complete }) => {
|
|
24
10
|
const cleanup = () => {
|
|
25
11
|
nodeStream.removeListener("data", next)
|
|
26
12
|
nodeStream.removeListener("error", error)
|
|
@@ -37,6 +23,28 @@ export const nodeStreamToObservable = (nodeStream) => {
|
|
|
37
23
|
|
|
38
24
|
return cleanup
|
|
39
25
|
})
|
|
26
|
+
|
|
27
|
+
if (nodeStream instanceof Readable) {
|
|
28
|
+
// safe measure, ensure the readable stream gets
|
|
29
|
+
// used in the next ${readableStreamLifetimeInSeconds} otherwise destroys it
|
|
30
|
+
const timeout = setTimeout(() => {
|
|
31
|
+
process.emitWarning(
|
|
32
|
+
`Readable stream not used after ${readableStreamLifetimeInSeconds} seconds. It will be destroyed to release ressources`,
|
|
33
|
+
{
|
|
34
|
+
CODE: "READABLE_STREAM_TIMEOUT",
|
|
35
|
+
// url is for http client request
|
|
36
|
+
detail: `path: ${nodeStream.path}, fd: ${nodeStream.fd}, url: ${nodeStream.url}`,
|
|
37
|
+
},
|
|
38
|
+
)
|
|
39
|
+
nodeStream.destroy()
|
|
40
|
+
}, readableStreamLifetimeInSeconds * 1000)
|
|
41
|
+
observable.timeout = timeout
|
|
42
|
+
onceReadableStreamUsedOrClosed(nodeStream, () => {
|
|
43
|
+
clearTimeout(timeout)
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return observable
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
const onceReadableStreamUsedOrClosed = (readableStream, callback) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Http2ServerResponse } from "node:http2"
|
|
2
2
|
import { raceCallbacks } from "@jsenv/abort"
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { normalizeBodyMethods } from "./body.js"
|
|
5
5
|
|
|
6
6
|
export const populateNodeResponse = async (
|
|
7
7
|
responseStream,
|
|
@@ -9,7 +9,7 @@ export const populateNodeResponse = async (
|
|
|
9
9
|
{ signal, ignoreBody, onAbort, onError, onHeadersSent, onEnd } = {},
|
|
10
10
|
) => {
|
|
11
11
|
body = await body
|
|
12
|
-
const bodyMethods =
|
|
12
|
+
const bodyMethods = normalizeBodyMethods(body)
|
|
13
13
|
|
|
14
14
|
if (signal.aborted) {
|
|
15
15
|
bodyMethods.destroy()
|
package/src/startServer.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createDetailedMessage, createLogger } from "@jsenv/logger"
|
|
|
5
5
|
import {
|
|
6
6
|
Abort,
|
|
7
7
|
raceProcessTeardownEvents,
|
|
8
|
-
|
|
8
|
+
createCallbackListNotifiedOnce,
|
|
9
9
|
} from "@jsenv/abort"
|
|
10
10
|
|
|
11
11
|
import { createPolyglotServer } from "./internal/server-polyglot.js"
|
|
@@ -164,7 +164,7 @@ export const startServer = async ({
|
|
|
164
164
|
// we can proceed to create a stop function to stop it gacefully
|
|
165
165
|
// and add a request handler
|
|
166
166
|
|
|
167
|
-
const stopCallbackList =
|
|
167
|
+
const stopCallbackList = createCallbackListNotifiedOnce()
|
|
168
168
|
stopCallbackList.add(({ reason }) => {
|
|
169
169
|
logger.info(`${serverName} stopping server (reason: ${reason})`)
|
|
170
170
|
})
|
|
@@ -178,7 +178,7 @@ export const startServer = async ({
|
|
|
178
178
|
})
|
|
179
179
|
const stop = memoize(async (reason = STOP_REASON_NOT_SPECIFIED) => {
|
|
180
180
|
status = "stopping"
|
|
181
|
-
await Promise.all(stopCallbackList.
|
|
181
|
+
await Promise.all(stopCallbackList.notify({ reason }))
|
|
182
182
|
status = "stopped"
|
|
183
183
|
stoppedResolve(reason)
|
|
184
184
|
})
|
|
@@ -636,6 +636,14 @@ export const startServer = async ({
|
|
|
636
636
|
if (receiveRequestOperation.signal.aborted) {
|
|
637
637
|
return
|
|
638
638
|
}
|
|
639
|
+
|
|
640
|
+
// the node request readable stream is never closed because
|
|
641
|
+
// the response headers contains "connection: keep-alive"
|
|
642
|
+
// In this scenario we want to disable READABLE_STREAM_TIMEOUT warning
|
|
643
|
+
if (responseProperties.headers.connection === "keep-alive") {
|
|
644
|
+
clearTimeout(request.body.timeout)
|
|
645
|
+
}
|
|
646
|
+
|
|
639
647
|
await sendResponse({
|
|
640
648
|
signal: sendResponseOperation.signal,
|
|
641
649
|
request,
|