@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.
Files changed (44) hide show
  1. package/README.md +63 -23
  2. package/main.js +32 -17
  3. package/package.json +20 -15
  4. package/src/{internal → content_negotiation}/applyContentNegotiation.js +5 -1
  5. package/src/{internal → content_negotiation}/checkContentNegotiation.js +3 -3
  6. package/src/{negotiateContentEncoding.js → content_negotiation/negotiateContentEncoding.js} +5 -3
  7. package/src/{negotiateContentLanguage.js → content_negotiation/negotiateContentLanguage.js} +9 -5
  8. package/src/{negotiateContentType.js → content_negotiation/negotiateContentType.js} +10 -5
  9. package/src/content_negotiation/plugin_content_negotiation_check.js +15 -0
  10. package/src/cors/plugin_cors.js +156 -0
  11. package/src/fetchUrl.js +12 -7
  12. package/src/findFreePort.js +2 -1
  13. package/src/{composeResponse.js → internal/composeResponse.js} +5 -3
  14. package/src/internal/compositionMappingToCompose.js +26 -6
  15. package/src/{convertFileSystemErrorToResponseProperties.js → internal/convertFileSystemErrorToResponseProperties.js} +0 -0
  16. package/src/internal/getServerOrigins.js +5 -1
  17. package/src/internal/headersFromObject.js +3 -1
  18. package/src/{headersToObject.js → internal/headersToObject.js} +0 -0
  19. package/src/internal/listenClientError.js +5 -1
  20. package/src/internal/listenEvent.js +6 -1
  21. package/src/internal/listenRequest.js +5 -1
  22. package/src/internal/listenServerConnectionError.js +31 -23
  23. package/src/internal/multiple-header.js +11 -5
  24. package/src/internal/nodeRequestToRequest.js +10 -7
  25. package/src/internal/observable.js +5 -1
  26. package/src/internal/populateNodeResponse.js +11 -2
  27. package/src/internal/server-polyglot.js +11 -2
  28. package/src/internal/trackServerPendingConnections.js +15 -11
  29. package/src/internal/trackServerPendingRequests.js +10 -7
  30. package/src/jsenvServerInternalErrorToResponse.js +7 -3
  31. package/src/plugin_request_waiting_check.js +34 -0
  32. package/src/serveFile.js +88 -41
  33. package/src/server_timing/plugin_server_timing.js +28 -0
  34. package/src/{serverTiming.js → server_timing/timing_header.js} +43 -62
  35. package/src/server_timing/timing_measure.js +26 -0
  36. package/src/service_composition/service_composition.js +65 -0
  37. package/src/{createSSERoom.js → sse/createSSERoom.js} +22 -6
  38. package/src/startServer.js +197 -252
  39. package/src/stopReasons.js +3 -1
  40. package/src/urlToContentType.js +13 -6
  41. package/src/jsenvAccessControlAllowedHeaders.js +0 -1
  42. package/src/jsenvAccessControlAllowedMethods.js +0 -1
  43. package/src/service-composition.js +0 -41
  44. package/src/urlToSearchParamValue.js +0 -3
@@ -1,5 +1,3 @@
1
- /* eslint-disable import/max-dependencies */
2
-
3
1
  import http from "node:http"
4
2
  import {
5
3
  createCancellationToken,
@@ -9,21 +7,27 @@ import {
9
7
  isCancelError,
10
8
  executeAsyncFunction,
11
9
  } from "@jsenv/cancellation"
12
- import { SIGINTSignal, unadvisedCrashSignal, teardownSignal } from "@jsenv/node-signals"
13
- import { memoize, urlToOrigin } from "@jsenv/filesystem"
14
- import { createLogger } from "@jsenv/logger"
10
+ import {
11
+ SIGINTSignal,
12
+ unadvisedCrashSignal,
13
+ teardownSignal,
14
+ } from "@jsenv/node-signals"
15
+ import { memoize } from "@jsenv/filesystem"
16
+ import { createDetailedMessage, createLogger } from "@jsenv/logger"
15
17
 
16
18
  import { createTracker } from "./internal/createTracker.js"
17
19
  import { createPolyglotServer } from "./internal/server-polyglot.js"
18
20
  import { trackServerPendingConnections } from "./internal/trackServerPendingConnections.js"
19
21
  import { trackServerPendingRequests } from "./internal/trackServerPendingRequests.js"
20
22
  import { nodeRequestToRequest } from "./internal/nodeRequestToRequest.js"
21
- import { composeResponseHeaders } from "./internal/composeResponseHeaders.js"
22
23
  import { populateNodeResponse } from "./internal/populateNodeResponse.js"
23
24
  import { colorizeResponseStatus } from "./internal/colorizeResponseStatus.js"
24
25
  import { getServerOrigins } from "./internal/getServerOrigins.js"
25
26
  import { listen, stopListening } from "./internal/listen.js"
26
- import { composeResponse } from "./composeResponse.js"
27
+ import { composeResponse } from "./internal/composeResponse.js"
28
+ import { listenRequest } from "./internal/listenRequest.js"
29
+ import { listenServerConnectionError } from "./internal/listenServerConnectionError.js"
30
+ import { jsenvServerInternalErrorToResponse } from "./jsenvServerInternalErrorToResponse.js"
27
31
  import {
28
32
  STOP_REASON_INTERNAL_ERROR,
29
33
  STOP_REASON_PROCESS_SIGHUP,
@@ -33,13 +37,6 @@ import {
33
37
  STOP_REASON_PROCESS_EXIT,
34
38
  STOP_REASON_NOT_SPECIFIED,
35
39
  } from "./stopReasons.js"
36
- import { jsenvAccessControlAllowedHeaders } from "./jsenvAccessControlAllowedHeaders.js"
37
- import { jsenvAccessControlAllowedMethods } from "./jsenvAccessControlAllowedMethods.js"
38
- import { listenRequest } from "./internal/listenRequest.js"
39
- import { timeFunction, timingToServerTimingResponseHeaders } from "./serverTiming.js"
40
- import { jsenvServerInternalErrorToResponse } from "./jsenvServerInternalErrorToResponse.js"
41
- import { checkContentNegotiation } from "./internal/checkContentNegotiation.js"
42
- import { listenServerConnectionError } from "./internal/listenServerConnectionError.js"
43
40
 
44
41
  export const startServer = async ({
45
42
  cancellationToken = createCancellationToken(),
@@ -66,36 +63,11 @@ export const startServer = async ({
66
63
  stopOnCrash = false,
67
64
  keepProcessAlive = true,
68
65
  requestToResponse = () => null,
66
+ plugins = {},
69
67
 
70
- accessControlAllowedOrigins = [],
71
- accessControlAllowedMethods = jsenvAccessControlAllowedMethods,
72
- accessControlAllowedHeaders = jsenvAccessControlAllowedHeaders,
73
- accessControlAllowRequestOrigin = false,
74
- accessControlAllowRequestMethod = false,
75
- accessControlAllowRequestHeaders = false,
76
- accessControlAllowCredentials = false,
77
- // by default OPTIONS request can be cache for a long time, it's not going to change soon ?
78
- // we could put a lot here, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
79
- accessControlMaxAge = 600,
80
-
81
- // https://www.w3.org/TR/server-timing/
82
- sendServerTiming = false,
83
68
  sendServerInternalErrorDetails = false,
84
69
  serverInternalErrorToResponse = jsenvServerInternalErrorToResponse,
85
70
 
86
- requestWaitingMs = 20000,
87
- requestWaitingCallback = (request, { logger }) => {
88
- logger.warn(
89
- `still no response found for request after ${requestWaitingMs} ms
90
- --- request url ---
91
- ${request.origin}${request.ressource}
92
- --- request headers ---
93
- ${JSON.stringify(request.headers, null, " ")}
94
- `,
95
- )
96
- },
97
- contentNegotiationWarnings = true,
98
-
99
71
  startedCallback = () => {},
100
72
  stoppedCallback = () => {},
101
73
  errorIsCancellation = () => false,
@@ -118,7 +90,11 @@ ${JSON.stringify(request.headers, null, " ")}
118
90
  }
119
91
 
120
92
  const logger = createLogger({ logLevel })
121
- if (redirectHttpToHttps === undefined && protocol === "https" && !allowHttpRequestOnHttps) {
93
+ if (
94
+ redirectHttpToHttps === undefined &&
95
+ protocol === "https" &&
96
+ !allowHttpRequestOnHttps
97
+ ) {
122
98
  redirectHttpToHttps = true
123
99
  }
124
100
  if (redirectHttpToHttps && protocol === "http") {
@@ -126,7 +102,9 @@ ${JSON.stringify(request.headers, null, " ")}
126
102
  redirectHttpToHttps = false
127
103
  }
128
104
  if (allowHttpRequestOnHttps && redirectHttpToHttps) {
129
- logger.warn(`redirectHttpToHttps ignored because allowHttpRequestOnHttps is enabled`)
105
+ logger.warn(
106
+ `redirectHttpToHttps ignored because allowHttpRequestOnHttps is enabled`,
107
+ )
130
108
  redirectHttpToHttps = false
131
109
  }
132
110
 
@@ -208,7 +186,10 @@ ${JSON.stringify(request.headers, null, " ")}
208
186
  const stop = memoize(async (reason = STOP_REASON_NOT_SPECIFIED) => {
209
187
  status = "stopping"
210
188
 
211
- errorIsCancellation = composePredicate(errorIsCancellation, (error) => error === reason)
189
+ errorIsCancellation = composePredicate(
190
+ errorIsCancellation,
191
+ (error) => error === reason,
192
+ )
212
193
  errorIsCancellation = composePredicate(
213
194
  errorIsCancellation,
214
195
  (error) => error && error.code === "ECONNRESET",
@@ -241,7 +222,10 @@ ${JSON.stringify(request.headers, null, " ")}
241
222
  const serverOrigins = getServerOrigins({ protocol, ip, port })
242
223
  const serverOrigin = serverOrigins.main
243
224
 
244
- const removeConnectionErrorListener = listenServerConnectionError(nodeServer, onError)
225
+ const removeConnectionErrorListener = listenServerConnectionError(
226
+ nodeServer,
227
+ onError,
228
+ )
245
229
  registerCleanupCallback(removeConnectionErrorListener)
246
230
 
247
231
  const connectionsTracker = trackServerPendingConnections(nodeServer, {
@@ -250,7 +234,9 @@ ${JSON.stringify(request.headers, null, " ")}
250
234
  // opened connection must be shutdown before the close event is emitted
251
235
  registerCleanupCallback(connectionsTracker.stop)
252
236
 
253
- const pendingRequestsTracker = trackServerPendingRequests(nodeServer, { http2 })
237
+ const pendingRequestsTracker = trackServerPendingRequests(nodeServer, {
238
+ http2,
239
+ })
254
240
  // ensure pending requests got a response from the server
255
241
  registerCleanupCallback((reason) => {
256
242
  pendingRequestsTracker.stop({
@@ -271,74 +257,173 @@ ${JSON.stringify(request.headers, null, " ")}
271
257
  return
272
258
  }
273
259
 
274
- const request = nodeRequestToRequest(nodeRequest, { serverCancellationToken, serverOrigin })
275
- nodeRequest.on("error", (error) => {
276
- logger[error.message === "aborted" ? "debug" : "error"](`error on request.
277
- --- request ressource ---
278
- ${request.ressource}
279
- --- error stack ---
280
- ${error.stack}`)
260
+ const request = nodeRequestToRequest(nodeRequest, {
261
+ serverCancellationToken,
262
+ serverOrigin,
281
263
  })
282
264
 
283
- const [startRespondingTiming, { response, error }] = await timeFunction(
284
- "time to start responding",
285
- () => generateResponseDescription(request),
286
- )
287
- if (sendServerTiming) {
288
- const serverTiming = {
289
- ...response.timing,
290
- ...startRespondingTiming,
291
- }
292
- response.headers = composeResponseHeaders(
293
- timingToServerTimingResponseHeaders(serverTiming),
294
- response.headers,
295
- )
296
-
297
- if (contentNegotiationWarnings) {
298
- checkContentNegotiation(request, response, { warn: logger.warn })
299
- }
265
+ // Handling request is asynchronous, we buffer logs for that request
266
+ // until we know what happens with that request
267
+ // It delays logs until we know of the request will be handled
268
+ // but it's mandatory to make logs readable.
269
+ let requestLogs = []
270
+ const releaseRequestLogs = () => {
271
+ requestLogs.forEach(({ type, value }) => {
272
+ logger[type](value)
273
+ })
274
+ requestLogs.length = 0
300
275
  }
276
+ const warn = (value) => {
277
+ requestLogs.push({
278
+ type: "warn",
279
+ value,
280
+ })
281
+ }
282
+ requestLogs.push({
283
+ type: "info",
284
+ value: `${request.method} ${request.origin}${request.ressource}`,
285
+ })
301
286
 
302
- logger.info(`${request.method} ${request.origin}${request.ressource}`)
287
+ nodeRequest.on("error", (error) => {
288
+ if (error.message === "aborted") {
289
+ requestLogs.push({
290
+ type: "debug",
291
+ value: createDetailedMessage(`request aborted by client`, {
292
+ "error message": error.message,
293
+ }),
294
+ })
295
+ } else {
296
+ requestLogs.push({
297
+ type: "error",
298
+ value: createDetailedMessage(`"error" event emitted on request`, {
299
+ "error stack": error.stack,
300
+ }),
301
+ })
302
+ }
303
+ })
303
304
 
304
- if (error && isCancelError(error) && internalCancellationToken.cancellationRequested) {
305
- logger.info("ignored because server closing")
306
- nodeResponse.destroy()
307
- return
305
+ const requestPlugins = []
306
+ let responseFromShortcircuit
307
+ const shortcircuitResponse = (value) => {
308
+ responseFromShortcircuit = value
308
309
  }
310
+ Object.keys(plugins).forEach((pluginName) => {
311
+ const { onRequest } = plugins[pluginName]
312
+ if (onRequest) {
313
+ const returnValue = onRequest(request, { warn, shortcircuitResponse })
314
+ if (returnValue) {
315
+ requestPlugins.push(returnValue)
316
+ }
317
+ }
318
+ })
319
+ let { error, responseProperties } = responseFromShortcircuit
320
+ ? { responseProperties: responseFromShortcircuit }
321
+ : await generateResponseDescription({
322
+ requestToResponse,
323
+ request,
324
+ })
309
325
 
310
- if (error && isCancelError(error) && request.cancellationToken.cancellationRequested) {
311
- logger.info("ignored because request canceled")
312
- nodeResponse.destroy()
313
- return
326
+ if (error && isCancelError(error)) {
327
+ if (internalCancellationToken.cancellationRequested) {
328
+ requestLogs.push({
329
+ type: "info",
330
+ value: `aborted because server closing`,
331
+ })
332
+ releaseRequestLogs()
333
+ nodeResponse.destroy()
334
+ return
335
+ }
336
+ if (request.cancellationToken.cancellationRequested) {
337
+ requestLogs.push({
338
+ type: "info",
339
+ value: `aborted because request aborted by client`,
340
+ })
341
+ releaseRequestLogs()
342
+ nodeResponse.destroy()
343
+ return
344
+ }
345
+ // unknown cancel error, never supposed to happen
314
346
  }
315
347
 
316
- if (request.aborted) {
317
- logger.info(`request aborted by client`)
318
- nodeResponse.destroy()
319
- return
348
+ // internal error, create 500 response
349
+ if (error) {
350
+ const propertiesFromError = await serverInternalErrorToResponse(error, {
351
+ request,
352
+ sendServerInternalErrorDetails,
353
+ })
354
+ responseProperties = composeResponse(
355
+ {
356
+ status: 500,
357
+ statusText: statusToStatusText(500),
358
+ headers: {
359
+ // ensure error are not cached
360
+ "cache-control": "no-store",
361
+ "content-type": "text/plain",
362
+ },
363
+ body: "",
364
+ },
365
+ propertiesFromError,
366
+ )
367
+ requestLogs.push({
368
+ type: "error",
369
+ value: createDetailedMessage(
370
+ `internal error while handling request`,
371
+ {
372
+ "error stack": error.stack,
373
+ },
374
+ ),
375
+ })
376
+ } else {
377
+ const {
378
+ status = 501,
379
+ statusText = statusToStatusText(status),
380
+ headers = {},
381
+ body = "",
382
+ ...rest
383
+ } = responseProperties || {}
384
+ responseProperties = {
385
+ status,
386
+ statusText,
387
+ headers,
388
+ body,
389
+ ...rest,
390
+ }
320
391
  }
321
392
 
393
+ // call every plugin "onResponse" hook
394
+ requestPlugins.forEach((requestPlugin) => {
395
+ const { onResponse } = requestPlugin
396
+ if (onResponse) {
397
+ const returnValue = onResponse(responseProperties)
398
+ if (returnValue) {
399
+ responseProperties = composeResponse(
400
+ responseProperties,
401
+ returnValue,
402
+ )
403
+ }
404
+ }
405
+ })
406
+
322
407
  if (
323
408
  request.method !== "HEAD" &&
324
- response.headers["content-length"] > 0 &&
325
- response.body === ""
409
+ responseProperties.headers["content-length"] > 0 &&
410
+ responseProperties.body === ""
326
411
  ) {
327
- logger.warn(
328
- `content-length header is ${response.headers["content-length"]} but body is empty`,
329
- )
412
+ requestLogs.push({
413
+ type: "warn",
414
+ value: `content-length header is ${responseProperties.headers["content-length"]} but body is empty`,
415
+ })
330
416
  }
331
417
 
332
- if (error) {
333
- logger.error(`internal error while handling request.
334
- --- error stack ---
335
- ${error.stack}
336
- --- request ---
337
- ${request.method} ${request.origin}${request.ressource}`)
338
- }
339
- logger.info(`${colorizeResponseStatus(response.status)} ${response.statusText}`)
418
+ requestLogs.push({
419
+ type: "info",
420
+ value: `${colorizeResponseStatus(responseProperties.status)} ${
421
+ responseProperties.statusText
422
+ }`,
423
+ })
424
+ releaseRequestLogs()
340
425
 
341
- populateNodeResponse(nodeResponse, response, {
426
+ populateNodeResponse(nodeResponse, responseProperties, {
342
427
  cancellationToken: request.cancellationToken,
343
428
  ignoreBody: request.method === "HEAD",
344
429
  // https://github.com/nodejs/node/blob/79296dc2d02c0b9872bbfcbb89148ea036a546d0/lib/internal/http2/compat.js#L97
@@ -348,12 +433,12 @@ ${request.method} ${request.origin}${request.ressource}`)
348
433
  })
349
434
 
350
435
  if (
351
- stopOnInternalError &&
436
+ error &&
352
437
  // stopOnInternalError stops server only if requestToResponse generated
353
438
  // a non controlled error (internal error).
354
439
  // if requestToResponse gracefully produced a 500 response (it did not throw)
355
440
  // then we can assume we are still in control of what we are doing
356
- error
441
+ stopOnInternalError
357
442
  ) {
358
443
  // il faudrais pouvoir stop que les autres response ?
359
444
  stop(STOP_REASON_INTERNAL_ERROR)
@@ -364,100 +449,11 @@ ${request.method} ${request.origin}${request.ressource}`)
364
449
  // ensure we don't try to handle new requests while server is stopping
365
450
  registerCleanupCallback(removeRequestListener)
366
451
 
367
- logger.info(`${serverName} started at ${serverOrigin} (${serverOrigins.external})`)
452
+ logger.info(
453
+ `${serverName} started at ${serverOrigin} (${serverOrigins.external})`,
454
+ )
368
455
  startedCallback({ origin: serverOrigin })
369
456
 
370
- const corsEnabled = accessControlAllowRequestOrigin || accessControlAllowedOrigins.length
371
- // here we check access control options to throw or warn if we find strange values
372
-
373
- const generateResponseDescription = async (request) => {
374
- const responsePropertiesToResponse = ({
375
- status = 501,
376
- statusText = statusToStatusText(status),
377
- headers = {},
378
- body = "",
379
- bodyEncoding,
380
- timing,
381
- }) => {
382
- if (corsEnabled) {
383
- const accessControlHeaders = generateAccessControlHeaders({
384
- request,
385
- accessControlAllowedOrigins,
386
- accessControlAllowRequestOrigin,
387
- accessControlAllowedMethods,
388
- accessControlAllowRequestMethod,
389
- accessControlAllowedHeaders,
390
- accessControlAllowRequestHeaders,
391
- accessControlAllowCredentials,
392
- accessControlMaxAge,
393
- sendServerTiming,
394
- })
395
-
396
- return {
397
- status,
398
- statusText,
399
- headers: composeResponseHeaders(headers, accessControlHeaders),
400
- body,
401
- bodyEncoding,
402
- timing,
403
- }
404
- }
405
-
406
- return {
407
- status,
408
- statusText,
409
- headers,
410
- body,
411
- bodyEncoding,
412
- timing,
413
- }
414
- }
415
-
416
- let timeout
417
- try {
418
- if (corsEnabled && request.method === "OPTIONS") {
419
- return {
420
- response: responsePropertiesToResponse({
421
- status: 200,
422
- headers: {
423
- "content-length": 0,
424
- },
425
- }),
426
- }
427
- }
428
-
429
- timeout = setTimeout(
430
- () => requestWaitingCallback(request, { logger, requestWaitingMs }),
431
- requestWaitingMs,
432
- )
433
-
434
- const responseProperties = await requestToResponse(request)
435
- clearTimeout(timeout)
436
- return {
437
- response: responsePropertiesToResponse(responseProperties || {}),
438
- }
439
- } catch (error) {
440
- clearTimeout(timeout)
441
- return {
442
- response: composeResponse(
443
- responsePropertiesToResponse({
444
- status: 500,
445
- headers: {
446
- // ensure error are not cached
447
- "cache-control": "no-store",
448
- "content-type": "text/plain",
449
- },
450
- }),
451
- await serverInternalErrorToResponse(error, {
452
- request,
453
- sendServerInternalErrorDetails,
454
- }),
455
- ),
456
- error,
457
- }
458
- }
459
- }
460
-
461
457
  return {
462
458
  getStatus: () => status,
463
459
  origin: serverOrigin,
@@ -497,73 +493,22 @@ const createNodeServer = async ({
497
493
  })
498
494
  }
499
495
 
500
- const statusToStatusText = (status) => http.STATUS_CODES[status] || "not specified"
501
-
502
- // https://www.w3.org/TR/cors/
503
- // https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
504
- const generateAccessControlHeaders = ({
505
- request: { headers },
506
- accessControlAllowedOrigins,
507
- accessControlAllowRequestOrigin,
508
- accessControlAllowedMethods,
509
- accessControlAllowRequestMethod,
510
- accessControlAllowedHeaders,
511
- accessControlAllowRequestHeaders,
512
- accessControlAllowCredentials,
513
- // by default OPTIONS request can be cache for a long time, it's not going to change soon ?
514
- // we could put a lot here, see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
515
- accessControlMaxAge = 600,
516
- sendServerTiming,
517
- } = {}) => {
518
- const vary = []
519
-
520
- const allowedOriginArray = [...accessControlAllowedOrigins]
521
- if (accessControlAllowRequestOrigin) {
522
- if ("origin" in headers && headers.origin !== "null") {
523
- allowedOriginArray.push(headers.origin)
524
- vary.push("origin")
525
- } else if ("referer" in headers) {
526
- allowedOriginArray.push(urlToOrigin(headers.referer))
527
- vary.push("referer")
528
- } else {
529
- allowedOriginArray.push("*")
496
+ const generateResponseDescription = async ({ requestToResponse, request }) => {
497
+ try {
498
+ const returnValue = await requestToResponse(request)
499
+ return {
500
+ responseProperties: returnValue,
530
501
  }
531
- }
532
-
533
- const allowedMethodArray = [...accessControlAllowedMethods]
534
- if (accessControlAllowRequestMethod && "access-control-request-method" in headers) {
535
- const requestMethodName = headers["access-control-request-method"]
536
- if (!allowedMethodArray.includes(requestMethodName)) {
537
- allowedMethodArray.push(requestMethodName)
538
- vary.push("access-control-request-method")
502
+ } catch (error) {
503
+ return {
504
+ error,
539
505
  }
540
506
  }
541
-
542
- const allowedHeaderArray = [...accessControlAllowedHeaders]
543
- if (accessControlAllowRequestHeaders && "access-control-request-headers" in headers) {
544
- const requestHeaderNameArray = headers["access-control-request-headers"].split(", ")
545
- requestHeaderNameArray.forEach((headerName) => {
546
- const headerNameLowerCase = headerName.toLowerCase()
547
- if (!allowedHeaderArray.includes(headerNameLowerCase)) {
548
- allowedHeaderArray.push(headerNameLowerCase)
549
- if (!vary.includes("access-control-request-headers")) {
550
- vary.push("access-control-request-headers")
551
- }
552
- }
553
- })
554
- }
555
-
556
- return {
557
- "access-control-allow-origin": allowedOriginArray.join(", "),
558
- "access-control-allow-methods": allowedMethodArray.join(", "),
559
- "access-control-allow-headers": allowedHeaderArray.join(", "),
560
- ...(accessControlAllowCredentials ? { "access-control-allow-credentials": true } : {}),
561
- "access-control-max-age": accessControlMaxAge,
562
- ...(sendServerTiming ? { "timing-allow-origin": allowedOriginArray.join(", ") } : {}),
563
- ...(vary.length ? { vary: vary.join(", ") } : {}),
564
- }
565
507
  }
566
508
 
509
+ const statusToStatusText = (status) =>
510
+ http.STATUS_CODES[status] || "not specified"
511
+
567
512
  const composePredicate = (previousPredicate, predicate) => {
568
513
  return (value) => {
569
514
  return previousPredicate(value) || predicate(value)
@@ -8,6 +8,8 @@ export const STOP_REASON_INTERNAL_ERROR = createReason("Internal error")
8
8
  export const STOP_REASON_PROCESS_SIGHUP = createReason("process SIGHUP")
9
9
  export const STOP_REASON_PROCESS_SIGTERM = createReason("process SIGTERM")
10
10
  export const STOP_REASON_PROCESS_SIGINT = createReason("process SIGINT")
11
- export const STOP_REASON_PROCESS_BEFORE_EXIT = createReason("process before exit")
11
+ export const STOP_REASON_PROCESS_BEFORE_EXIT = createReason(
12
+ "process before exit",
13
+ )
12
14
  export const STOP_REASON_PROCESS_EXIT = createReason("process exit")
13
15
  export const STOP_REASON_NOT_SPECIFIED = createReason("not specified")
@@ -1,6 +1,7 @@
1
1
  // https://github.com/jshttp/mime-db/blob/master/src/apache-types.json
2
2
 
3
- import { extname } from "path"
3
+ import { extname } from "node:path"
4
+
4
5
  import { jsenvContentTypeMap } from "./jsenvContentTypeMap.js"
5
6
 
6
7
  export const urlToContentType = (
@@ -9,7 +10,9 @@ export const urlToContentType = (
9
10
  contentTypeDefault = "application/octet-stream",
10
11
  ) => {
11
12
  if (typeof contentTypeMap !== "object") {
12
- throw new TypeError(`contentTypeMap must be an object, got ${contentTypeMap}`)
13
+ throw new TypeError(
14
+ `contentTypeMap must be an object, got ${contentTypeMap}`,
15
+ )
13
16
  }
14
17
  const pathname = new URL(url).pathname
15
18
 
@@ -21,9 +24,13 @@ export const urlToContentType = (
21
24
 
22
25
  const extension = extensionWithDot.slice(1)
23
26
  const availableContentTypes = Object.keys(contentTypeMap)
24
- const contentTypeForExtension = availableContentTypes.find((contentTypeName) => {
25
- const contentType = contentTypeMap[contentTypeName]
26
- return contentType.extensions && contentType.extensions.indexOf(extension) > -1
27
- })
27
+ const contentTypeForExtension = availableContentTypes.find(
28
+ (contentTypeName) => {
29
+ const contentType = contentTypeMap[contentTypeName]
30
+ return (
31
+ contentType.extensions && contentType.extensions.indexOf(extension) > -1
32
+ )
33
+ },
34
+ )
28
35
  return contentTypeForExtension || contentTypeDefault
29
36
  }
@@ -1 +0,0 @@
1
- export const jsenvAccessControlAllowedHeaders = ["x-requested-with"]
@@ -1 +0,0 @@
1
- export const jsenvAccessControlAllowedMethods = ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
@@ -1,41 +0,0 @@
1
- import { firstOperationMatching } from "@jsenv/cancellation"
2
- import { timeFunction } from "./serverTiming.js"
3
- import { composeResponse } from "./composeResponse.js"
4
-
5
- export const composeServices = (...callbacks) => {
6
- return (request) => {
7
- return firstOperationMatching({
8
- array: callbacks,
9
- start: (callback) => callback(request),
10
- predicate: serviceGeneratedResponsePredicate,
11
- })
12
- }
13
- }
14
-
15
- export const composeServicesWithTiming = (namedServices) => {
16
- return async (request) => {
17
- const servicesTiming = {}
18
- const response = await firstOperationMatching({
19
- array: Object.keys(namedServices).map((serviceName) => {
20
- return { serviceName, serviceFn: namedServices[serviceName] }
21
- }),
22
- start: async ({ serviceName, serviceFn }) => {
23
- const [serviceTiming, value] = await timeFunction(serviceName, () => serviceFn(request))
24
- Object.assign(servicesTiming, serviceTiming)
25
- return value
26
- },
27
- predicate: serviceGeneratedResponsePredicate,
28
- })
29
- if (response) {
30
- return composeResponse({ timing: servicesTiming }, response)
31
- }
32
- return null
33
- }
34
- }
35
-
36
- const serviceGeneratedResponsePredicate = (value) => {
37
- if (value === null) {
38
- return false
39
- }
40
- return typeof value === "object"
41
- }
@@ -1,3 +0,0 @@
1
- export const urlToSearchParamValue = (url, searchParamName) => {
2
- return new URL(url).searchParams.get(searchParamName)
3
- }