@jsenv/server 15.0.2 → 15.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/README.md +23 -23
- package/package.json +4 -4
- package/src/content_negotiation/pick_accepted_content.js +13 -13
- package/src/content_negotiation/pick_content_encoding.js +25 -25
- package/src/content_negotiation/pick_content_language.js +35 -34
- package/src/content_negotiation/pick_content_type.js +29 -29
- package/src/fetch_filesystem.js +110 -107
- package/src/from_fetch_response.js +6 -6
- package/src/interfacing_with_node/body.js +22 -22
- package/src/interfacing_with_node/observable.js +80 -80
- package/src/interfacing_with_node/observable_from_node_stream.js +35 -35
- package/src/interfacing_with_node/request_factory.js +26 -26
- package/src/interfacing_with_node/write_node_response.js +82 -82
- package/src/internal/colorizeResponseStatus.js +26 -26
- package/src/internal/convertFileSystemErrorToResponseProperties.js +10 -10
- package/src/internal/dns_resolution.js +7 -7
- package/src/internal/etag.js +11 -11
- package/src/internal/filesystem.js +19 -19
- package/src/internal/headersFromObject.js +8 -8
- package/src/internal/headersFromString.js +11 -11
- package/src/internal/headersToObject.js +5 -5
- package/src/internal/headersToString.js +7 -7
- package/src/internal/headers_composition.js +9 -9
- package/src/internal/hostname_parser.js +9 -9
- package/src/internal/listen.js +47 -47
- package/src/internal/listenClientError.js +10 -10
- package/src/internal/listenEvent.js +5 -5
- package/src/internal/listenRequest.js +8 -8
- package/src/internal/listenServerConnectionError.js +19 -19
- package/src/internal/multiple-header.js +50 -50
- package/src/internal/normalizeHeaderName.js +4 -4
- package/src/internal/normalizeHeaderValue.js +2 -2
- package/src/internal/object_composition.js +41 -39
- package/src/internal/response_composition.js +6 -6
- package/src/internal/server-polyglot.js +42 -42
- package/src/internal/server_ips.js +20 -20
- package/src/internal/trackServerPendingConnections.js +23 -23
- package/src/internal/trackServerPendingRequests.js +28 -28
- package/src/main.js +18 -18
- package/src/readRequestBody.js +15 -15
- package/src/serve_directory.js +16 -16
- package/src/server_timing/timing_header.js +24 -24
- package/src/server_timing/timing_measure.js +14 -14
- package/src/service_composition/path_to_regexp.js +173 -168
- package/src/service_composition/routing.js +12 -12
- package/src/service_controller.js +77 -77
- package/src/services/cors/jsenv_service_cors.js +29 -29
- package/src/services/error_handler/jsenv_service_error_handler.js +18 -18
- package/src/services/request_aliases/jsenv_service_request_aliases.js +20 -20
- package/src/services/response_acceptance_check/jsenv_service_response_acceptance_check.js +16 -16
- package/src/sse/sse_room.js +117 -115
- package/src/start_server.js +328 -328
- package/src/stopReasons.js +9 -9
package/src/start_server.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import { isIP } from "node:net"
|
|
2
|
-
import cluster from "node:cluster"
|
|
3
|
-
import { createDetailedMessage, createLogger } from "@jsenv/log"
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
import cluster from "node:cluster";
|
|
3
|
+
import { createDetailedMessage, createLogger } from "@jsenv/log";
|
|
4
4
|
import {
|
|
5
5
|
Abort,
|
|
6
6
|
raceProcessTeardownEvents,
|
|
7
7
|
createCallbackListNotifiedOnce,
|
|
8
|
-
} from "@jsenv/abort"
|
|
9
|
-
import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
|
|
8
|
+
} from "@jsenv/abort";
|
|
9
|
+
import { memoize } from "@jsenv/utils/src/memoize/memoize.js";
|
|
10
10
|
|
|
11
|
-
import { createServiceController } from "./service_controller.js"
|
|
12
|
-
import { timingToServerTimingResponseHeaders } from "./server_timing/timing_header.js"
|
|
13
|
-
import { createPolyglotServer } from "./internal/server-polyglot.js"
|
|
14
|
-
import { trackServerPendingConnections } from "./internal/trackServerPendingConnections.js"
|
|
15
|
-
import { trackServerPendingRequests } from "./internal/trackServerPendingRequests.js"
|
|
11
|
+
import { createServiceController } from "./service_controller.js";
|
|
12
|
+
import { timingToServerTimingResponseHeaders } from "./server_timing/timing_header.js";
|
|
13
|
+
import { createPolyglotServer } from "./internal/server-polyglot.js";
|
|
14
|
+
import { trackServerPendingConnections } from "./internal/trackServerPendingConnections.js";
|
|
15
|
+
import { trackServerPendingRequests } from "./internal/trackServerPendingRequests.js";
|
|
16
16
|
import {
|
|
17
17
|
fromNodeRequest,
|
|
18
18
|
createPushRequest,
|
|
19
19
|
applyRedirectionToRequest,
|
|
20
|
-
} from "./interfacing_with_node/request_factory.js"
|
|
21
|
-
import { writeNodeResponse } from "./interfacing_with_node/write_node_response.js"
|
|
20
|
+
} from "./interfacing_with_node/request_factory.js";
|
|
21
|
+
import { writeNodeResponse } from "./interfacing_with_node/write_node_response.js";
|
|
22
22
|
import {
|
|
23
23
|
statusToType,
|
|
24
24
|
colorizeResponseStatus,
|
|
25
|
-
} from "./internal/colorizeResponseStatus.js"
|
|
26
|
-
import { listen, stopListening } from "./internal/listen.js"
|
|
27
|
-
import { composeTwoResponses } from "./internal/response_composition.js"
|
|
28
|
-
import { listenRequest } from "./internal/listenRequest.js"
|
|
29
|
-
import { listenEvent } from "./internal/listenEvent.js"
|
|
30
|
-
import { listenServerConnectionError } from "./internal/listenServerConnectionError.js"
|
|
25
|
+
} from "./internal/colorizeResponseStatus.js";
|
|
26
|
+
import { listen, stopListening } from "./internal/listen.js";
|
|
27
|
+
import { composeTwoResponses } from "./internal/response_composition.js";
|
|
28
|
+
import { listenRequest } from "./internal/listenRequest.js";
|
|
29
|
+
import { listenEvent } from "./internal/listenEvent.js";
|
|
30
|
+
import { listenServerConnectionError } from "./internal/listenServerConnectionError.js";
|
|
31
31
|
import {
|
|
32
32
|
STOP_REASON_INTERNAL_ERROR,
|
|
33
33
|
STOP_REASON_PROCESS_SIGHUP,
|
|
@@ -36,12 +36,12 @@ import {
|
|
|
36
36
|
STOP_REASON_PROCESS_BEFORE_EXIT,
|
|
37
37
|
STOP_REASON_PROCESS_EXIT,
|
|
38
38
|
STOP_REASON_NOT_SPECIFIED,
|
|
39
|
-
} from "./stopReasons.js"
|
|
40
|
-
import { composeTwoHeaders } from "./internal/headers_composition.js"
|
|
39
|
+
} from "./stopReasons.js";
|
|
40
|
+
import { composeTwoHeaders } from "./internal/headers_composition.js";
|
|
41
41
|
|
|
42
|
-
import { createIpGetters } from "./internal/server_ips.js"
|
|
43
|
-
import { parseHostname } from "./internal/hostname_parser.js"
|
|
44
|
-
import { applyDnsResolution } from "./internal/dns_resolution.js"
|
|
42
|
+
import { createIpGetters } from "./internal/server_ips.js";
|
|
43
|
+
import { parseHostname } from "./internal/hostname_parser.js";
|
|
44
|
+
import { applyDnsResolution } from "./internal/dns_resolution.js";
|
|
45
45
|
|
|
46
46
|
export const startServer = async ({
|
|
47
47
|
signal = new AbortController().signal,
|
|
@@ -82,7 +82,7 @@ export const startServer = async ({
|
|
|
82
82
|
"request headers": JSON.stringify(request.headers, null, " "),
|
|
83
83
|
},
|
|
84
84
|
),
|
|
85
|
-
)
|
|
85
|
+
);
|
|
86
86
|
},
|
|
87
87
|
// timeAllocated to start responding to a request
|
|
88
88
|
// after this delay the server will respond with 504
|
|
@@ -95,28 +95,28 @@ export const startServer = async ({
|
|
|
95
95
|
} = {}) => {
|
|
96
96
|
// param validations
|
|
97
97
|
{
|
|
98
|
-
const unexpectedParamNames = Object.keys(rest)
|
|
98
|
+
const unexpectedParamNames = Object.keys(rest);
|
|
99
99
|
if (unexpectedParamNames.length > 0) {
|
|
100
100
|
throw new TypeError(
|
|
101
101
|
`${unexpectedParamNames.join(",")}: there is no such param`,
|
|
102
|
-
)
|
|
102
|
+
);
|
|
103
103
|
}
|
|
104
104
|
if (https) {
|
|
105
105
|
if (typeof https !== "object") {
|
|
106
|
-
throw new TypeError(`https must be an object, got ${https}`)
|
|
106
|
+
throw new TypeError(`https must be an object, got ${https}`);
|
|
107
107
|
}
|
|
108
|
-
const { certificate, privateKey } = https
|
|
108
|
+
const { certificate, privateKey } = https;
|
|
109
109
|
if (!certificate || !privateKey) {
|
|
110
110
|
throw new TypeError(
|
|
111
111
|
`https must be an object with { certificate, privateKey }`,
|
|
112
|
-
)
|
|
112
|
+
);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
115
115
|
if (http2 && !https) {
|
|
116
|
-
throw new Error(`http2 needs https`)
|
|
116
|
+
throw new Error(`http2 needs https`);
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
const logger = createLogger({ logLevel })
|
|
119
|
+
const logger = createLogger({ logLevel });
|
|
120
120
|
// param warnings and normalization
|
|
121
121
|
{
|
|
122
122
|
if (
|
|
@@ -124,121 +124,121 @@ export const startServer = async ({
|
|
|
124
124
|
https &&
|
|
125
125
|
!allowHttpRequestOnHttps
|
|
126
126
|
) {
|
|
127
|
-
redirectHttpToHttps = true
|
|
127
|
+
redirectHttpToHttps = true;
|
|
128
128
|
}
|
|
129
129
|
if (redirectHttpToHttps && !https) {
|
|
130
|
-
logger.warn(`redirectHttpToHttps ignored because protocol is http`)
|
|
131
|
-
redirectHttpToHttps = false
|
|
130
|
+
logger.warn(`redirectHttpToHttps ignored because protocol is http`);
|
|
131
|
+
redirectHttpToHttps = false;
|
|
132
132
|
}
|
|
133
133
|
if (allowHttpRequestOnHttps && redirectHttpToHttps) {
|
|
134
134
|
logger.warn(
|
|
135
135
|
`redirectHttpToHttps ignored because allowHttpRequestOnHttps is enabled`,
|
|
136
|
-
)
|
|
137
|
-
redirectHttpToHttps = false
|
|
136
|
+
);
|
|
137
|
+
redirectHttpToHttps = false;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
if (allowHttpRequestOnHttps && !https) {
|
|
141
|
-
logger.warn(`allowHttpRequestOnHttps ignored because protocol is http`)
|
|
142
|
-
allowHttpRequestOnHttps = false
|
|
141
|
+
logger.warn(`allowHttpRequestOnHttps ignored because protocol is http`);
|
|
142
|
+
allowHttpRequestOnHttps = false;
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
const server = {}
|
|
147
|
-
const serviceController = createServiceController(services)
|
|
146
|
+
const server = {};
|
|
147
|
+
const serviceController = createServiceController(services);
|
|
148
148
|
const processTeardownEvents = {
|
|
149
149
|
SIGHUP: stopOnExit,
|
|
150
150
|
SIGTERM: stopOnExit,
|
|
151
151
|
SIGINT: stopOnSIGINT,
|
|
152
152
|
beforeExit: stopOnExit,
|
|
153
153
|
exit: stopOnExit,
|
|
154
|
-
}
|
|
154
|
+
};
|
|
155
155
|
|
|
156
|
-
let status = "starting"
|
|
157
|
-
let nodeServer
|
|
158
|
-
const startServerOperation = Abort.startOperation()
|
|
159
|
-
const stopCallbackList = createCallbackListNotifiedOnce()
|
|
156
|
+
let status = "starting";
|
|
157
|
+
let nodeServer;
|
|
158
|
+
const startServerOperation = Abort.startOperation();
|
|
159
|
+
const stopCallbackList = createCallbackListNotifiedOnce();
|
|
160
160
|
const serverOrigins = {
|
|
161
161
|
local: "", // favors hostname when possible
|
|
162
|
-
}
|
|
162
|
+
};
|
|
163
163
|
|
|
164
164
|
try {
|
|
165
|
-
startServerOperation.addAbortSignal(signal)
|
|
165
|
+
startServerOperation.addAbortSignal(signal);
|
|
166
166
|
startServerOperation.addAbortSource((abort) => {
|
|
167
167
|
return raceProcessTeardownEvents(processTeardownEvents, ({ name }) => {
|
|
168
|
-
logger.info(`process teardown (${name}) -> aborting start server`)
|
|
169
|
-
abort()
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
startServerOperation.throwIfAborted()
|
|
168
|
+
logger.info(`process teardown (${name}) -> aborting start server`);
|
|
169
|
+
abort();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
startServerOperation.throwIfAborted();
|
|
173
173
|
nodeServer = await createNodeServer({
|
|
174
174
|
https,
|
|
175
175
|
redirectHttpToHttps,
|
|
176
176
|
allowHttpRequestOnHttps,
|
|
177
177
|
http2,
|
|
178
178
|
http1Allowed,
|
|
179
|
-
})
|
|
180
|
-
startServerOperation.throwIfAborted()
|
|
179
|
+
});
|
|
180
|
+
startServerOperation.throwIfAborted();
|
|
181
181
|
|
|
182
182
|
// https://nodejs.org/api/net.html#net_server_unref
|
|
183
183
|
if (!keepProcessAlive) {
|
|
184
|
-
nodeServer.unref()
|
|
184
|
+
nodeServer.unref();
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
const createOrigin = (hostname) => {
|
|
188
|
-
const protocol = https ? "https" : "http"
|
|
188
|
+
const protocol = https ? "https" : "http";
|
|
189
189
|
if (isIP(hostname) === 6) {
|
|
190
|
-
return `${protocol}://[${hostname}]
|
|
190
|
+
return `${protocol}://[${hostname}]`;
|
|
191
191
|
}
|
|
192
|
-
return `${protocol}://${hostname}
|
|
193
|
-
}
|
|
192
|
+
return `${protocol}://${hostname}`;
|
|
193
|
+
};
|
|
194
194
|
|
|
195
|
-
const ipGetters = createIpGetters()
|
|
196
|
-
let hostnameToListen
|
|
195
|
+
const ipGetters = createIpGetters();
|
|
196
|
+
let hostnameToListen;
|
|
197
197
|
if (acceptAnyIp) {
|
|
198
|
-
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 })
|
|
199
|
-
serverOrigins.local = createOrigin(firstInternalIp)
|
|
200
|
-
serverOrigins.localip = createOrigin(firstInternalIp)
|
|
201
|
-
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 })
|
|
202
|
-
serverOrigins.externalip = createOrigin(firstExternalIp)
|
|
203
|
-
hostnameToListen = preferIpv6 ? "::" : "0.0.0.0"
|
|
198
|
+
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 });
|
|
199
|
+
serverOrigins.local = createOrigin(firstInternalIp);
|
|
200
|
+
serverOrigins.localip = createOrigin(firstInternalIp);
|
|
201
|
+
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 });
|
|
202
|
+
serverOrigins.externalip = createOrigin(firstExternalIp);
|
|
203
|
+
hostnameToListen = preferIpv6 ? "::" : "0.0.0.0";
|
|
204
204
|
} else {
|
|
205
|
-
hostnameToListen = hostname
|
|
205
|
+
hostnameToListen = hostname;
|
|
206
206
|
}
|
|
207
|
-
const hostnameInfo = parseHostname(hostname)
|
|
207
|
+
const hostnameInfo = parseHostname(hostname);
|
|
208
208
|
if (hostnameInfo.type === "ip") {
|
|
209
209
|
if (acceptAnyIp) {
|
|
210
210
|
throw new Error(
|
|
211
211
|
`hostname cannot be an ip when acceptAnyIp is enabled, got ${hostname}`,
|
|
212
|
-
)
|
|
212
|
+
);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
preferIpv6 = hostnameInfo.version === 6
|
|
216
|
-
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 })
|
|
217
|
-
serverOrigins.local = createOrigin(firstInternalIp)
|
|
218
|
-
serverOrigins.localip = createOrigin(firstInternalIp)
|
|
215
|
+
preferIpv6 = hostnameInfo.version === 6;
|
|
216
|
+
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 });
|
|
217
|
+
serverOrigins.local = createOrigin(firstInternalIp);
|
|
218
|
+
serverOrigins.localip = createOrigin(firstInternalIp);
|
|
219
219
|
if (hostnameInfo.label === "unspecified") {
|
|
220
|
-
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 })
|
|
221
|
-
serverOrigins.externalip = createOrigin(firstExternalIp)
|
|
220
|
+
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 });
|
|
221
|
+
serverOrigins.externalip = createOrigin(firstExternalIp);
|
|
222
222
|
} else if (hostnameInfo.label === "loopback") {
|
|
223
223
|
// nothing
|
|
224
224
|
} else {
|
|
225
|
-
serverOrigins.local = createOrigin(hostname)
|
|
225
|
+
serverOrigins.local = createOrigin(hostname);
|
|
226
226
|
}
|
|
227
227
|
} else {
|
|
228
228
|
const hostnameDnsResolution = await applyDnsResolution(hostname, {
|
|
229
229
|
verbatim: true,
|
|
230
|
-
})
|
|
230
|
+
});
|
|
231
231
|
if (hostnameDnsResolution) {
|
|
232
|
-
const hostnameIp = hostnameDnsResolution.address
|
|
233
|
-
serverOrigins.localip = createOrigin(hostnameIp)
|
|
234
|
-
serverOrigins.local = createOrigin(hostname)
|
|
232
|
+
const hostnameIp = hostnameDnsResolution.address;
|
|
233
|
+
serverOrigins.localip = createOrigin(hostnameIp);
|
|
234
|
+
serverOrigins.local = createOrigin(hostname);
|
|
235
235
|
} else {
|
|
236
|
-
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 })
|
|
236
|
+
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 });
|
|
237
237
|
// fallback to internal ip because there is no ip
|
|
238
238
|
// associated to this hostname on operating system (in hosts file)
|
|
239
|
-
hostname = firstInternalIp
|
|
240
|
-
hostnameToListen = firstInternalIp
|
|
241
|
-
serverOrigins.local = createOrigin(firstInternalIp)
|
|
239
|
+
hostname = firstInternalIp;
|
|
240
|
+
hostnameToListen = firstInternalIp;
|
|
241
|
+
serverOrigins.local = createOrigin(firstInternalIp);
|
|
242
242
|
}
|
|
243
243
|
}
|
|
244
244
|
|
|
@@ -248,20 +248,20 @@ export const startServer = async ({
|
|
|
248
248
|
port,
|
|
249
249
|
portHint,
|
|
250
250
|
hostname: hostnameToListen,
|
|
251
|
-
})
|
|
251
|
+
});
|
|
252
252
|
|
|
253
253
|
// normalize origins (remove :80 when port is 80 for instance)
|
|
254
254
|
Object.keys(serverOrigins).forEach((key) => {
|
|
255
|
-
serverOrigins[key] = new URL(`${serverOrigins[key]}:${port}`).origin
|
|
256
|
-
})
|
|
255
|
+
serverOrigins[key] = new URL(`${serverOrigins[key]}:${port}`).origin;
|
|
256
|
+
});
|
|
257
257
|
|
|
258
|
-
serviceController.callHooks("serverListening", { port })
|
|
258
|
+
serviceController.callHooks("serverListening", { port });
|
|
259
259
|
startServerOperation.addAbortCallback(async () => {
|
|
260
|
-
await stopListening(nodeServer)
|
|
261
|
-
})
|
|
262
|
-
startServerOperation.throwIfAborted()
|
|
260
|
+
await stopListening(nodeServer);
|
|
261
|
+
});
|
|
262
|
+
startServerOperation.throwIfAborted();
|
|
263
263
|
} finally {
|
|
264
|
-
await startServerOperation.end()
|
|
264
|
+
await startServerOperation.end();
|
|
265
265
|
}
|
|
266
266
|
|
|
267
267
|
// the main server origin
|
|
@@ -274,116 +274,116 @@ export const startServer = async ({
|
|
|
274
274
|
// for hostnames, not for ips
|
|
275
275
|
// so we prefer https://locahost or https://local_hostname
|
|
276
276
|
// over the ip
|
|
277
|
-
const serverOrigin = serverOrigins.local
|
|
277
|
+
const serverOrigin = serverOrigins.local;
|
|
278
278
|
|
|
279
279
|
// now the server is started (listening) it cannot be aborted anymore
|
|
280
280
|
// (otherwise an AbortError is thrown to the code calling "startServer")
|
|
281
281
|
// we can proceed to create a stop function to stop it gacefully
|
|
282
282
|
// and add a request handler
|
|
283
283
|
stopCallbackList.add(({ reason }) => {
|
|
284
|
-
logger.info(`${serverName} stopping server (reason: ${reason})`)
|
|
285
|
-
})
|
|
284
|
+
logger.info(`${serverName} stopping server (reason: ${reason})`);
|
|
285
|
+
});
|
|
286
286
|
stopCallbackList.add(async () => {
|
|
287
|
-
await stopListening(nodeServer)
|
|
288
|
-
})
|
|
289
|
-
let stoppedResolve
|
|
287
|
+
await stopListening(nodeServer);
|
|
288
|
+
});
|
|
289
|
+
let stoppedResolve;
|
|
290
290
|
const stoppedPromise = new Promise((resolve) => {
|
|
291
|
-
stoppedResolve = resolve
|
|
292
|
-
})
|
|
291
|
+
stoppedResolve = resolve;
|
|
292
|
+
});
|
|
293
293
|
const stop = memoize(async (reason = STOP_REASON_NOT_SPECIFIED) => {
|
|
294
|
-
status = "stopping"
|
|
295
|
-
await Promise.all(stopCallbackList.notify({ reason }))
|
|
296
|
-
serviceController.callHooks("serverStopped", { reason })
|
|
297
|
-
status = "stopped"
|
|
298
|
-
stoppedResolve(reason)
|
|
299
|
-
})
|
|
294
|
+
status = "stopping";
|
|
295
|
+
await Promise.all(stopCallbackList.notify({ reason }));
|
|
296
|
+
serviceController.callHooks("serverStopped", { reason });
|
|
297
|
+
status = "stopped";
|
|
298
|
+
stoppedResolve(reason);
|
|
299
|
+
});
|
|
300
300
|
|
|
301
301
|
const cancelProcessTeardownRace = raceProcessTeardownEvents(
|
|
302
302
|
processTeardownEvents,
|
|
303
303
|
(winner) => {
|
|
304
|
-
stop(PROCESS_TEARDOWN_EVENTS_MAP[winner.name])
|
|
304
|
+
stop(PROCESS_TEARDOWN_EVENTS_MAP[winner.name]);
|
|
305
305
|
},
|
|
306
|
-
)
|
|
307
|
-
stopCallbackList.add(cancelProcessTeardownRace)
|
|
306
|
+
);
|
|
307
|
+
stopCallbackList.add(cancelProcessTeardownRace);
|
|
308
308
|
|
|
309
309
|
const onError = (error) => {
|
|
310
310
|
if (status === "stopping" && error.code === "ECONNRESET") {
|
|
311
|
-
return
|
|
311
|
+
return;
|
|
312
312
|
}
|
|
313
|
-
throw error
|
|
314
|
-
}
|
|
313
|
+
throw error;
|
|
314
|
+
};
|
|
315
315
|
|
|
316
|
-
status = "opened"
|
|
316
|
+
status = "opened";
|
|
317
317
|
|
|
318
318
|
const removeConnectionErrorListener = listenServerConnectionError(
|
|
319
319
|
nodeServer,
|
|
320
320
|
onError,
|
|
321
|
-
)
|
|
322
|
-
stopCallbackList.add(removeConnectionErrorListener)
|
|
321
|
+
);
|
|
322
|
+
stopCallbackList.add(removeConnectionErrorListener);
|
|
323
323
|
|
|
324
324
|
const connectionsTracker = trackServerPendingConnections(nodeServer, {
|
|
325
325
|
http2,
|
|
326
|
-
})
|
|
326
|
+
});
|
|
327
327
|
// opened connection must be shutdown before the close event is emitted
|
|
328
|
-
stopCallbackList.add(connectionsTracker.stop)
|
|
328
|
+
stopCallbackList.add(connectionsTracker.stop);
|
|
329
329
|
|
|
330
330
|
const pendingRequestsTracker = trackServerPendingRequests(nodeServer, {
|
|
331
331
|
http2,
|
|
332
|
-
})
|
|
332
|
+
});
|
|
333
333
|
// ensure pending requests got a response from the server
|
|
334
334
|
stopCallbackList.add((reason) => {
|
|
335
335
|
pendingRequestsTracker.stop({
|
|
336
336
|
status: reason === STOP_REASON_INTERNAL_ERROR ? 500 : 503,
|
|
337
337
|
reason,
|
|
338
|
-
})
|
|
339
|
-
})
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
340
|
|
|
341
341
|
request: {
|
|
342
342
|
const requestCallback = async (nodeRequest, nodeResponse) => {
|
|
343
343
|
// pause the stream to let a chance to "requestToResponse"
|
|
344
344
|
// to call "requestRequestBody". Without this the request body readable stream
|
|
345
345
|
// might be closed when we'll try to attach "data" and "end" listeners to it
|
|
346
|
-
nodeRequest.pause()
|
|
346
|
+
nodeRequest.pause();
|
|
347
347
|
if (!nagle) {
|
|
348
|
-
nodeRequest.connection.setNoDelay(true)
|
|
348
|
+
nodeRequest.connection.setNoDelay(true);
|
|
349
349
|
}
|
|
350
350
|
if (redirectHttpToHttps && !nodeRequest.connection.encrypted) {
|
|
351
351
|
nodeResponse.writeHead(301, {
|
|
352
352
|
location: `${serverOrigin}${nodeRequest.url}`,
|
|
353
|
-
})
|
|
354
|
-
nodeResponse.end()
|
|
355
|
-
return
|
|
353
|
+
});
|
|
354
|
+
nodeResponse.end();
|
|
355
|
+
return;
|
|
356
356
|
}
|
|
357
357
|
|
|
358
|
-
const receiveRequestOperation = Abort.startOperation()
|
|
358
|
+
const receiveRequestOperation = Abort.startOperation();
|
|
359
359
|
receiveRequestOperation.addAbortSource((abort) => {
|
|
360
360
|
const closeEventCallback = () => {
|
|
361
361
|
if (nodeRequest.complete) {
|
|
362
|
-
receiveRequestOperation.end()
|
|
362
|
+
receiveRequestOperation.end();
|
|
363
363
|
} else {
|
|
364
|
-
nodeResponse.destroy()
|
|
365
|
-
abort()
|
|
364
|
+
nodeResponse.destroy();
|
|
365
|
+
abort();
|
|
366
366
|
}
|
|
367
|
-
}
|
|
368
|
-
nodeRequest.once("close", closeEventCallback)
|
|
367
|
+
};
|
|
368
|
+
nodeRequest.once("close", closeEventCallback);
|
|
369
369
|
return () => {
|
|
370
|
-
nodeRequest.removeListener("close", closeEventCallback)
|
|
371
|
-
}
|
|
372
|
-
})
|
|
370
|
+
nodeRequest.removeListener("close", closeEventCallback);
|
|
371
|
+
};
|
|
372
|
+
});
|
|
373
373
|
receiveRequestOperation.addAbortSource((abort) => {
|
|
374
|
-
return stopCallbackList.add(abort)
|
|
375
|
-
})
|
|
374
|
+
return stopCallbackList.add(abort);
|
|
375
|
+
});
|
|
376
376
|
|
|
377
|
-
const sendResponseOperation = Abort.startOperation()
|
|
378
|
-
sendResponseOperation.addAbortSignal(receiveRequestOperation.signal)
|
|
377
|
+
const sendResponseOperation = Abort.startOperation();
|
|
378
|
+
sendResponseOperation.addAbortSignal(receiveRequestOperation.signal);
|
|
379
379
|
sendResponseOperation.addAbortSource((abort) => {
|
|
380
|
-
return stopCallbackList.add(abort)
|
|
381
|
-
})
|
|
380
|
+
return stopCallbackList.add(abort);
|
|
381
|
+
});
|
|
382
382
|
|
|
383
383
|
const request = fromNodeRequest(nodeRequest, {
|
|
384
384
|
serverOrigin,
|
|
385
385
|
signal: receiveRequestOperation.signal,
|
|
386
|
-
})
|
|
386
|
+
});
|
|
387
387
|
|
|
388
388
|
// Handling request is asynchronous, we buffer logs for that request
|
|
389
389
|
// until we know what happens with that request
|
|
@@ -392,67 +392,67 @@ export const startServer = async ({
|
|
|
392
392
|
const rootRequestNode = {
|
|
393
393
|
logs: [],
|
|
394
394
|
children: [],
|
|
395
|
-
}
|
|
395
|
+
};
|
|
396
396
|
const addRequestLog = (node, { type, value }) => {
|
|
397
|
-
node.logs.push({ type, value })
|
|
398
|
-
}
|
|
397
|
+
node.logs.push({ type, value });
|
|
398
|
+
};
|
|
399
399
|
const onRequestHandled = (node) => {
|
|
400
400
|
if (node !== rootRequestNode) {
|
|
401
401
|
// keep buffering until root request write logs for everyone
|
|
402
|
-
return
|
|
402
|
+
return;
|
|
403
403
|
}
|
|
404
404
|
const prefixLines = (string, prefix) => {
|
|
405
|
-
return string.replace(/^(?!\s*$)/gm, prefix)
|
|
406
|
-
}
|
|
405
|
+
return string.replace(/^(?!\s*$)/gm, prefix);
|
|
406
|
+
};
|
|
407
407
|
const writeLog = (
|
|
408
408
|
{ type, value },
|
|
409
409
|
{ someLogIsError, someLogIsWarn, depth },
|
|
410
410
|
) => {
|
|
411
411
|
if (depth > 0) {
|
|
412
|
-
value = prefixLines(value, " ".repeat(depth))
|
|
412
|
+
value = prefixLines(value, " ".repeat(depth));
|
|
413
413
|
}
|
|
414
414
|
if (type === "info") {
|
|
415
415
|
if (someLogIsError) {
|
|
416
|
-
type = "error"
|
|
416
|
+
type = "error";
|
|
417
417
|
} else if (someLogIsWarn) {
|
|
418
|
-
type = "warn"
|
|
418
|
+
type = "warn";
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
|
-
logger[type](value)
|
|
422
|
-
}
|
|
421
|
+
logger[type](value);
|
|
422
|
+
};
|
|
423
423
|
const visitRequestNodeToLog = (requestNode, depth) => {
|
|
424
|
-
let someLogIsError = false
|
|
425
|
-
let someLogIsWarn = false
|
|
424
|
+
let someLogIsError = false;
|
|
425
|
+
let someLogIsWarn = false;
|
|
426
426
|
requestNode.logs.forEach((log) => {
|
|
427
427
|
if (log.type === "error") {
|
|
428
|
-
someLogIsError = true
|
|
428
|
+
someLogIsError = true;
|
|
429
429
|
}
|
|
430
430
|
if (log.type === "warn") {
|
|
431
|
-
someLogIsWarn = true
|
|
431
|
+
someLogIsWarn = true;
|
|
432
432
|
}
|
|
433
|
-
})
|
|
433
|
+
});
|
|
434
434
|
|
|
435
|
-
const firstLog = requestNode.logs.shift()
|
|
436
|
-
const lastLog = requestNode.logs.pop()
|
|
437
|
-
const middleLogs = requestNode.logs
|
|
435
|
+
const firstLog = requestNode.logs.shift();
|
|
436
|
+
const lastLog = requestNode.logs.pop();
|
|
437
|
+
const middleLogs = requestNode.logs;
|
|
438
438
|
|
|
439
|
-
writeLog(firstLog, { someLogIsError, someLogIsWarn, depth })
|
|
439
|
+
writeLog(firstLog, { someLogIsError, someLogIsWarn, depth });
|
|
440
440
|
middleLogs.forEach((log) => {
|
|
441
|
-
writeLog(log, { someLogIsError, someLogIsWarn, depth })
|
|
442
|
-
})
|
|
441
|
+
writeLog(log, { someLogIsError, someLogIsWarn, depth });
|
|
442
|
+
});
|
|
443
443
|
requestNode.children.forEach((child) => {
|
|
444
|
-
visitRequestNodeToLog(child, depth + 1)
|
|
445
|
-
})
|
|
444
|
+
visitRequestNodeToLog(child, depth + 1);
|
|
445
|
+
});
|
|
446
446
|
if (lastLog) {
|
|
447
447
|
writeLog(lastLog, {
|
|
448
448
|
someLogIsError,
|
|
449
449
|
someLogIsWarn,
|
|
450
450
|
depth: depth + 1,
|
|
451
|
-
})
|
|
451
|
+
});
|
|
452
452
|
}
|
|
453
|
-
}
|
|
454
|
-
visitRequestNodeToLog(rootRequestNode, 0)
|
|
455
|
-
}
|
|
453
|
+
};
|
|
454
|
+
visitRequestNodeToLog(rootRequestNode, 0);
|
|
455
|
+
};
|
|
456
456
|
nodeRequest.on("error", (error) => {
|
|
457
457
|
if (error.message === "aborted") {
|
|
458
458
|
addRequestLog(rootRequestNode, {
|
|
@@ -460,7 +460,7 @@ export const startServer = async ({
|
|
|
460
460
|
value: createDetailedMessage(`request aborted by client`, {
|
|
461
461
|
"error message": error.message,
|
|
462
462
|
}),
|
|
463
|
-
})
|
|
463
|
+
});
|
|
464
464
|
} else {
|
|
465
465
|
// I'm not sure this can happen but it's here in case
|
|
466
466
|
addRequestLog(rootRequestNode, {
|
|
@@ -468,12 +468,12 @@ export const startServer = async ({
|
|
|
468
468
|
value: createDetailedMessage(`"error" event emitted on request`, {
|
|
469
469
|
"error stack": error.stack,
|
|
470
470
|
}),
|
|
471
|
-
})
|
|
471
|
+
});
|
|
472
472
|
}
|
|
473
|
-
})
|
|
473
|
+
});
|
|
474
474
|
|
|
475
475
|
const pushResponse = async ({ path, method }, { requestNode }) => {
|
|
476
|
-
const http2Stream = nodeResponse.stream
|
|
476
|
+
const http2Stream = nodeResponse.stream;
|
|
477
477
|
|
|
478
478
|
// being able to push a stream is nice to have
|
|
479
479
|
// so when it fails it's not critical
|
|
@@ -486,12 +486,12 @@ export const startServer = async ({
|
|
|
486
486
|
"error stack": e.stack,
|
|
487
487
|
},
|
|
488
488
|
),
|
|
489
|
-
})
|
|
490
|
-
}
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
491
|
|
|
492
492
|
// not aborted, let's try to push a stream into that response
|
|
493
493
|
// https://nodejs.org/docs/latest-v16.x/api/http2.html#http2streampushstreamheaders-options-callback
|
|
494
|
-
let pushStream
|
|
494
|
+
let pushStream;
|
|
495
495
|
try {
|
|
496
496
|
pushStream = await new Promise((resolve, reject) => {
|
|
497
497
|
http2Stream.pushStream(
|
|
@@ -505,55 +505,55 @@ export const startServer = async ({
|
|
|
505
505
|
// headers
|
|
506
506
|
) => {
|
|
507
507
|
if (error) {
|
|
508
|
-
reject(error)
|
|
508
|
+
reject(error);
|
|
509
509
|
}
|
|
510
|
-
resolve(pushStream)
|
|
510
|
+
resolve(pushStream);
|
|
511
511
|
},
|
|
512
|
-
)
|
|
513
|
-
})
|
|
512
|
+
);
|
|
513
|
+
});
|
|
514
514
|
} catch (e) {
|
|
515
|
-
onPushStreamError(e)
|
|
516
|
-
return
|
|
515
|
+
onPushStreamError(e);
|
|
516
|
+
return;
|
|
517
517
|
}
|
|
518
518
|
|
|
519
|
-
const abortController = new AbortController()
|
|
519
|
+
const abortController = new AbortController();
|
|
520
520
|
// It's possible to get NGHTTP2_REFUSED_STREAM errors here
|
|
521
521
|
// https://github.com/nodejs/node/issues/20824
|
|
522
522
|
const pushErrorCallback = (error) => {
|
|
523
|
-
onPushStreamError(error)
|
|
524
|
-
abortController.abort()
|
|
525
|
-
}
|
|
526
|
-
pushStream.on("error", pushErrorCallback)
|
|
523
|
+
onPushStreamError(error);
|
|
524
|
+
abortController.abort();
|
|
525
|
+
};
|
|
526
|
+
pushStream.on("error", pushErrorCallback);
|
|
527
527
|
sendResponseOperation.addEndCallback(() => {
|
|
528
|
-
pushStream.removeListener("error", onPushStreamError)
|
|
529
|
-
})
|
|
528
|
+
pushStream.removeListener("error", onPushStreamError);
|
|
529
|
+
});
|
|
530
530
|
|
|
531
531
|
await sendResponseOperation.withSignal(async (signal) => {
|
|
532
|
-
const pushResponseOperation = Abort.startOperation()
|
|
533
|
-
pushResponseOperation.addAbortSignal(signal)
|
|
534
|
-
pushResponseOperation.addAbortSignal(abortController.signal)
|
|
532
|
+
const pushResponseOperation = Abort.startOperation();
|
|
533
|
+
pushResponseOperation.addAbortSignal(signal);
|
|
534
|
+
pushResponseOperation.addAbortSignal(abortController.signal);
|
|
535
535
|
|
|
536
536
|
const pushRequest = createPushRequest(request, {
|
|
537
537
|
signal: pushResponseOperation.signal,
|
|
538
538
|
pathname: path,
|
|
539
539
|
method,
|
|
540
|
-
})
|
|
540
|
+
});
|
|
541
541
|
|
|
542
542
|
try {
|
|
543
543
|
const responseProperties = await handleRequest(pushRequest, {
|
|
544
544
|
requestNode,
|
|
545
|
-
})
|
|
545
|
+
});
|
|
546
546
|
if (!abortController.signal.aborted) {
|
|
547
547
|
if (pushStream.destroyed) {
|
|
548
|
-
abortController.abort()
|
|
548
|
+
abortController.abort();
|
|
549
549
|
} else if (!http2Stream.pushAllowed) {
|
|
550
|
-
abortController.abort()
|
|
550
|
+
abortController.abort();
|
|
551
551
|
} else if (responseProperties.requestAborted) {
|
|
552
552
|
} else {
|
|
553
553
|
const responseLength =
|
|
554
|
-
responseProperties.headers["content-length"] || 0
|
|
554
|
+
responseProperties.headers["content-length"] || 0;
|
|
555
555
|
const { effectiveRecvDataLength, remoteWindowSize } =
|
|
556
|
-
http2Stream.session.state
|
|
556
|
+
http2Stream.session.state;
|
|
557
557
|
if (
|
|
558
558
|
effectiveRecvDataLength + responseLength >
|
|
559
559
|
remoteWindowSize
|
|
@@ -561,8 +561,8 @@ export const startServer = async ({
|
|
|
561
561
|
addRequestLog(requestNode, {
|
|
562
562
|
type: "debug",
|
|
563
563
|
value: `Aborting stream to prevent exceeding remoteWindowSize`,
|
|
564
|
-
})
|
|
565
|
-
abortController.abort()
|
|
564
|
+
});
|
|
565
|
+
abortController.abort();
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
568
|
}
|
|
@@ -572,37 +572,37 @@ export const startServer = async ({
|
|
|
572
572
|
requestNode,
|
|
573
573
|
responseStream: pushStream,
|
|
574
574
|
responseProperties,
|
|
575
|
-
})
|
|
575
|
+
});
|
|
576
576
|
} finally {
|
|
577
|
-
await pushResponseOperation.end()
|
|
577
|
+
await pushResponseOperation.end();
|
|
578
578
|
}
|
|
579
|
-
})
|
|
580
|
-
}
|
|
579
|
+
});
|
|
580
|
+
};
|
|
581
581
|
|
|
582
582
|
const handleRequest = async (request, { requestNode }) => {
|
|
583
|
-
let requestReceivedMeasure
|
|
583
|
+
let requestReceivedMeasure;
|
|
584
584
|
if (serverTiming) {
|
|
585
|
-
requestReceivedMeasure = performance.now()
|
|
585
|
+
requestReceivedMeasure = performance.now();
|
|
586
586
|
}
|
|
587
587
|
addRequestLog(requestNode, {
|
|
588
588
|
type: "info",
|
|
589
589
|
value: request.parent
|
|
590
590
|
? `Push ${request.resource}`
|
|
591
591
|
: `${request.method} ${request.url}`,
|
|
592
|
-
})
|
|
592
|
+
});
|
|
593
593
|
const warn = (value) => {
|
|
594
594
|
addRequestLog(requestNode, {
|
|
595
595
|
type: "warn",
|
|
596
596
|
value,
|
|
597
|
-
})
|
|
598
|
-
}
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
599
|
|
|
600
|
-
let requestWaitingTimeout
|
|
600
|
+
let requestWaitingTimeout;
|
|
601
601
|
if (requestWaitingMs) {
|
|
602
602
|
requestWaitingTimeout = setTimeout(
|
|
603
603
|
() => requestWaitingCallback({ request, warn, requestWaitingMs }),
|
|
604
604
|
requestWaitingMs,
|
|
605
|
-
).unref()
|
|
605
|
+
).unref();
|
|
606
606
|
}
|
|
607
607
|
|
|
608
608
|
serviceController.callHooks(
|
|
@@ -615,16 +615,16 @@ export const startServer = async ({
|
|
|
615
615
|
original: request.original || request,
|
|
616
616
|
previous: request,
|
|
617
617
|
...newRequestProperties,
|
|
618
|
-
})
|
|
618
|
+
});
|
|
619
619
|
}
|
|
620
620
|
},
|
|
621
|
-
)
|
|
621
|
+
);
|
|
622
622
|
|
|
623
|
-
let handleRequestReturnValue
|
|
624
|
-
let errorWhileHandlingRequest = null
|
|
625
|
-
let handleRequestTimings = serverTiming ? {} : null
|
|
623
|
+
let handleRequestReturnValue;
|
|
624
|
+
let errorWhileHandlingRequest = null;
|
|
625
|
+
let handleRequestTimings = serverTiming ? {} : null;
|
|
626
626
|
|
|
627
|
-
let timeout
|
|
627
|
+
let timeout;
|
|
628
628
|
const timeoutPromise = new Promise((resolve) => {
|
|
629
629
|
timeout = setTimeout(() => {
|
|
630
630
|
resolve({
|
|
@@ -636,9 +636,9 @@ export const startServer = async ({
|
|
|
636
636
|
statusText: `server timeout after ${
|
|
637
637
|
responseTimeout / 1000
|
|
638
638
|
}s waiting to handle request`,
|
|
639
|
-
})
|
|
640
|
-
}, responseTimeout)
|
|
641
|
-
})
|
|
639
|
+
});
|
|
640
|
+
}, responseTimeout);
|
|
641
|
+
});
|
|
642
642
|
const handleRequestPromise = serviceController.callAsyncHooksUntil(
|
|
643
643
|
"handleRequest",
|
|
644
644
|
request,
|
|
@@ -650,29 +650,29 @@ export const startServer = async ({
|
|
|
650
650
|
addRequestLog(requestNode, {
|
|
651
651
|
type: "warn",
|
|
652
652
|
value: `response push ignored because path is invalid (must be a string starting with "/", found ${path})`,
|
|
653
|
-
})
|
|
654
|
-
return
|
|
653
|
+
});
|
|
654
|
+
return;
|
|
655
655
|
}
|
|
656
656
|
if (!request.http2) {
|
|
657
657
|
addRequestLog(requestNode, {
|
|
658
658
|
type: "warn",
|
|
659
659
|
value: `response push ignored because request is not http2`,
|
|
660
|
-
})
|
|
661
|
-
return
|
|
660
|
+
});
|
|
661
|
+
return;
|
|
662
662
|
}
|
|
663
|
-
const canPushStream = testCanPushStream(nodeResponse.stream)
|
|
663
|
+
const canPushStream = testCanPushStream(nodeResponse.stream);
|
|
664
664
|
if (!canPushStream.can) {
|
|
665
665
|
addRequestLog(requestNode, {
|
|
666
666
|
type: "debug",
|
|
667
667
|
value: `response push ignored because ${canPushStream.reason}`,
|
|
668
|
-
})
|
|
669
|
-
return
|
|
668
|
+
});
|
|
669
|
+
return;
|
|
670
670
|
}
|
|
671
671
|
|
|
672
|
-
let preventedByService = null
|
|
672
|
+
let preventedByService = null;
|
|
673
673
|
const prevent = () => {
|
|
674
|
-
preventedByService = serviceController.getCurrentService()
|
|
675
|
-
}
|
|
674
|
+
preventedByService = serviceController.getCurrentService();
|
|
675
|
+
};
|
|
676
676
|
serviceController.callHooksUntil(
|
|
677
677
|
"onResponsePush",
|
|
678
678
|
{ path, method },
|
|
@@ -682,44 +682,44 @@ export const startServer = async ({
|
|
|
682
682
|
prevent,
|
|
683
683
|
},
|
|
684
684
|
() => preventedByService,
|
|
685
|
-
)
|
|
685
|
+
);
|
|
686
686
|
if (preventedByService) {
|
|
687
687
|
addRequestLog(requestNode, {
|
|
688
688
|
type: "debug",
|
|
689
689
|
value: `response push prevented by "${preventedByService.name}" service`,
|
|
690
|
-
})
|
|
691
|
-
return
|
|
690
|
+
});
|
|
691
|
+
return;
|
|
692
692
|
}
|
|
693
693
|
|
|
694
|
-
const requestChildNode = { logs: [], children: [] }
|
|
695
|
-
requestNode.children.push(requestChildNode)
|
|
694
|
+
const requestChildNode = { logs: [], children: [] };
|
|
695
|
+
requestNode.children.push(requestChildNode);
|
|
696
696
|
await pushResponse(
|
|
697
697
|
{ path, method },
|
|
698
698
|
{
|
|
699
699
|
requestNode: requestChildNode,
|
|
700
700
|
parentHttp2Stream: nodeResponse.stream,
|
|
701
701
|
},
|
|
702
|
-
)
|
|
702
|
+
);
|
|
703
703
|
},
|
|
704
704
|
},
|
|
705
|
-
)
|
|
705
|
+
);
|
|
706
706
|
try {
|
|
707
707
|
handleRequestReturnValue = await Promise.race([
|
|
708
708
|
timeoutPromise,
|
|
709
709
|
handleRequestPromise,
|
|
710
|
-
])
|
|
710
|
+
]);
|
|
711
711
|
} catch (e) {
|
|
712
|
-
errorWhileHandlingRequest = e
|
|
712
|
+
errorWhileHandlingRequest = e;
|
|
713
713
|
}
|
|
714
|
-
clearTimeout(timeout)
|
|
714
|
+
clearTimeout(timeout);
|
|
715
715
|
|
|
716
|
-
let responseProperties
|
|
716
|
+
let responseProperties;
|
|
717
717
|
if (errorWhileHandlingRequest) {
|
|
718
718
|
if (
|
|
719
719
|
errorWhileHandlingRequest.name === "AbortError" &&
|
|
720
720
|
request.signal.aborted
|
|
721
721
|
) {
|
|
722
|
-
responseProperties = { requestAborted: true }
|
|
722
|
+
responseProperties = { requestAborted: true };
|
|
723
723
|
} else {
|
|
724
724
|
// internal error, create 500 response
|
|
725
725
|
if (
|
|
@@ -730,7 +730,7 @@ export const startServer = async ({
|
|
|
730
730
|
stopOnInternalError
|
|
731
731
|
) {
|
|
732
732
|
// il faudrais pouvoir stop que les autres response ?
|
|
733
|
-
stop(STOP_REASON_INTERNAL_ERROR)
|
|
733
|
+
stop(STOP_REASON_INTERNAL_ERROR);
|
|
734
734
|
}
|
|
735
735
|
const handleErrorReturnValue =
|
|
736
736
|
await serviceController.callAsyncHooksUntil(
|
|
@@ -740,9 +740,9 @@ export const startServer = async ({
|
|
|
740
740
|
request,
|
|
741
741
|
warn,
|
|
742
742
|
},
|
|
743
|
-
)
|
|
743
|
+
);
|
|
744
744
|
if (!handleErrorReturnValue) {
|
|
745
|
-
throw errorWhileHandlingRequest
|
|
745
|
+
throw errorWhileHandlingRequest;
|
|
746
746
|
}
|
|
747
747
|
addRequestLog(requestNode, {
|
|
748
748
|
type: "error",
|
|
@@ -752,7 +752,7 @@ export const startServer = async ({
|
|
|
752
752
|
"error stack": errorWhileHandlingRequest.stack,
|
|
753
753
|
},
|
|
754
754
|
),
|
|
755
|
-
})
|
|
755
|
+
});
|
|
756
756
|
responseProperties = composeTwoResponses(
|
|
757
757
|
{
|
|
758
758
|
status: 500,
|
|
@@ -764,7 +764,7 @@ export const startServer = async ({
|
|
|
764
764
|
},
|
|
765
765
|
},
|
|
766
766
|
handleErrorReturnValue,
|
|
767
|
-
)
|
|
767
|
+
);
|
|
768
768
|
}
|
|
769
769
|
} else {
|
|
770
770
|
const {
|
|
@@ -774,7 +774,7 @@ export const startServer = async ({
|
|
|
774
774
|
headers = {},
|
|
775
775
|
body,
|
|
776
776
|
...rest
|
|
777
|
-
} = handleRequestReturnValue || {}
|
|
777
|
+
} = handleRequestReturnValue || {};
|
|
778
778
|
responseProperties = {
|
|
779
779
|
status,
|
|
780
780
|
statusText,
|
|
@@ -782,25 +782,25 @@ export const startServer = async ({
|
|
|
782
782
|
headers,
|
|
783
783
|
body,
|
|
784
784
|
...rest,
|
|
785
|
-
}
|
|
785
|
+
};
|
|
786
786
|
}
|
|
787
787
|
|
|
788
788
|
if (serverTiming) {
|
|
789
|
-
const responseReadyMeasure = performance.now()
|
|
789
|
+
const responseReadyMeasure = performance.now();
|
|
790
790
|
const timeToStartResponding =
|
|
791
|
-
responseReadyMeasure - requestReceivedMeasure
|
|
791
|
+
responseReadyMeasure - requestReceivedMeasure;
|
|
792
792
|
const serverTiming = {
|
|
793
793
|
...handleRequestTimings,
|
|
794
794
|
...responseProperties.timing,
|
|
795
795
|
"time to start responding": timeToStartResponding,
|
|
796
|
-
}
|
|
796
|
+
};
|
|
797
797
|
responseProperties.headers = composeTwoHeaders(
|
|
798
798
|
responseProperties.headers,
|
|
799
799
|
timingToServerTimingResponseHeaders(serverTiming),
|
|
800
|
-
)
|
|
800
|
+
);
|
|
801
801
|
}
|
|
802
802
|
if (requestWaitingMs) {
|
|
803
|
-
clearTimeout(requestWaitingTimeout)
|
|
803
|
+
clearTimeout(requestWaitingTimeout);
|
|
804
804
|
}
|
|
805
805
|
if (
|
|
806
806
|
request.method !== "HEAD" &&
|
|
@@ -810,7 +810,7 @@ export const startServer = async ({
|
|
|
810
810
|
addRequestLog(requestNode, {
|
|
811
811
|
type: "warn",
|
|
812
812
|
value: `content-length header is ${responseProperties.headers["content-length"]} but body is empty`,
|
|
813
|
-
})
|
|
813
|
+
});
|
|
814
814
|
}
|
|
815
815
|
serviceController.callHooks(
|
|
816
816
|
"injectResponseHeaders",
|
|
@@ -824,16 +824,16 @@ export const startServer = async ({
|
|
|
824
824
|
responseProperties.headers = composeTwoHeaders(
|
|
825
825
|
responseProperties.headers,
|
|
826
826
|
returnValue,
|
|
827
|
-
)
|
|
827
|
+
);
|
|
828
828
|
}
|
|
829
829
|
},
|
|
830
|
-
)
|
|
830
|
+
);
|
|
831
831
|
serviceController.callHooks("responseReady", responseProperties, {
|
|
832
832
|
request,
|
|
833
833
|
warn,
|
|
834
|
-
})
|
|
835
|
-
return responseProperties
|
|
836
|
-
}
|
|
834
|
+
});
|
|
835
|
+
return responseProperties;
|
|
836
|
+
};
|
|
837
837
|
|
|
838
838
|
const sendResponse = async ({
|
|
839
839
|
signal,
|
|
@@ -846,10 +846,10 @@ export const startServer = async ({
|
|
|
846
846
|
// the parent response is immediatly ended. It means child responses (pushed streams)
|
|
847
847
|
// won't get a chance to be pushed.
|
|
848
848
|
// To let a chance to pushed streams we wait a little before sending the response
|
|
849
|
-
const ignoreBody = request.method === "HEAD"
|
|
850
|
-
const bodyIsEmpty = !responseProperties.body || ignoreBody
|
|
849
|
+
const ignoreBody = request.method === "HEAD";
|
|
850
|
+
const bodyIsEmpty = !responseProperties.body || ignoreBody;
|
|
851
851
|
if (bodyIsEmpty && requestNode.children.length > 0) {
|
|
852
|
-
await new Promise((resolve) => setTimeout(resolve))
|
|
852
|
+
await new Promise((resolve) => setTimeout(resolve));
|
|
853
853
|
}
|
|
854
854
|
|
|
855
855
|
await writeNodeResponse(responseStream, responseProperties, {
|
|
@@ -859,8 +859,8 @@ export const startServer = async ({
|
|
|
859
859
|
addRequestLog(requestNode, {
|
|
860
860
|
type: "info",
|
|
861
861
|
value: `response aborted`,
|
|
862
|
-
})
|
|
863
|
-
onRequestHandled(requestNode)
|
|
862
|
+
});
|
|
863
|
+
onRequestHandled(requestNode);
|
|
864
864
|
},
|
|
865
865
|
onError: (error) => {
|
|
866
866
|
addRequestLog(requestNode, {
|
|
@@ -871,11 +871,11 @@ export const startServer = async ({
|
|
|
871
871
|
"error stack": error.stack,
|
|
872
872
|
},
|
|
873
873
|
),
|
|
874
|
-
})
|
|
875
|
-
onRequestHandled(requestNode)
|
|
874
|
+
});
|
|
875
|
+
onRequestHandled(requestNode);
|
|
876
876
|
},
|
|
877
877
|
onHeadersSent: ({ status, statusText }) => {
|
|
878
|
-
const statusType = statusToType(status)
|
|
878
|
+
const statusType = statusToType(status);
|
|
879
879
|
addRequestLog(requestNode, {
|
|
880
880
|
type:
|
|
881
881
|
status === 404 && request.pathname === "/favicon.ico"
|
|
@@ -890,31 +890,31 @@ export const startServer = async ({
|
|
|
890
890
|
value: `${colorizeResponseStatus(status)} ${
|
|
891
891
|
responseProperties.statusMessage || statusText
|
|
892
892
|
}`,
|
|
893
|
-
})
|
|
893
|
+
});
|
|
894
894
|
},
|
|
895
895
|
onEnd: () => {
|
|
896
|
-
onRequestHandled(requestNode)
|
|
896
|
+
onRequestHandled(requestNode);
|
|
897
897
|
},
|
|
898
|
-
})
|
|
899
|
-
}
|
|
898
|
+
});
|
|
899
|
+
};
|
|
900
900
|
|
|
901
901
|
try {
|
|
902
902
|
if (receiveRequestOperation.signal.aborted) {
|
|
903
|
-
return
|
|
903
|
+
return;
|
|
904
904
|
}
|
|
905
905
|
const responseProperties = await handleRequest(request, {
|
|
906
906
|
requestNode: rootRequestNode,
|
|
907
|
-
})
|
|
908
|
-
nodeRequest.resume()
|
|
907
|
+
});
|
|
908
|
+
nodeRequest.resume();
|
|
909
909
|
if (receiveRequestOperation.signal.aborted) {
|
|
910
|
-
return
|
|
910
|
+
return;
|
|
911
911
|
}
|
|
912
912
|
|
|
913
913
|
// the node request readable stream is never closed because
|
|
914
914
|
// the response headers contains "connection: keep-alive"
|
|
915
915
|
// In this scenario we want to disable READABLE_STREAM_TIMEOUT warning
|
|
916
916
|
if (responseProperties.headers.connection === "keep-alive") {
|
|
917
|
-
clearTimeout(request.body.timeout)
|
|
917
|
+
clearTimeout(request.body.timeout);
|
|
918
918
|
}
|
|
919
919
|
|
|
920
920
|
await sendResponse({
|
|
@@ -923,75 +923,75 @@ export const startServer = async ({
|
|
|
923
923
|
requestNode: rootRequestNode,
|
|
924
924
|
responseStream: nodeResponse,
|
|
925
925
|
responseProperties,
|
|
926
|
-
})
|
|
926
|
+
});
|
|
927
927
|
} finally {
|
|
928
|
-
await sendResponseOperation.end()
|
|
928
|
+
await sendResponseOperation.end();
|
|
929
929
|
}
|
|
930
|
-
}
|
|
931
|
-
const removeRequestListener = listenRequest(nodeServer, requestCallback)
|
|
930
|
+
};
|
|
931
|
+
const removeRequestListener = listenRequest(nodeServer, requestCallback);
|
|
932
932
|
// ensure we don't try to handle new requests while server is stopping
|
|
933
|
-
stopCallbackList.add(removeRequestListener)
|
|
933
|
+
stopCallbackList.add(removeRequestListener);
|
|
934
934
|
}
|
|
935
935
|
|
|
936
936
|
websocket: {
|
|
937
937
|
// https://github.com/websockets/ws/blob/master/doc/ws.md#class-websocket
|
|
938
|
-
const websocketHandlers = []
|
|
938
|
+
const websocketHandlers = [];
|
|
939
939
|
serviceController.services.forEach((service) => {
|
|
940
|
-
const { handleWebsocket } = service
|
|
940
|
+
const { handleWebsocket } = service;
|
|
941
941
|
if (handleWebsocket) {
|
|
942
|
-
websocketHandlers.push(handleWebsocket)
|
|
942
|
+
websocketHandlers.push(handleWebsocket);
|
|
943
943
|
}
|
|
944
|
-
})
|
|
944
|
+
});
|
|
945
945
|
if (websocketHandlers.length > 0) {
|
|
946
|
-
const websocketClients = new Set()
|
|
947
|
-
const { WebSocketServer } = await import("ws")
|
|
948
|
-
let websocketServer = new WebSocketServer({ noServer: true })
|
|
946
|
+
const websocketClients = new Set();
|
|
947
|
+
const { WebSocketServer } = await import("ws");
|
|
948
|
+
let websocketServer = new WebSocketServer({ noServer: true });
|
|
949
949
|
const websocketOrigin = https
|
|
950
950
|
? `wss://${hostname}:${port}`
|
|
951
|
-
: `ws://${hostname}:${port}
|
|
952
|
-
server.websocketOrigin = websocketOrigin
|
|
951
|
+
: `ws://${hostname}:${port}`;
|
|
952
|
+
server.websocketOrigin = websocketOrigin;
|
|
953
953
|
const upgradeCallback = (nodeRequest, socket, head) => {
|
|
954
954
|
websocketServer.handleUpgrade(
|
|
955
955
|
nodeRequest,
|
|
956
956
|
socket,
|
|
957
957
|
head,
|
|
958
958
|
async (websocket) => {
|
|
959
|
-
websocketClients.add(websocket)
|
|
959
|
+
websocketClients.add(websocket);
|
|
960
960
|
websocket.once("close", () => {
|
|
961
|
-
websocketClients.delete(websocket)
|
|
962
|
-
})
|
|
961
|
+
websocketClients.delete(websocket);
|
|
962
|
+
});
|
|
963
963
|
const request = fromNodeRequest(nodeRequest, {
|
|
964
964
|
serverOrigin: websocketOrigin,
|
|
965
965
|
signal: new AbortController().signal,
|
|
966
966
|
requestBodyLifetime,
|
|
967
|
-
})
|
|
967
|
+
});
|
|
968
968
|
serviceController.callAsyncHooksUntil(
|
|
969
969
|
"handleWebsocket",
|
|
970
970
|
websocket,
|
|
971
971
|
{
|
|
972
972
|
request,
|
|
973
973
|
},
|
|
974
|
-
)
|
|
974
|
+
);
|
|
975
975
|
},
|
|
976
|
-
)
|
|
977
|
-
}
|
|
976
|
+
);
|
|
977
|
+
};
|
|
978
978
|
|
|
979
979
|
// see server-polyglot.js, upgrade must be listened on https server when used
|
|
980
|
-
const facadeServer = nodeServer._tlsServer || nodeServer
|
|
980
|
+
const facadeServer = nodeServer._tlsServer || nodeServer;
|
|
981
981
|
const removeUpgradeCallback = listenEvent(
|
|
982
982
|
facadeServer,
|
|
983
983
|
"upgrade",
|
|
984
984
|
upgradeCallback,
|
|
985
|
-
)
|
|
986
|
-
stopCallbackList.add(removeUpgradeCallback)
|
|
985
|
+
);
|
|
986
|
+
stopCallbackList.add(removeUpgradeCallback);
|
|
987
987
|
stopCallbackList.add(() => {
|
|
988
988
|
websocketClients.forEach((websocketClient) => {
|
|
989
|
-
websocketClient.close()
|
|
990
|
-
})
|
|
991
|
-
websocketClients.clear()
|
|
992
|
-
websocketServer.close()
|
|
993
|
-
websocketServer = null
|
|
994
|
-
})
|
|
989
|
+
websocketClient.close();
|
|
990
|
+
});
|
|
991
|
+
websocketClients.clear();
|
|
992
|
+
websocketServer.close();
|
|
993
|
+
websocketServer = null;
|
|
994
|
+
});
|
|
995
995
|
}
|
|
996
996
|
}
|
|
997
997
|
|
|
@@ -999,9 +999,9 @@ export const startServer = async ({
|
|
|
999
999
|
if (serverOrigins.network) {
|
|
1000
1000
|
logger.info(
|
|
1001
1001
|
`${serverName} started at ${serverOrigins.local} (${serverOrigins.network})`,
|
|
1002
|
-
)
|
|
1002
|
+
);
|
|
1003
1003
|
} else {
|
|
1004
|
-
logger.info(`${serverName} started at ${serverOrigins.local}`)
|
|
1004
|
+
logger.info(`${serverName} started at ${serverOrigins.local}`);
|
|
1005
1005
|
}
|
|
1006
1006
|
}
|
|
1007
1007
|
|
|
@@ -1015,14 +1015,14 @@ export const startServer = async ({
|
|
|
1015
1015
|
stop,
|
|
1016
1016
|
stoppedPromise,
|
|
1017
1017
|
addEffect: (callback) => {
|
|
1018
|
-
const cleanup = callback()
|
|
1018
|
+
const cleanup = callback();
|
|
1019
1019
|
if (typeof cleanup === "function") {
|
|
1020
|
-
stopCallbackList.add(cleanup)
|
|
1020
|
+
stopCallbackList.add(cleanup);
|
|
1021
1021
|
}
|
|
1022
1022
|
},
|
|
1023
|
-
})
|
|
1024
|
-
return server
|
|
1025
|
-
}
|
|
1023
|
+
});
|
|
1024
|
+
return server;
|
|
1025
|
+
};
|
|
1026
1026
|
|
|
1027
1027
|
const createNodeServer = async ({
|
|
1028
1028
|
https,
|
|
@@ -1032,47 +1032,47 @@ const createNodeServer = async ({
|
|
|
1032
1032
|
http1Allowed,
|
|
1033
1033
|
}) => {
|
|
1034
1034
|
if (https) {
|
|
1035
|
-
const { certificate, privateKey } = https
|
|
1035
|
+
const { certificate, privateKey } = https;
|
|
1036
1036
|
if (redirectHttpToHttps || allowHttpRequestOnHttps) {
|
|
1037
1037
|
return createPolyglotServer({
|
|
1038
1038
|
certificate,
|
|
1039
1039
|
privateKey,
|
|
1040
1040
|
http2,
|
|
1041
1041
|
http1Allowed,
|
|
1042
|
-
})
|
|
1042
|
+
});
|
|
1043
1043
|
}
|
|
1044
|
-
const { createServer } = await import("node:https")
|
|
1044
|
+
const { createServer } = await import("node:https");
|
|
1045
1045
|
return createServer({
|
|
1046
1046
|
cert: certificate,
|
|
1047
1047
|
key: privateKey,
|
|
1048
|
-
})
|
|
1048
|
+
});
|
|
1049
1049
|
}
|
|
1050
|
-
const { createServer } = await import("node:http")
|
|
1051
|
-
return createServer()
|
|
1052
|
-
}
|
|
1050
|
+
const { createServer } = await import("node:http");
|
|
1051
|
+
return createServer();
|
|
1052
|
+
};
|
|
1053
1053
|
|
|
1054
1054
|
const testCanPushStream = (http2Stream) => {
|
|
1055
1055
|
if (!http2Stream.pushAllowed) {
|
|
1056
1056
|
return {
|
|
1057
1057
|
can: false,
|
|
1058
1058
|
reason: `stream.pushAllowed is false`,
|
|
1059
|
-
}
|
|
1059
|
+
};
|
|
1060
1060
|
}
|
|
1061
1061
|
|
|
1062
1062
|
// See https://nodejs.org/dist/latest-v16.x/docs/api/http2.html#http2sessionstate
|
|
1063
1063
|
// And https://github.com/google/node-h2-auto-push/blob/67a36c04cbbd6da7b066a4e8d361c593d38853a4/src/index.ts#L100-L106
|
|
1064
|
-
const { remoteWindowSize } = http2Stream.session.state
|
|
1064
|
+
const { remoteWindowSize } = http2Stream.session.state;
|
|
1065
1065
|
if (remoteWindowSize === 0) {
|
|
1066
1066
|
return {
|
|
1067
1067
|
can: false,
|
|
1068
1068
|
reason: `no more remoteWindowSize`,
|
|
1069
|
-
}
|
|
1069
|
+
};
|
|
1070
1070
|
}
|
|
1071
1071
|
|
|
1072
1072
|
return {
|
|
1073
1073
|
can: true,
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1074
|
+
};
|
|
1075
|
+
};
|
|
1076
1076
|
|
|
1077
1077
|
const PROCESS_TEARDOWN_EVENTS_MAP = {
|
|
1078
1078
|
SIGHUP: STOP_REASON_PROCESS_SIGHUP,
|
|
@@ -1080,4 +1080,4 @@ const PROCESS_TEARDOWN_EVENTS_MAP = {
|
|
|
1080
1080
|
SIGINT: STOP_REASON_PROCESS_SIGINT,
|
|
1081
1081
|
beforeExit: STOP_REASON_PROCESS_BEFORE_EXIT,
|
|
1082
1082
|
exit: STOP_REASON_PROCESS_EXIT,
|
|
1083
|
-
}
|
|
1083
|
+
};
|