@jsenv/server 7.1.0 → 8.0.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/README.md +63 -23
- package/main.js +32 -17
- package/package.json +20 -15
- package/src/{internal → content_negotiation}/applyContentNegotiation.js +5 -1
- package/src/{internal → content_negotiation}/checkContentNegotiation.js +3 -3
- package/src/{negotiateContentEncoding.js → content_negotiation/negotiateContentEncoding.js} +5 -3
- package/src/{negotiateContentLanguage.js → content_negotiation/negotiateContentLanguage.js} +9 -5
- package/src/{negotiateContentType.js → content_negotiation/negotiateContentType.js} +10 -5
- package/src/content_negotiation/plugin_content_negotiation_check.js +15 -0
- package/src/cors/plugin_cors.js +156 -0
- package/src/fetchUrl.js +12 -7
- package/src/findFreePort.js +2 -1
- package/src/{composeResponse.js → internal/composeResponse.js} +5 -3
- package/src/internal/compositionMappingToCompose.js +26 -6
- package/src/{convertFileSystemErrorToResponseProperties.js → internal/convertFileSystemErrorToResponseProperties.js} +0 -0
- package/src/internal/getServerOrigins.js +5 -1
- package/src/internal/headersFromObject.js +3 -1
- package/src/{headersToObject.js → internal/headersToObject.js} +0 -0
- package/src/internal/listenClientError.js +5 -1
- package/src/internal/listenEvent.js +6 -1
- package/src/internal/listenRequest.js +5 -1
- package/src/internal/listenServerConnectionError.js +31 -23
- package/src/internal/multiple-header.js +11 -5
- package/src/internal/nodeRequestToRequest.js +10 -7
- package/src/internal/observable.js +5 -1
- package/src/internal/populateNodeResponse.js +11 -2
- package/src/internal/server-polyglot.js +11 -2
- package/src/internal/trackServerPendingConnections.js +15 -11
- package/src/internal/trackServerPendingRequests.js +10 -7
- package/src/jsenvServerInternalErrorToResponse.js +7 -3
- package/src/plugin_request_waiting_check.js +34 -0
- package/src/serveFile.js +88 -41
- package/src/server_timing/plugin_server_timing.js +28 -0
- package/src/{serverTiming.js → server_timing/timing_header.js} +43 -62
- package/src/server_timing/timing_measure.js +26 -0
- package/src/service_composition/service_composition.js +65 -0
- package/src/{createSSERoom.js → sse/createSSERoom.js} +22 -6
- package/src/startServer.js +197 -252
- package/src/stopReasons.js +3 -1
- package/src/urlToContentType.js +13 -6
- package/src/jsenvAccessControlAllowedHeaders.js +0 -1
- package/src/jsenvAccessControlAllowedMethods.js +0 -1
- package/src/service-composition.js +0 -41
- package/src/urlToSearchParamValue.js +0 -3
|
@@ -1,30 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// as specified in https://w3c.github.io/server-timing/#the-performanceservertiming-interface
|
|
6
|
-
// duration is a https://www.w3.org/TR/hr-time-2/#sec-domhighrestimestamp
|
|
7
|
-
const startTimestamp = performance.now()
|
|
8
|
-
const timeEnd = () => {
|
|
9
|
-
const endTimestamp = performance.now()
|
|
10
|
-
const timing = {
|
|
11
|
-
[name]: endTimestamp - startTimestamp,
|
|
12
|
-
}
|
|
13
|
-
return timing
|
|
14
|
-
}
|
|
15
|
-
return timeEnd
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const timeFunction = (name, fn) => {
|
|
19
|
-
const timeEnd = timeStart(name)
|
|
20
|
-
const returnValue = fn()
|
|
21
|
-
if (returnValue && typeof returnValue.then === "function") {
|
|
22
|
-
return returnValue.then((value) => {
|
|
23
|
-
return [timeEnd(), value]
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
return [timeEnd(), returnValue]
|
|
27
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
parseMultipleHeader,
|
|
3
|
+
stringifyMultipleHeader,
|
|
4
|
+
} from "../internal/multiple-header.js"
|
|
28
5
|
|
|
29
6
|
// to predict order in chrome devtools we should put a,b,c,d,e or something
|
|
30
7
|
// because in chrome dev tools they are shown in alphabetic order
|
|
@@ -41,47 +18,22 @@ export const timingToServerTimingResponseHeaders = (timing) => {
|
|
|
41
18
|
dur: timing[key],
|
|
42
19
|
}
|
|
43
20
|
})
|
|
44
|
-
const serverTimingHeaderString =
|
|
21
|
+
const serverTimingHeaderString =
|
|
22
|
+
stringifyServerTimingHeader(serverTimingHeader)
|
|
45
23
|
|
|
46
24
|
return { "server-timing": serverTimingHeaderString }
|
|
47
25
|
}
|
|
48
26
|
|
|
49
|
-
const letters = [
|
|
50
|
-
"a",
|
|
51
|
-
"b",
|
|
52
|
-
"c",
|
|
53
|
-
"d",
|
|
54
|
-
"e",
|
|
55
|
-
"f",
|
|
56
|
-
"g",
|
|
57
|
-
"h",
|
|
58
|
-
"i",
|
|
59
|
-
"j",
|
|
60
|
-
"k",
|
|
61
|
-
"l",
|
|
62
|
-
"m",
|
|
63
|
-
"n",
|
|
64
|
-
"o",
|
|
65
|
-
"p",
|
|
66
|
-
"q",
|
|
67
|
-
"r",
|
|
68
|
-
"s",
|
|
69
|
-
"t",
|
|
70
|
-
"u",
|
|
71
|
-
"v",
|
|
72
|
-
"w",
|
|
73
|
-
"x",
|
|
74
|
-
"y",
|
|
75
|
-
"z",
|
|
76
|
-
]
|
|
77
|
-
|
|
78
27
|
export const parseServerTimingHeader = (serverTimingHeaderString) => {
|
|
79
|
-
const serverTimingHeaderObject = parseMultipleHeader(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
28
|
+
const serverTimingHeaderObject = parseMultipleHeader(
|
|
29
|
+
serverTimingHeaderString,
|
|
30
|
+
{
|
|
31
|
+
validateName: validateServerTimingName,
|
|
32
|
+
validateProperty: ({ name }) => {
|
|
33
|
+
return name === "desc" || name === "dur"
|
|
34
|
+
},
|
|
83
35
|
},
|
|
84
|
-
|
|
36
|
+
)
|
|
85
37
|
|
|
86
38
|
const serverTiming = {}
|
|
87
39
|
Object.keys(serverTimingHeaderObject).forEach((key) => {
|
|
@@ -114,3 +66,32 @@ const validateServerTimingName = (name) => {
|
|
|
114
66
|
}
|
|
115
67
|
return true
|
|
116
68
|
}
|
|
69
|
+
|
|
70
|
+
const letters = [
|
|
71
|
+
"a",
|
|
72
|
+
"b",
|
|
73
|
+
"c",
|
|
74
|
+
"d",
|
|
75
|
+
"e",
|
|
76
|
+
"f",
|
|
77
|
+
"g",
|
|
78
|
+
"h",
|
|
79
|
+
"i",
|
|
80
|
+
"j",
|
|
81
|
+
"k",
|
|
82
|
+
"l",
|
|
83
|
+
"m",
|
|
84
|
+
"n",
|
|
85
|
+
"o",
|
|
86
|
+
"p",
|
|
87
|
+
"q",
|
|
88
|
+
"r",
|
|
89
|
+
"s",
|
|
90
|
+
"t",
|
|
91
|
+
"u",
|
|
92
|
+
"v",
|
|
93
|
+
"w",
|
|
94
|
+
"x",
|
|
95
|
+
"y",
|
|
96
|
+
"z",
|
|
97
|
+
]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { performance } from "perf_hooks"
|
|
2
|
+
|
|
3
|
+
export const timeStart = (name) => {
|
|
4
|
+
// as specified in https://w3c.github.io/server-timing/#the-performanceservertiming-interface
|
|
5
|
+
// duration is a https://www.w3.org/TR/hr-time-2/#sec-domhighrestimestamp
|
|
6
|
+
const startTimestamp = performance.now()
|
|
7
|
+
const timeEnd = () => {
|
|
8
|
+
const endTimestamp = performance.now()
|
|
9
|
+
const timing = {
|
|
10
|
+
[name]: endTimestamp - startTimestamp,
|
|
11
|
+
}
|
|
12
|
+
return timing
|
|
13
|
+
}
|
|
14
|
+
return timeEnd
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const timeFunction = (name, fn) => {
|
|
18
|
+
const timeEnd = timeStart(name)
|
|
19
|
+
const returnValue = fn()
|
|
20
|
+
if (returnValue && typeof returnValue.then === "function") {
|
|
21
|
+
return returnValue.then((value) => {
|
|
22
|
+
return [timeEnd(), value]
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
return [timeEnd(), returnValue]
|
|
26
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { firstOperationMatching } from "@jsenv/cancellation"
|
|
2
|
+
|
|
3
|
+
import { timeFunction } from "@jsenv/server/src/server_timing/timing_measure.js"
|
|
4
|
+
import { composeResponse } from "@jsenv/server/src/internal/composeResponse.js"
|
|
5
|
+
|
|
6
|
+
const nextSymbol = Symbol("next")
|
|
7
|
+
|
|
8
|
+
export const nextService = (nextRequest) => {
|
|
9
|
+
return {
|
|
10
|
+
[nextSymbol]: nextRequest,
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const composeServices = (...callbacks) => {
|
|
15
|
+
return (request) => {
|
|
16
|
+
return firstOperationMatching({
|
|
17
|
+
array: callbacks,
|
|
18
|
+
start: (callback) => callback(request),
|
|
19
|
+
predicate: (returnValue) => {
|
|
20
|
+
if (returnValue === null || typeof returnValue !== "object") {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
const nextRequest = returnValue[nextSymbol]
|
|
24
|
+
if (nextRequest) {
|
|
25
|
+
request = nextRequest
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
return true
|
|
29
|
+
},
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const composeServicesWithTiming = (namedServices) => {
|
|
35
|
+
return async (request) => {
|
|
36
|
+
const servicesTiming = {}
|
|
37
|
+
const response = await firstOperationMatching({
|
|
38
|
+
array: Object.keys(namedServices).map((serviceName) => {
|
|
39
|
+
return { serviceName, serviceFn: namedServices[serviceName] }
|
|
40
|
+
}),
|
|
41
|
+
start: async ({ serviceName, serviceFn }) => {
|
|
42
|
+
const [serviceTiming, value] = await timeFunction(serviceName, () =>
|
|
43
|
+
serviceFn(request),
|
|
44
|
+
)
|
|
45
|
+
Object.assign(servicesTiming, serviceTiming)
|
|
46
|
+
return value
|
|
47
|
+
},
|
|
48
|
+
predicate: (returnValue) => {
|
|
49
|
+
if (returnValue === null || typeof returnValue !== "object") {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
const nextRequest = returnValue[nextSymbol]
|
|
53
|
+
if (nextRequest) {
|
|
54
|
+
request = nextRequest
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
return true
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
if (response) {
|
|
61
|
+
return composeResponse({ timing: servicesTiming }, response)
|
|
62
|
+
}
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { createLogger } from "@jsenv/logger"
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createObservable,
|
|
5
|
+
createCompositeProducer,
|
|
6
|
+
} from "@jsenv/server/src/internal/observable.js"
|
|
3
7
|
|
|
4
8
|
// https://www.html5rocks.com/en/tutorials/eventsource/basics/
|
|
5
9
|
export const createSSERoom = ({
|
|
@@ -35,7 +39,9 @@ export const createSSERoom = ({
|
|
|
35
39
|
|
|
36
40
|
const lastKnownId =
|
|
37
41
|
request.headers["last-event-id"] ||
|
|
38
|
-
new URL(request.ressource, request.origin).searchParams.get(
|
|
42
|
+
new URL(request.ressource, request.origin).searchParams.get(
|
|
43
|
+
"last-event-id",
|
|
44
|
+
)
|
|
39
45
|
|
|
40
46
|
if (clients.size >= maxClientAllowed) {
|
|
41
47
|
return {
|
|
@@ -55,7 +61,9 @@ export const createSSERoom = ({
|
|
|
55
61
|
complete,
|
|
56
62
|
}
|
|
57
63
|
clients.add(client)
|
|
58
|
-
logger.debug(
|
|
64
|
+
logger.debug(
|
|
65
|
+
`A client has joined. Number of client in room: ${clients.size}`,
|
|
66
|
+
)
|
|
59
67
|
|
|
60
68
|
if (lastKnownId !== undefined) {
|
|
61
69
|
const previousEvents = getAllEventSince(lastKnownId)
|
|
@@ -101,7 +109,11 @@ export const createSSERoom = ({
|
|
|
101
109
|
}
|
|
102
110
|
})
|
|
103
111
|
|
|
104
|
-
const requestSSEObservable = connectRequestAndRoom(
|
|
112
|
+
const requestSSEObservable = connectRequestAndRoom(
|
|
113
|
+
request,
|
|
114
|
+
room,
|
|
115
|
+
sseRoomObservable,
|
|
116
|
+
)
|
|
105
117
|
|
|
106
118
|
return {
|
|
107
119
|
status: 200,
|
|
@@ -128,7 +140,9 @@ export const createSSERoom = ({
|
|
|
128
140
|
|
|
129
141
|
const sendEvent = (event) => {
|
|
130
142
|
if (event.type !== "comment") {
|
|
131
|
-
logger.debug(
|
|
143
|
+
logger.debug(
|
|
144
|
+
`send ${event.type} event to ${clients.size} client in the room`,
|
|
145
|
+
)
|
|
132
146
|
addEventToHistory(event)
|
|
133
147
|
}
|
|
134
148
|
write(stringifySourceEvent(event))
|
|
@@ -150,7 +164,9 @@ export const createSSERoom = ({
|
|
|
150
164
|
|
|
151
165
|
const keepAlive = () => {
|
|
152
166
|
// maybe that, when an event occurs, we can delay the keep alive event
|
|
153
|
-
logger.debug(
|
|
167
|
+
logger.debug(
|
|
168
|
+
`send keep alive event, number of client listening event source: ${clients.size}`,
|
|
169
|
+
)
|
|
154
170
|
sendEvent({
|
|
155
171
|
type: "comment",
|
|
156
172
|
data: new Date().toLocaleTimeString(),
|