@jsenv/server 10.2.1 → 12.2.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 CHANGED
@@ -70,7 +70,10 @@ const notFoundService = (request) => {
70
70
  }
71
71
 
72
72
  const server = await startServer({
73
- requestToResponse: composeServices(indexService, notFoundService),
73
+ requestToResponse: composeServices({
74
+ index: indexService,
75
+ otherwise: notFoundService,
76
+ }),
74
77
  })
75
78
 
76
79
  const fetch = await import("node-fetch")
@@ -89,8 +92,8 @@ import { startServer } from "@jsenv/server"
89
92
 
90
93
  await startServer({
91
94
  protocol: "https",
92
- serverCertificate: readFileSyncAsString("./server.crt"),
93
- serverCertificatePrivateKey: readFileSyncAsString("./server.key"),
95
+ certificate: readFileSyncAsString("./server.crt"),
96
+ privateKey: readFileSyncAsString("./server.key"),
94
97
  allowHttpRequestOnHttps: true,
95
98
  requestToResponse: (request) => {
96
99
  const clientUsesHttp = request.origin.startsWith("http:")
package/main.js CHANGED
@@ -5,11 +5,8 @@
5
5
  */
6
6
 
7
7
  export { startServer } from "./src/startServer.js"
8
- export {
9
- composeServices,
10
- composeServicesWithTiming,
11
- redirectRequest,
12
- } from "./src/service_composition/service_composition.js"
8
+ export { composeServices } from "./src/service_composition/service_composition.js"
9
+ export { setupRoutes } from "./src/service_composition/routing.js"
13
10
  export { jsenvServerInternalErrorToResponse } from "./src/jsenvServerInternalErrorToResponse.js"
14
11
  export { readRequestBody } from "./src/readRequestBody.js"
15
12
  export { fetchFileSystem } from "./src/fetch_filesystem.js"
@@ -46,6 +43,7 @@ export { pluginContentNegotiationCheck } from "./src/content_negotiation/plugin_
46
43
  // others
47
44
  export { fetchUrl } from "./src/fetch_url.js"
48
45
  export { composeTwoResponses } from "./src/internal/response_composition.js"
46
+ export { pluginRessourceAliases } from "./src/ressource_aliases/plugin_ressource_aliases.js"
49
47
  export { pluginRequestWaitingCheck } from "./src/plugin_request_waiting_check.js"
50
48
  export { findFreePort } from "./src/internal/listen.js"
51
49
  export { urlToContentType } from "./src/urlToContentType.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/server",
3
- "version": "10.2.1",
3
+ "version": "12.2.0",
4
4
  "description": "Write your Node.js server using pure functions",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -13,7 +13,7 @@
13
13
  "url": "https://github.com/jsenv/server"
14
14
  },
15
15
  "engines": {
16
- "node": ">=14.17.0"
16
+ "node": ">=16.12.0"
17
17
  },
18
18
  "publishConfig": {
19
19
  "access": "public"
@@ -25,20 +25,20 @@
25
25
  },
26
26
  "./*": "./*"
27
27
  },
28
+ "main": "./main.js",
28
29
  "files": [
29
30
  "/src/",
30
31
  "/main.js"
31
32
  ],
32
33
  "scripts": {
33
- "eslint-check": "node ./node_modules/eslint/bin/eslint.js . --ext=.js,.mjs",
34
34
  "importmap-generate": "node ./script/importmap/generate_importmap.mjs",
35
35
  "measure-performances": "node --expose-gc ./script/performance/generate_performance_report.mjs --log",
36
- "test": "node --unhandled-rejections=strict ./script/test/test.mjs",
36
+ "test": "node ./script/test/test.mjs",
37
37
  "test-with-coverage": "npm run test -- --coverage",
38
38
  "generate-html-snapshots": "node ./test/internal_error/generate_html_snapshot_files.mjs",
39
- "prettier-format": "node ./script/prettier/prettier_format.mjs",
40
- "prettier-format-stage": "npm run prettier-format -- --staged",
41
- "prettier-check": "npm run prettier-format -- --dry-run",
39
+ "eslint-check": "node ./node_modules/eslint/bin/eslint.js . --ext=.js,.mjs",
40
+ "prettier-check": "prettier --check .",
41
+ "prettier-write": "prettier --write .",
42
42
  "certificate-authority-install": "node ./script/certificate/certificate_authority_install.mjs",
43
43
  "playwright-install": "npx playwright install-deps && npx playwright install",
44
44
  "prepublishOnly": "node ./script/publish/remove_postinstall.mjs",
@@ -48,28 +48,25 @@
48
48
  "@jsenv/abort": "4.1.1",
49
49
  "@jsenv/logger": "4.0.1",
50
50
  "@jsenv/url-meta": "6.0.1",
51
- "node-fetch": "2.6.2"
51
+ "node-fetch": "2.6.2",
52
+ "path-to-regexp": "6.2.0"
52
53
  },
53
54
  "devDependencies": {
54
- "@babel/core": "7.16.0",
55
- "@babel/eslint-parser": "7.16.0",
56
55
  "@jsenv/assert": "2.3.2",
57
- "@jsenv/codecov-upload": "3.5.0",
58
- "@jsenv/core": "23.7.1",
59
- "@jsenv/eslint-config": "16.0.8",
56
+ "@jsenv/core": "23.10.0",
57
+ "@jsenv/eslint-config": "16.0.9",
60
58
  "@jsenv/filesystem": "2.5.1",
61
59
  "@jsenv/github-release-package": "1.2.3",
62
- "@jsenv/https-local": "1.0.2",
60
+ "@jsenv/https-local": "1.0.3",
63
61
  "@jsenv/importmap-eslint-resolver": "5.1.2",
64
- "@jsenv/importmap-node-module": "2.4.1",
62
+ "@jsenv/importmap-node-module": "2.7.1",
65
63
  "@jsenv/package-publish": "1.6.2",
66
64
  "@jsenv/performance-impact": "2.2.1",
67
- "@jsenv/prettier-check-project": "5.6.1",
68
- "eslint": "7.32.0",
69
- "eslint-plugin-import": "2.25.2",
65
+ "eslint": "8.2.0",
66
+ "eslint-plugin-import": "2.25.3",
70
67
  "eventsource": "1.1.0",
71
- "fastify": "3.22.1",
72
- "fastify-static": "4.4.2",
68
+ "fastify": "3.24.0",
69
+ "fastify-static": "4.5.0",
73
70
  "playwright": "1.16.3",
74
71
  "prettier": "2.4.1"
75
72
  }
@@ -1,125 +1,93 @@
1
- import { urlToMeta, normalizeStructuredMetaMap } from "@jsenv/url-meta"
2
-
3
1
  import { nodeStreamToObservable } from "./nodeStreamToObservable.js"
4
2
  import { headersFromObject } from "./headersFromObject.js"
5
3
 
6
- export const createRequestFactory = ({ serverOrigin, ressourceAliases }) => {
7
- const ressourceAliasMap = {}
8
- Object.keys(ressourceAliases).forEach((key) => {
9
- ressourceAliasMap[ressourceAliases[key]] = key
10
- })
11
- const structuredMetaMap = normalizeStructuredMetaMap(
12
- { alias: ressourceAliasMap },
13
- "file:///",
14
- )
15
-
16
- const getRessourceAlias = (pathname) => {
17
- const { alias } = urlToMeta({
18
- url: `file://${pathname}`,
19
- structuredMetaMap,
20
- })
21
- return alias
4
+ export const fromNodeRequest = (nodeRequest, { serverOrigin, signal }) => {
5
+ const headers = headersFromObject(nodeRequest.headers)
6
+ const body = nodeStreamToObservable(nodeRequest)
7
+
8
+ let requestOrigin
9
+ if (nodeRequest.authority) {
10
+ requestOrigin = nodeRequest.connection.encrypted
11
+ ? `https://${nodeRequest.authority}`
12
+ : `http://${nodeRequest.authority}`
13
+ } else if (nodeRequest.headers.host) {
14
+ requestOrigin = nodeRequest.connection.encrypted
15
+ ? `https://${nodeRequest.headers.host}`
16
+ : `http://${nodeRequest.headers.host}`
17
+ } else {
18
+ requestOrigin = serverOrigin
22
19
  }
23
20
 
24
- const getPropertiesFromRessource = ({ ressource, baseUrl }) => {
25
- const urlObject = new URL(ressource, baseUrl)
26
- let pathname = urlObject.pathname
27
- const ressourceAlias = getRessourceAlias(pathname)
28
-
29
- if (!ressourceAlias) {
30
- return {
31
- url: String(urlObject),
32
- pathname,
33
- ressource,
34
- }
35
- }
21
+ return Object.freeze({
22
+ signal,
23
+ http2: Boolean(nodeRequest.stream),
24
+ origin: requestOrigin,
25
+ ...getPropertiesFromRessource({
26
+ ressource: nodeRequest.url,
27
+ baseUrl: requestOrigin,
28
+ }),
29
+ method: nodeRequest.method,
30
+ headers,
31
+ body,
32
+ })
33
+ }
36
34
 
37
- const urlObjectAfterAlias = replaceUrlRessource(urlObject, ressourceAlias)
38
- return {
39
- url: String(urlObjectAfterAlias),
40
- pathname: urlObjectAfterAlias.pathname,
41
- ressource: `${urlObjectAfterAlias.pathname}${urlObjectAfterAlias.search}`,
42
- ressourceBeforeAlias: ressource,
43
- }
35
+ export const applyRedirectionToRequest = (
36
+ request,
37
+ { ressource, pathname, ...rest },
38
+ ) => {
39
+ return {
40
+ ...request,
41
+ ...(ressource
42
+ ? getPropertiesFromRessource({
43
+ ressource,
44
+ baseUrl: request.url,
45
+ })
46
+ : pathname
47
+ ? getPropertiesFromPathname({
48
+ pathname,
49
+ baseUrl: request.url,
50
+ })
51
+ : {}),
52
+ ...rest,
44
53
  }
54
+ }
45
55
 
46
- const getPropertiesFromPathname = ({ pathname, baseUrl }) => {
47
- return getPropertiesFromRessource({
48
- ressource: `${pathname}${new URL(baseUrl).search}`,
49
- baseUrl,
50
- })
51
- }
56
+ const getPropertiesFromRessource = ({ ressource, baseUrl }) => {
57
+ const urlObject = new URL(ressource, baseUrl)
58
+ let pathname = urlObject.pathname
52
59
 
53
60
  return {
54
- fromNodeRequest: (nodeRequest, { signal }) => {
55
- const headers = headersFromObject(nodeRequest.headers)
56
- const body = nodeStreamToObservable(nodeRequest)
57
-
58
- let requestOrigin
59
- if (nodeRequest.authority) {
60
- requestOrigin = nodeRequest.connection.encrypted
61
- ? `https://${nodeRequest.authority}`
62
- : `http://${nodeRequest.authority}`
63
- } else if (nodeRequest.headers.host) {
64
- requestOrigin = nodeRequest.connection.encrypted
65
- ? `https://${nodeRequest.headers.host}`
66
- : `http://${nodeRequest.headers.host}`
67
- } else {
68
- requestOrigin = serverOrigin
69
- }
70
-
71
- return Object.freeze({
72
- signal,
73
- http2: Boolean(nodeRequest.stream),
74
- origin: requestOrigin,
75
- ...getPropertiesFromRessource({
76
- ressource: nodeRequest.url,
77
- baseUrl: requestOrigin,
78
- }),
79
- method: nodeRequest.method,
80
- headers,
81
- body,
82
- })
83
- },
84
-
85
- createPushRequest: (request, { signal, pathname, method }) => {
86
- const pushRequest = Object.freeze({
87
- ...request,
88
- parent: request,
89
- signal,
90
- http2: true,
91
- ...(pathname
92
- ? getPropertiesFromPathname({
93
- pathname,
94
- baseUrl: request.url,
95
- })
96
- : {}),
97
- method: method || request.method,
98
- headers: getHeadersInheritedByPushRequest(request),
99
- body: undefined,
100
- })
61
+ url: String(urlObject),
62
+ pathname,
63
+ ressource,
64
+ }
65
+ }
101
66
 
102
- return pushRequest
103
- },
67
+ const getPropertiesFromPathname = ({ pathname, baseUrl }) => {
68
+ return getPropertiesFromRessource({
69
+ ressource: `${pathname}${new URL(baseUrl).search}`,
70
+ baseUrl,
71
+ })
72
+ }
104
73
 
105
- applyRedirectionToRequest: (request, { ressource, pathname, ...rest }) => {
106
- return {
107
- ...request,
108
- ...(ressource
109
- ? getPropertiesFromRessource({
110
- ressource,
111
- baseUrl: request.url,
112
- })
113
- : pathname
114
- ? getPropertiesFromPathname({
115
- pathname,
116
- baseUrl: request.url,
117
- })
118
- : {}),
119
- ...rest,
120
- }
121
- },
122
- }
74
+ export const createPushRequest = (request, { signal, pathname, method }) => {
75
+ const pushRequest = Object.freeze({
76
+ ...request,
77
+ parent: request,
78
+ signal,
79
+ http2: true,
80
+ ...(pathname
81
+ ? getPropertiesFromPathname({
82
+ pathname,
83
+ baseUrl: request.url,
84
+ })
85
+ : {}),
86
+ method: method || request.method,
87
+ headers: getHeadersInheritedByPushRequest(request),
88
+ body: undefined,
89
+ })
90
+ return pushRequest
123
91
  }
124
92
 
125
93
  const getHeadersInheritedByPushRequest = (request) => {
@@ -135,17 +103,3 @@ const getHeadersInheritedByPushRequest = (request) => {
135
103
 
136
104
  return headersInherited
137
105
  }
138
-
139
- const replaceUrlRessource = (url, ressource) => {
140
- const urlObject = new URL(url)
141
-
142
- const searchSeparatorIndex = ressource.indexOf("?")
143
- if (searchSeparatorIndex === -1) {
144
- urlObject.pathname = ressource
145
- return urlObject
146
- }
147
-
148
- const pathname = ressource.slice(0, searchSeparatorIndex)
149
- urlObject.pathname = pathname
150
- return urlObject
151
- }
@@ -11,13 +11,13 @@ import { listenEvent } from "./listenEvent.js"
11
11
  export const createPolyglotServer = async ({
12
12
  http2 = false,
13
13
  http1Allowed = true,
14
- serverCertificate,
15
- serverCertificatePrivateKey,
14
+ certificate,
15
+ privateKey,
16
16
  }) => {
17
17
  const httpServer = http.createServer()
18
18
  const tlsServer = await createSecureServer({
19
- serverCertificate,
20
- serverCertificatePrivateKey,
19
+ certificate,
20
+ privateKey,
21
21
  http2,
22
22
  http1Allowed,
23
23
  })
@@ -63,24 +63,24 @@ export const createPolyglotServer = async ({
63
63
  // The async part is just to lazyly import "http2" or "https"
64
64
  // so that these module are parsed only if used.
65
65
  const createSecureServer = async ({
66
- serverCertificate,
67
- serverCertificatePrivateKey,
66
+ certificate,
67
+ privateKey,
68
68
  http2,
69
69
  http1Allowed,
70
70
  }) => {
71
71
  if (http2) {
72
72
  const { createSecureServer } = await import("http2")
73
73
  return createSecureServer({
74
- cert: serverCertificate,
75
- key: serverCertificatePrivateKey,
74
+ cert: certificate,
75
+ key: privateKey,
76
76
  allowHTTP1: http1Allowed,
77
77
  })
78
78
  }
79
79
 
80
80
  const { createServer } = await import("https")
81
81
  return createServer({
82
- cert: serverCertificate,
83
- key: serverCertificatePrivateKey,
82
+ cert: certificate,
83
+ key: privateKey,
84
84
  })
85
85
  }
86
86
 
@@ -16,8 +16,6 @@ export const pluginRequestWaitingCheck = ({
16
16
  } = {}) => {
17
17
  return {
18
18
  plugin_request_waiting_check: {
19
- init: () => {},
20
-
21
19
  onRequest: (request, { logger }) => {
22
20
  const timeout = setTimeout(
23
21
  () => requestWaitingCallback({ request, logger, requestWaitingMs }),
@@ -0,0 +1,55 @@
1
+ import { urlToMeta, normalizeStructuredMetaMap } from "@jsenv/url-meta"
2
+
3
+ export const pluginRessourceAliases = (ressourceAliases) => {
4
+ const ressourceAliasMap = {}
5
+ Object.keys(ressourceAliases).forEach((key) => {
6
+ ressourceAliasMap[ressourceAliases[key]] = key
7
+ })
8
+ const structuredMetaMap = normalizeStructuredMetaMap(
9
+ { alias: ressourceAliasMap },
10
+ "file:///",
11
+ )
12
+
13
+ const getRessourceAlias = (pathname) => {
14
+ const { alias } = urlToMeta({
15
+ url: `file://${pathname}`,
16
+ structuredMetaMap,
17
+ })
18
+ return alias
19
+ }
20
+
21
+ return {
22
+ ressource_aliases: {
23
+ onRequest: (request, { redirectRequest }) => {
24
+ const ressourceAlias = getRessourceAlias(request.pathname)
25
+ if (!ressourceAlias) {
26
+ return
27
+ }
28
+
29
+ const urlObjectAfterAlias = replaceUrlRessource(
30
+ request.url,
31
+ ressourceAlias,
32
+ )
33
+ const ressource = `${urlObjectAfterAlias.pathname}${urlObjectAfterAlias.search}`
34
+ redirectRequest({
35
+ ressourceBeforeAlias: request.ressource,
36
+ ressource,
37
+ })
38
+ },
39
+ },
40
+ }
41
+ }
42
+
43
+ const replaceUrlRessource = (url, ressource) => {
44
+ const urlObject = new URL(url)
45
+
46
+ const searchSeparatorIndex = ressource.indexOf("?")
47
+ if (searchSeparatorIndex === -1) {
48
+ urlObject.pathname = ressource
49
+ return urlObject
50
+ }
51
+
52
+ const pathname = ressource.slice(0, searchSeparatorIndex)
53
+ urlObject.pathname = pathname
54
+ return urlObject
55
+ }
@@ -0,0 +1,35 @@
1
+ import { createRequire } from "node:module"
2
+
3
+ const require = createRequire(import.meta.url)
4
+
5
+ export const setupRoutes = (routes) => {
6
+ const { match } = require("path-to-regexp")
7
+
8
+ const candidates = Object.keys(routes).map((pathPattern) => {
9
+ const applyPatternMatching = match(pathPattern, {
10
+ decode: decodeURIComponent,
11
+ })
12
+ return {
13
+ applyPatternMatching,
14
+ requestHandler: routes[pathPattern],
15
+ }
16
+ })
17
+
18
+ return (request, { pushResponse, redirectRequest }) => {
19
+ let result
20
+ const found = candidates.find((candidate) => {
21
+ result = candidate.applyPatternMatching(request.pathname)
22
+ return Boolean(result)
23
+ })
24
+ if (found) {
25
+ return found.requestHandler(
26
+ {
27
+ ...request,
28
+ routeParams: result.params,
29
+ },
30
+ { pushResponse, redirectRequest },
31
+ )
32
+ }
33
+ return null
34
+ }
35
+ }
@@ -1,39 +1,13 @@
1
1
  import { timeFunction } from "@jsenv/server/src/server_timing/timing_measure.js"
2
2
  import { composeTwoResponses } from "@jsenv/server/src/internal/response_composition.js"
3
+ import { applyRedirectionToRequest } from "@jsenv/server/src/internal/request_factory.js"
3
4
 
4
- const requestRedirectionSymbol = Symbol("request_redirection")
5
-
6
- export const redirectRequest = (redirection) => {
7
- return {
8
- [requestRedirectionSymbol]: redirection,
9
- }
10
- }
11
-
12
- export const composeServices = (...callbacks) => {
13
- return (request, { pushResponse, requestFactory }) => {
14
- return firstOperationMatching({
15
- array: callbacks,
16
- start: (callback) => callback(request, { pushResponse }),
17
- predicate: (returnValue) => {
18
- if (returnValue === null || typeof returnValue !== "object") {
19
- return false
20
- }
21
- const requestRedirection = returnValue[requestRedirectionSymbol]
22
- if (requestRedirection) {
23
- request = requestFactory.applyRedirectionToRequest(
24
- request,
25
- requestRedirection,
26
- )
27
- return false
28
- }
29
- return true
30
- },
31
- })
32
- }
33
- }
5
+ export const composeServices = (namedServices) => {
6
+ return async (request, { pushResponse }) => {
7
+ const redirectRequest = (requestRedirection) => {
8
+ request = applyRedirectionToRequest(request, requestRedirection)
9
+ }
34
10
 
35
- export const composeServicesWithTiming = (namedServices) => {
36
- return async (request, { pushResponse, requestFactory }) => {
37
11
  const servicesTiming = {}
38
12
  const response = await firstOperationMatching({
39
13
  array: Object.keys(namedServices).map((serviceName) => {
@@ -41,7 +15,7 @@ export const composeServicesWithTiming = (namedServices) => {
41
15
  }),
42
16
  start: async ({ serviceName, serviceFn }) => {
43
17
  const [serviceTiming, value] = await timeFunction(serviceName, () =>
44
- serviceFn(request, { pushResponse }),
18
+ serviceFn(request, { pushResponse, redirectRequest }),
45
19
  )
46
20
  Object.assign(servicesTiming, serviceTiming)
47
21
  return value
@@ -50,14 +24,6 @@ export const composeServicesWithTiming = (namedServices) => {
50
24
  if (returnValue === null || typeof returnValue !== "object") {
51
25
  return false
52
26
  }
53
- const requestRedirection = returnValue[requestRedirectionSymbol]
54
- if (requestRedirection) {
55
- request = requestFactory.applyRedirectionToRequest(
56
- request,
57
- requestRedirection,
58
- )
59
- return false
60
- }
61
27
  return true
62
28
  },
63
29
  })
@@ -8,6 +8,7 @@ import {
8
8
  // https://www.html5rocks.com/en/tutorials/eventsource/basics/
9
9
  export const createSSERoom = ({
10
10
  logLevel,
11
+ effect = () => {},
11
12
  // do not keep process alive because of rooms, something else must keep it alive
12
13
  keepProcessAlive = false,
13
14
  keepaliveDuration = 30 * 1000,
@@ -29,6 +30,7 @@ export const createSSERoom = ({
29
30
  let previousEventId = 0
30
31
  let opened = false
31
32
  let interval
33
+ let cleanupEffect = CLEANUP_NOOP
32
34
 
33
35
  const join = (request) => {
34
36
  // should we ensure a given request can join a room only once?
@@ -56,6 +58,14 @@ export const createSSERoom = ({
56
58
  next,
57
59
  complete,
58
60
  }
61
+ if (clients.size === 0) {
62
+ const effectReturnValue = effect()
63
+ if (typeof effectReturnValue === "function") {
64
+ cleanupEffect = effectReturnValue
65
+ } else {
66
+ cleanupEffect = CLEANUP_NOOP
67
+ }
68
+ }
59
69
  clients.add(client)
60
70
  logger.debug(
61
71
  `A client has joined. Number of client in room: ${clients.size}`,
@@ -101,6 +111,10 @@ export const createSSERoom = ({
101
111
 
102
112
  return () => {
103
113
  clients.delete(client)
114
+ if (clients.size === 0) {
115
+ cleanupEffect()
116
+ cleanupEffect = CLEANUP_NOOP
117
+ }
104
118
  logger.debug(`A client left. Number of client in room: ${clients.size}`)
105
119
  }
106
120
  })
@@ -210,6 +224,8 @@ export const createSSERoom = ({
210
224
  return room
211
225
  }
212
226
 
227
+ const CLEANUP_NOOP = () => {}
228
+
213
229
  const requestMap = new Map()
214
230
 
215
231
  const connectRequestAndRoom = (request, room, roomObservable) => {
@@ -11,7 +11,11 @@ import {
11
11
  import { createPolyglotServer } from "./internal/server-polyglot.js"
12
12
  import { trackServerPendingConnections } from "./internal/trackServerPendingConnections.js"
13
13
  import { trackServerPendingRequests } from "./internal/trackServerPendingRequests.js"
14
- import { createRequestFactory } from "./internal/request_factory.js"
14
+ import {
15
+ fromNodeRequest,
16
+ createPushRequest,
17
+ applyRedirectionToRequest,
18
+ } from "./internal/request_factory.js"
15
19
  import { populateNodeResponse } from "./internal/populateNodeResponse.js"
16
20
  import { colorizeResponseStatus } from "./internal/colorizeResponseStatus.js"
17
21
  import { getServerOrigins } from "./internal/getServerOrigins.js"
@@ -43,8 +47,8 @@ export const startServer = async ({
43
47
  ip = "0.0.0.0", // will it work on windows ? https://github.com/nodejs/node/issues/14900
44
48
  port = 0, // assign a random available port
45
49
  portHint,
46
- serverCertificatePrivateKey,
47
- serverCertificate,
50
+ privateKey,
51
+ certificate,
48
52
 
49
53
  // when inside a worker, we should not try to stop server on SIGINT
50
54
  // otherwise it can create an EPIPE error while primary process tries
@@ -56,7 +60,6 @@ export const startServer = async ({
56
60
  stopOnInternalError = false,
57
61
  keepProcessAlive = true,
58
62
  requestToResponse = () => null,
59
- ressourceAliases = {},
60
63
  plugins = {},
61
64
 
62
65
  sendServerInternalErrorDetails = false,
@@ -71,11 +74,11 @@ export const startServer = async ({
71
74
  throw new Error(`protocol must be http or https, got ${protocol}`)
72
75
  }
73
76
  if (protocol === "https") {
74
- if (!serverCertificate) {
75
- throw new Error(`missing serverCertificate for https server`)
77
+ if (!certificate) {
78
+ throw new Error(`missing certificate for https server`)
76
79
  }
77
- if (!serverCertificatePrivateKey) {
78
- throw new Error(`missing serverCertificatePrivateKey for https server`)
80
+ if (!privateKey) {
81
+ throw new Error(`missing privateKey for https server`)
79
82
  }
80
83
  }
81
84
  if (http2 && protocol !== "https") {
@@ -131,8 +134,8 @@ export const startServer = async ({
131
134
  protocol,
132
135
  redirectHttpToHttps,
133
136
  allowHttpRequestOnHttps,
134
- serverCertificate,
135
- serverCertificatePrivateKey,
137
+ certificate,
138
+ privateKey,
136
139
  http2,
137
140
  http1Allowed,
138
141
  })
@@ -226,11 +229,6 @@ export const startServer = async ({
226
229
  })
227
230
  })
228
231
 
229
- const requestFactory = createRequestFactory({
230
- serverOrigin,
231
- ressourceAliases,
232
- })
233
-
234
232
  const requestCallback = async (nodeRequest, nodeResponse) => {
235
233
  if (!nagle) {
236
234
  nodeRequest.connection.setNoDelay(true)
@@ -268,7 +266,8 @@ export const startServer = async ({
268
266
  return stopCallbackList.add(abort)
269
267
  })
270
268
 
271
- const request = requestFactory.fromNodeRequest(nodeRequest, {
269
+ const request = fromNodeRequest(nodeRequest, {
270
+ serverOrigin,
272
271
  signal: receiveRequestOperation.signal,
273
272
  })
274
273
 
@@ -333,29 +332,6 @@ export const startServer = async ({
333
332
  const pushResponse = async ({ path, method }, { requestNode }) => {
334
333
  const http2Stream = nodeResponse.stream
335
334
 
336
- if (typeof path !== "string" || path[0] !== "/") {
337
- addRequestLog(requestNode, {
338
- type: "warn",
339
- value: `pushResponse call ignored because path is invalid (must be a string starting with "/", found ${path})`,
340
- })
341
- return
342
- }
343
- if (!request.http2) {
344
- addRequestLog(requestNode, {
345
- type: "warn",
346
- value: `pushResponse call ignored because request is not http2`,
347
- })
348
- return
349
- }
350
- const canPushStream = testCanPushStream(http2Stream)
351
- if (!canPushStream.can) {
352
- addRequestLog(requestNode, {
353
- type: "debug",
354
- value: `Ignoring call to pushResponse because ${canPushStream.reason}`,
355
- })
356
- return
357
- }
358
-
359
335
  // being able to push a stream is nice to have
360
336
  // so when it fails it's not critical
361
337
  const onPushStreamError = (e) => {
@@ -414,7 +390,7 @@ export const startServer = async ({
414
390
  pushResponseOperation.addAbortSignal(signal)
415
391
  pushResponseOperation.addAbortSignal(abortController.signal)
416
392
 
417
- const pushRequest = requestFactory.createPushRequest(request, {
393
+ const pushRequest = createPushRequest(request, {
418
394
  signal: pushResponseOperation.signal,
419
395
  pathname: path,
420
396
  method,
@@ -476,15 +452,23 @@ export const startServer = async ({
476
452
  const shortcircuitResponse = (value) => {
477
453
  responseFromShortcircuit = value
478
454
  }
455
+ const redirectRequest = (requestRedirection) => {
456
+ request = applyRedirectionToRequest(request, requestRedirection)
457
+ }
479
458
  Object.keys(plugins).forEach((pluginName) => {
480
459
  const { onRequest } = plugins[pluginName]
481
460
  if (onRequest) {
482
461
  const returnValue = onRequest(request, {
483
462
  warn,
463
+ logger,
484
464
  shortcircuitResponse,
465
+ redirectRequest,
485
466
  })
486
467
  if (returnValue) {
487
- requestPlugins.push(returnValue)
468
+ requestPlugins.push({
469
+ pluginName,
470
+ ...returnValue,
471
+ })
488
472
  }
489
473
  }
490
474
  })
@@ -493,10 +477,53 @@ export const startServer = async ({
493
477
  : await generateResponseDescription({
494
478
  requestToResponse,
495
479
  request,
496
- pushResponse: ({ path, method }) => {
480
+ pushResponse: async ({ path, method }) => {
481
+ if (typeof path !== "string" || path[0] !== "/") {
482
+ addRequestLog(requestNode, {
483
+ type: "warn",
484
+ value: `response push ignored because path is invalid (must be a string starting with "/", found ${path})`,
485
+ })
486
+ return
487
+ }
488
+ if (!request.http2) {
489
+ addRequestLog(requestNode, {
490
+ type: "warn",
491
+ value: `response push ignored because request is not http2`,
492
+ })
493
+ return
494
+ }
495
+ const canPushStream = testCanPushStream(nodeResponse.stream)
496
+ if (!canPushStream.can) {
497
+ addRequestLog(requestNode, {
498
+ type: "debug",
499
+ value: `response push ignored because ${canPushStream.reason}`,
500
+ })
501
+ return
502
+ }
503
+
504
+ let prevented = false
505
+ const prevent = () => {
506
+ prevented = true
507
+ }
508
+ const preventedByPlugin = requestPlugins.find((requestPlugin) => {
509
+ const { onPushResponse } = requestPlugin
510
+ if (!onPushResponse) {
511
+ return false
512
+ }
513
+ onPushResponse({ path, method }, { prevent })
514
+ return prevented
515
+ })
516
+ if (prevented) {
517
+ addRequestLog(requestNode, {
518
+ type: "debug",
519
+ value: `response push prevented by "${preventedByPlugin.pluginName}" plugin`,
520
+ })
521
+ return
522
+ }
523
+
497
524
  const requestChildNode = { logs: [], children: [] }
498
525
  requestNode.children.push(requestChildNode)
499
- return pushResponse(
526
+ await pushResponse(
500
527
  { path, method },
501
528
  {
502
529
  requestNode: requestChildNode,
@@ -504,7 +531,6 @@ export const startServer = async ({
504
531
  },
505
532
  )
506
533
  },
507
- requestFactory,
508
534
  })
509
535
 
510
536
  if (aborted) {
@@ -698,8 +724,8 @@ const createNodeServer = async ({
698
724
  protocol,
699
725
  redirectHttpToHttps,
700
726
  allowHttpRequestOnHttps,
701
- serverCertificate,
702
- serverCertificatePrivateKey,
727
+ certificate,
728
+ privateKey,
703
729
  http2,
704
730
  http1Allowed,
705
731
  }) => {
@@ -709,8 +735,8 @@ const createNodeServer = async ({
709
735
 
710
736
  if (redirectHttpToHttps || allowHttpRequestOnHttps) {
711
737
  return createPolyglotServer({
712
- serverCertificate,
713
- serverCertificatePrivateKey,
738
+ certificate,
739
+ privateKey,
714
740
  http2,
715
741
  http1Allowed,
716
742
  })
@@ -718,8 +744,8 @@ const createNodeServer = async ({
718
744
 
719
745
  const { createServer } = await import("https")
720
746
  return createServer({
721
- cert: serverCertificate,
722
- key: serverCertificatePrivateKey,
747
+ cert: certificate,
748
+ key: privateKey,
723
749
  })
724
750
  }
725
751
 
@@ -727,12 +753,10 @@ const generateResponseDescription = async ({
727
753
  requestToResponse,
728
754
  request,
729
755
  pushResponse,
730
- requestFactory,
731
756
  }) => {
732
757
  try {
733
758
  const returnValue = await requestToResponse(request, {
734
759
  pushResponse,
735
- requestFactory,
736
760
  })
737
761
  return {
738
762
  responseProperties: returnValue,