@jsenv/server 12.2.0 → 12.3.2
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/main.js +1 -0
- package/package.json +21 -24
- package/src/cors/plugin_cors.js +1 -3
- package/src/from_fetch_response.js +15 -0
- package/src/internal/memoize.js +24 -0
- package/src/internal/populateNodeResponse.js +15 -7
- package/src/startServer.js +53 -54
package/main.js
CHANGED
|
@@ -42,6 +42,7 @@ export { pluginContentNegotiationCheck } from "./src/content_negotiation/plugin_
|
|
|
42
42
|
|
|
43
43
|
// others
|
|
44
44
|
export { fetchUrl } from "./src/fetch_url.js"
|
|
45
|
+
export { fromFetchResponse } from "./src/from_fetch_response.js"
|
|
45
46
|
export { composeTwoResponses } from "./src/internal/response_composition.js"
|
|
46
47
|
export { pluginRessourceAliases } from "./src/ressource_aliases/plugin_ressource_aliases.js"
|
|
47
48
|
export { pluginRequestWaitingCheck } from "./src/plugin_request_waiting_check.js"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/server",
|
|
3
|
-
"version": "12.2
|
|
3
|
+
"version": "12.3.2",
|
|
4
4
|
"description": "Write your Node.js server using pure functions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"url": "https://github.com/jsenv/server"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=16.
|
|
16
|
+
"node": ">=16.13.0"
|
|
17
17
|
},
|
|
18
18
|
"publishConfig": {
|
|
19
19
|
"access": "public"
|
|
@@ -31,43 +31,40 @@
|
|
|
31
31
|
"/main.js"
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
|
-
"importmap
|
|
35
|
-
"
|
|
34
|
+
"importmap": "node ./script/importmap/importmap.mjs",
|
|
35
|
+
"eslint": "node ./node_modules/eslint/bin/eslint.js . --ext=.js,.mjs",
|
|
36
|
+
"performance": "node --expose-gc ./script/performance/performance.mjs --log",
|
|
36
37
|
"test": "node ./script/test/test.mjs",
|
|
37
38
|
"test-with-coverage": "npm run test -- --coverage",
|
|
38
39
|
"generate-html-snapshots": "node ./test/internal_error/generate_html_snapshot_files.mjs",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"certificate-authority-install": "node ./script/certificate/certificate_authority_install.mjs",
|
|
43
|
-
"playwright-install": "npx playwright install-deps && npx playwright install",
|
|
44
|
-
"prepublishOnly": "node ./script/publish/remove_postinstall.mjs",
|
|
45
|
-
"postpublish": "node ./script/publish/restore_postinstall.mjs"
|
|
40
|
+
"prettier": "prettier --write .",
|
|
41
|
+
"certificate-install": "node ./script/certificate/certificate_install.mjs",
|
|
42
|
+
"playwright-install": "npx playwright install-deps && npx playwright install"
|
|
46
43
|
},
|
|
47
44
|
"dependencies": {
|
|
48
|
-
"@jsenv/abort": "4.1.
|
|
45
|
+
"@jsenv/abort": "4.1.2",
|
|
49
46
|
"@jsenv/logger": "4.0.1",
|
|
50
|
-
"@jsenv/url-meta": "6.0.
|
|
47
|
+
"@jsenv/url-meta": "6.0.3",
|
|
51
48
|
"node-fetch": "2.6.2",
|
|
52
49
|
"path-to-regexp": "6.2.0"
|
|
53
50
|
},
|
|
54
51
|
"devDependencies": {
|
|
55
|
-
"@jsenv/assert": "2.
|
|
56
|
-
"@jsenv/core": "
|
|
52
|
+
"@jsenv/assert": "2.4.1",
|
|
53
|
+
"@jsenv/core": "25.1.0",
|
|
57
54
|
"@jsenv/eslint-config": "16.0.9",
|
|
58
|
-
"@jsenv/filesystem": "2.
|
|
55
|
+
"@jsenv/filesystem": "2.6.1",
|
|
59
56
|
"@jsenv/github-release-package": "1.2.3",
|
|
60
57
|
"@jsenv/https-local": "1.0.3",
|
|
61
|
-
"@jsenv/importmap-eslint-resolver": "5.
|
|
62
|
-
"@jsenv/importmap-node-module": "
|
|
58
|
+
"@jsenv/importmap-eslint-resolver": "5.2.2",
|
|
59
|
+
"@jsenv/importmap-node-module": "5.1.0",
|
|
63
60
|
"@jsenv/package-publish": "1.6.2",
|
|
64
61
|
"@jsenv/performance-impact": "2.2.1",
|
|
65
|
-
"eslint": "8.
|
|
66
|
-
"eslint-plugin-import": "2.25.
|
|
62
|
+
"eslint": "8.7.0",
|
|
63
|
+
"eslint-plugin-import": "2.25.4",
|
|
67
64
|
"eventsource": "1.1.0",
|
|
68
|
-
"fastify": "3.
|
|
65
|
+
"fastify": "3.25.3",
|
|
69
66
|
"fastify-static": "4.5.0",
|
|
70
|
-
"playwright": "1.
|
|
71
|
-
"prettier": "2.
|
|
67
|
+
"playwright": "1.17.2",
|
|
68
|
+
"prettier": "2.5.1"
|
|
72
69
|
}
|
|
73
|
-
}
|
|
70
|
+
}
|
package/src/cors/plugin_cors.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { urlToOrigin } from "@jsenv/filesystem"
|
|
2
|
-
|
|
3
1
|
export const jsenvAccessControlAllowedHeaders = ["x-requested-with"]
|
|
4
2
|
|
|
5
3
|
export const jsenvAccessControlAllowedMethods = [
|
|
@@ -107,7 +105,7 @@ const generateAccessControlHeaders = ({
|
|
|
107
105
|
allowedOriginArray.push(headers.origin)
|
|
108
106
|
vary.push("origin")
|
|
109
107
|
} else if ("referer" in headers) {
|
|
110
|
-
allowedOriginArray.push(
|
|
108
|
+
allowedOriginArray.push(new URL(headers.referer).origin)
|
|
111
109
|
vary.push("referer")
|
|
112
110
|
} else {
|
|
113
111
|
allowedOriginArray.push("*")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const fromFetchResponse = (fetchResponse) => {
|
|
2
|
+
const responseHeaders = {}
|
|
3
|
+
const headersToIgnore = ["connection"]
|
|
4
|
+
fetchResponse.headers.forEach((value, name) => {
|
|
5
|
+
if (!headersToIgnore.includes(name)) {
|
|
6
|
+
responseHeaders[name] = value
|
|
7
|
+
}
|
|
8
|
+
})
|
|
9
|
+
return {
|
|
10
|
+
status: fetchResponse.status,
|
|
11
|
+
statusText: fetchResponse.statusText,
|
|
12
|
+
headers: responseHeaders,
|
|
13
|
+
body: fetchResponse.body, // node-fetch assumed
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const memoize = (compute) => {
|
|
2
|
+
let memoized = false
|
|
3
|
+
let memoizedValue
|
|
4
|
+
|
|
5
|
+
const fnWithMemoization = (...args) => {
|
|
6
|
+
if (memoized) {
|
|
7
|
+
return memoizedValue
|
|
8
|
+
}
|
|
9
|
+
// if compute is recursive wait for it to be fully done before storing the value
|
|
10
|
+
// so set memoized boolean after the call
|
|
11
|
+
memoizedValue = compute(...args)
|
|
12
|
+
memoized = true
|
|
13
|
+
return memoizedValue
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fnWithMemoization.forget = () => {
|
|
17
|
+
const value = memoizedValue
|
|
18
|
+
memoized = false
|
|
19
|
+
memoizedValue = undefined
|
|
20
|
+
return value
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return fnWithMemoization
|
|
24
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import http from "node:http"
|
|
1
2
|
import { Http2ServerResponse } from "node:http2"
|
|
2
3
|
import { raceCallbacks } from "@jsenv/abort"
|
|
3
4
|
|
|
@@ -22,8 +23,8 @@ export const populateNodeResponse = async (
|
|
|
22
23
|
status,
|
|
23
24
|
statusText,
|
|
24
25
|
headers,
|
|
26
|
+
onHeadersSent,
|
|
25
27
|
})
|
|
26
|
-
onHeadersSent()
|
|
27
28
|
|
|
28
29
|
if (!body) {
|
|
29
30
|
onEnd()
|
|
@@ -134,41 +135,48 @@ export const populateNodeResponse = async (
|
|
|
134
135
|
})
|
|
135
136
|
}
|
|
136
137
|
|
|
137
|
-
const writeHead = (
|
|
138
|
+
const writeHead = (
|
|
139
|
+
responseStream,
|
|
140
|
+
{ status, statusText, headers, onHeadersSent },
|
|
141
|
+
) => {
|
|
138
142
|
const responseIsHttp2ServerResponse =
|
|
139
143
|
responseStream instanceof Http2ServerResponse
|
|
140
144
|
const responseIsServerHttp2Stream =
|
|
141
145
|
responseStream.constructor.name === "ServerHttp2Stream"
|
|
142
|
-
|
|
143
146
|
let nodeHeaders = headersToNodeHeaders(headers, {
|
|
144
147
|
// https://github.com/nodejs/node/blob/79296dc2d02c0b9872bbfcbb89148ea036a546d0/lib/internal/http2/compat.js#L112
|
|
145
148
|
ignoreConnectionHeader:
|
|
146
149
|
responseIsHttp2ServerResponse || responseIsServerHttp2Stream,
|
|
147
150
|
})
|
|
148
|
-
|
|
151
|
+
if (statusText === undefined) {
|
|
152
|
+
statusText = statusTextFromStatus(status)
|
|
153
|
+
}
|
|
149
154
|
if (responseIsServerHttp2Stream) {
|
|
150
155
|
nodeHeaders = {
|
|
151
156
|
...nodeHeaders,
|
|
152
157
|
":status": status,
|
|
153
158
|
}
|
|
154
159
|
responseStream.respond(nodeHeaders)
|
|
160
|
+
onHeadersSent({ nodeHeaders, status, statusText })
|
|
155
161
|
return
|
|
156
162
|
}
|
|
157
|
-
|
|
158
163
|
// nodejs strange signature for writeHead force this
|
|
159
164
|
// https://nodejs.org/api/http.html#http_response_writehead_statuscode_statusmessage_headers
|
|
160
165
|
if (
|
|
161
|
-
statusText === undefined ||
|
|
162
166
|
// https://github.com/nodejs/node/blob/79296dc2d02c0b9872bbfcbb89148ea036a546d0/lib/internal/http2/compat.js#L97
|
|
163
167
|
responseIsHttp2ServerResponse
|
|
164
168
|
) {
|
|
165
169
|
responseStream.writeHead(status, nodeHeaders)
|
|
170
|
+
onHeadersSent({ nodeHeaders, status, statusText })
|
|
166
171
|
return
|
|
167
172
|
}
|
|
168
|
-
|
|
169
173
|
responseStream.writeHead(status, statusText, nodeHeaders)
|
|
174
|
+
onHeadersSent({ nodeHeaders, status, statusText })
|
|
170
175
|
}
|
|
171
176
|
|
|
177
|
+
const statusTextFromStatus = (status) =>
|
|
178
|
+
http.STATUS_CODES[status] || "not specified"
|
|
179
|
+
|
|
172
180
|
const headersToNodeHeaders = (headers, { ignoreConnectionHeader }) => {
|
|
173
181
|
const nodeHeaders = {}
|
|
174
182
|
|
package/src/startServer.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import http from "node:http"
|
|
2
2
|
import cluster from "node:cluster"
|
|
3
|
-
import { memoize } from "@jsenv/filesystem"
|
|
4
3
|
import { createDetailedMessage, createLogger } from "@jsenv/logger"
|
|
5
4
|
import {
|
|
6
5
|
Abort,
|
|
@@ -23,6 +22,7 @@ import { listen, stopListening } from "./internal/listen.js"
|
|
|
23
22
|
import { composeTwoResponses } from "./internal/response_composition.js"
|
|
24
23
|
import { listenRequest } from "./internal/listenRequest.js"
|
|
25
24
|
import { listenServerConnectionError } from "./internal/listenServerConnectionError.js"
|
|
25
|
+
import { memoize } from "./internal/memoize.js"
|
|
26
26
|
import { jsenvServerInternalErrorToResponse } from "./jsenvServerInternalErrorToResponse.js"
|
|
27
27
|
import {
|
|
28
28
|
STOP_REASON_INTERNAL_ERROR,
|
|
@@ -472,8 +472,8 @@ export const startServer = async ({
|
|
|
472
472
|
}
|
|
473
473
|
}
|
|
474
474
|
})
|
|
475
|
-
|
|
476
|
-
? {
|
|
475
|
+
const result = responseFromShortcircuit
|
|
476
|
+
? { returnValue: responseFromShortcircuit }
|
|
477
477
|
: await generateResponseDescription({
|
|
478
478
|
requestToResponse,
|
|
479
479
|
request,
|
|
@@ -533,65 +533,72 @@ export const startServer = async ({
|
|
|
533
533
|
},
|
|
534
534
|
})
|
|
535
535
|
|
|
536
|
+
const { aborted } = result
|
|
536
537
|
if (aborted) {
|
|
537
538
|
return ABORTED_RESPONSE_PROPERTIES
|
|
538
539
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
{
|
|
559
|
-
status: 500,
|
|
560
|
-
statusText: statusToStatusText(500),
|
|
561
|
-
headers: {
|
|
562
|
-
// ensure error are not cached
|
|
563
|
-
"cache-control": "no-store",
|
|
564
|
-
"content-type": "text/plain",
|
|
540
|
+
const readResponseProperties = async () => {
|
|
541
|
+
const { error } = result
|
|
542
|
+
// internal error, create 500 response
|
|
543
|
+
if (error) {
|
|
544
|
+
if (
|
|
545
|
+
// stopOnInternalError stops server only if requestToResponse generated
|
|
546
|
+
// a non controlled error (internal error).
|
|
547
|
+
// if requestToResponse gracefully produced a 500 response (it did not throw)
|
|
548
|
+
// then we can assume we are still in control of what we are doing
|
|
549
|
+
stopOnInternalError
|
|
550
|
+
) {
|
|
551
|
+
// il faudrais pouvoir stop que les autres response ?
|
|
552
|
+
stop(STOP_REASON_INTERNAL_ERROR)
|
|
553
|
+
}
|
|
554
|
+
const propertiesFromError = await serverInternalErrorToResponse(
|
|
555
|
+
error,
|
|
556
|
+
{
|
|
557
|
+
request,
|
|
558
|
+
sendServerInternalErrorDetails,
|
|
565
559
|
},
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
560
|
+
)
|
|
561
|
+
addRequestLog(requestNode, {
|
|
562
|
+
type: "error",
|
|
563
|
+
value: createDetailedMessage(
|
|
564
|
+
`internal error while handling request`,
|
|
565
|
+
{
|
|
566
|
+
"error stack": error.stack,
|
|
567
|
+
},
|
|
568
|
+
),
|
|
569
|
+
})
|
|
570
|
+
return composeTwoResponses(
|
|
573
571
|
{
|
|
574
|
-
|
|
572
|
+
status: 500,
|
|
573
|
+
statusText: "Internal Server Error",
|
|
574
|
+
headers: {
|
|
575
|
+
// ensure error are not cached
|
|
576
|
+
"cache-control": "no-store",
|
|
577
|
+
"content-type": "text/plain",
|
|
578
|
+
},
|
|
575
579
|
},
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
580
|
+
propertiesFromError,
|
|
581
|
+
)
|
|
582
|
+
}
|
|
583
|
+
|
|
579
584
|
const {
|
|
580
585
|
status = 501,
|
|
581
|
-
statusText
|
|
586
|
+
statusText,
|
|
582
587
|
headers = {},
|
|
583
588
|
body,
|
|
584
589
|
...rest
|
|
585
|
-
} =
|
|
586
|
-
responseProperties = {
|
|
590
|
+
} = result.returnValue || {}
|
|
591
|
+
const responseProperties = {
|
|
587
592
|
status,
|
|
588
593
|
statusText,
|
|
589
594
|
headers,
|
|
590
595
|
body,
|
|
591
596
|
...rest,
|
|
592
597
|
}
|
|
598
|
+
return responseProperties
|
|
593
599
|
}
|
|
594
600
|
|
|
601
|
+
let responseProperties = await readResponseProperties()
|
|
595
602
|
// call every plugin "onResponse" hook
|
|
596
603
|
requestPlugins.forEach((requestPlugin) => {
|
|
597
604
|
const { onResponse } = requestPlugin
|
|
@@ -605,7 +612,6 @@ export const startServer = async ({
|
|
|
605
612
|
}
|
|
606
613
|
}
|
|
607
614
|
})
|
|
608
|
-
|
|
609
615
|
if (
|
|
610
616
|
request.method !== "HEAD" &&
|
|
611
617
|
responseProperties.headers["content-length"] > 0 &&
|
|
@@ -616,7 +622,6 @@ export const startServer = async ({
|
|
|
616
622
|
value: `content-length header is ${responseProperties.headers["content-length"]} but body is empty`,
|
|
617
623
|
})
|
|
618
624
|
}
|
|
619
|
-
|
|
620
625
|
return responseProperties
|
|
621
626
|
}
|
|
622
627
|
|
|
@@ -659,12 +664,10 @@ export const startServer = async ({
|
|
|
659
664
|
})
|
|
660
665
|
onRequestHandled(requestNode)
|
|
661
666
|
},
|
|
662
|
-
onHeadersSent: () => {
|
|
667
|
+
onHeadersSent: ({ status, statusText }) => {
|
|
663
668
|
addRequestLog(requestNode, {
|
|
664
669
|
type: "info",
|
|
665
|
-
value: `${colorizeResponseStatus(
|
|
666
|
-
responseProperties.statusText
|
|
667
|
-
}`,
|
|
670
|
+
value: `${colorizeResponseStatus(status)} ${statusText}`,
|
|
668
671
|
})
|
|
669
672
|
},
|
|
670
673
|
onEnd: () => {
|
|
@@ -759,7 +762,7 @@ const generateResponseDescription = async ({
|
|
|
759
762
|
pushResponse,
|
|
760
763
|
})
|
|
761
764
|
return {
|
|
762
|
-
|
|
765
|
+
returnValue,
|
|
763
766
|
}
|
|
764
767
|
} catch (error) {
|
|
765
768
|
if (error.name === "AbortError" && request.signal.aborted) {
|
|
@@ -772,10 +775,6 @@ const generateResponseDescription = async ({
|
|
|
772
775
|
}
|
|
773
776
|
}
|
|
774
777
|
}
|
|
775
|
-
|
|
776
|
-
const statusToStatusText = (status) =>
|
|
777
|
-
http.STATUS_CODES[status] || "not specified"
|
|
778
|
-
|
|
779
778
|
const testCanPushStream = (http2Stream) => {
|
|
780
779
|
if (!http2Stream.pushAllowed) {
|
|
781
780
|
return {
|