@jsenv/server 12.8.0 → 14.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 (30) hide show
  1. package/README.md +66 -81
  2. package/package.json +3 -2
  3. package/src/content_negotiation/{applyContentNegotiation.js → pick_accepted_content.js} +1 -3
  4. package/src/content_negotiation/{negotiateContentEncoding.js → pick_content_encoding.js} +3 -3
  5. package/src/content_negotiation/{negotiateContentLanguage.js → pick_content_language.js} +3 -3
  6. package/src/content_negotiation/{negotiateContentType.js → pick_content_type.js} +3 -3
  7. package/src/fetch_filesystem.js +2 -2
  8. package/src/internal/dns_resolution.js +17 -0
  9. package/src/internal/hostname_parser.js +48 -0
  10. package/src/internal/listen.js +15 -13
  11. package/src/internal/request_factory.js +3 -1
  12. package/src/internal/server-polyglot.js +1 -0
  13. package/src/internal/server_ips.js +49 -0
  14. package/src/main.js +11 -13
  15. package/src/serve_directory.js +2 -2
  16. package/src/service_controller.js +174 -0
  17. package/src/{cors/plugin_cors.js → services/cors/jsenv_service_cors.js} +29 -45
  18. package/src/services/error_handler/jsenv_service_error_handler.js +98 -0
  19. package/src/{content_negotiation/checkContentNegotiation.js → services/response_acceptance_check/jsenv_service_response_acceptance_check.js} +16 -7
  20. package/src/services/ressource_aliases/jsenv_service_ressource_aliases.js +40 -0
  21. package/src/sse/{createSSERoom.js → sse_room.js} +18 -16
  22. package/src/start_server.js +1045 -0
  23. package/src/content_negotiation/plugin_content_negotiation_check.js +0 -15
  24. package/src/error_to_response_default.js +0 -94
  25. package/src/internal/getServerOrigins.js +0 -63
  26. package/src/plugin_request_waiting_check.js +0 -32
  27. package/src/ressource_aliases/plugin_ressource_aliases.js +0 -44
  28. package/src/server_timing/plugin_server_timing.js +0 -28
  29. package/src/service_composition/service_composition.js +0 -55
  30. package/src/startServer.js +0 -868
@@ -1,15 +0,0 @@
1
- import { checkContentNegotiation } from "./checkContentNegotiation.js"
2
-
3
- export const pluginContentNegotiationCheck = () => {
4
- return {
5
- content_negotiation_check: {
6
- onRequest: (request, { warn }) => {
7
- return {
8
- onResponse: (response) => {
9
- checkContentNegotiation(request, response, { warn })
10
- },
11
- }
12
- },
13
- },
14
- }
15
- }
@@ -1,94 +0,0 @@
1
- import { negotiateContentType } from "./content_negotiation/negotiateContentType.js"
2
-
3
- export const applyDefaultErrorToResponse = (
4
- serverInternalError,
5
- { request, sendErrorDetails = false },
6
- ) => {
7
- const serverInternalErrorIsAPrimitive =
8
- serverInternalError === null ||
9
- (typeof serverInternalError !== "object" &&
10
- typeof serverInternalError !== "function")
11
-
12
- if (!serverInternalErrorIsAPrimitive && serverInternalError.asResponse) {
13
- return serverInternalError.asResponse()
14
- }
15
-
16
- const dataToSend = serverInternalErrorIsAPrimitive
17
- ? {
18
- code: "VALUE_THROWED",
19
- value: serverInternalError,
20
- }
21
- : {
22
- code: serverInternalError.code || "UNKNOWN_ERROR",
23
- ...(sendErrorDetails
24
- ? {
25
- stack: serverInternalError.stack,
26
- ...serverInternalError,
27
- }
28
- : {}),
29
- }
30
-
31
- const availableContentTypes = {
32
- "text/html": () => {
33
- const renderHtmlForErrorWithoutDetails = () => {
34
- return `<p>Details not available: to enable them server must be started with sendErrorDetails: true.</p>`
35
- }
36
-
37
- const renderHtmlForErrorWithDetails = () => {
38
- if (serverInternalErrorIsAPrimitive) {
39
- return `<pre>${JSON.stringify(serverInternalError, null, " ")}</pre>`
40
- }
41
- return `<pre>${serverInternalError.stack}</pre>`
42
- }
43
-
44
- const body = `<!DOCTYPE html>
45
- <html>
46
- <head>
47
- <title>Internal server error</title>
48
- <meta charset="utf-8" />
49
- <link rel="icon" href="data:," />
50
- </head>
51
-
52
- <body>
53
- <h1>Internal server error</h1>
54
- <p>${
55
- serverInternalErrorIsAPrimitive
56
- ? `Code inside server has thrown a literal.`
57
- : `Code inside server has thrown an error.`
58
- }</p>
59
- <details>
60
- <summary>See internal error details</summary>
61
- ${
62
- sendErrorDetails
63
- ? renderHtmlForErrorWithDetails()
64
- : renderHtmlForErrorWithoutDetails()
65
- }
66
- </details>
67
- </body>
68
- </html>`
69
-
70
- return {
71
- headers: {
72
- "content-type": "text/html",
73
- "content-length": Buffer.byteLength(body),
74
- },
75
- body,
76
- }
77
- },
78
- "application/json": () => {
79
- const body = JSON.stringify(dataToSend)
80
- return {
81
- headers: {
82
- "content-type": "application/json",
83
- "content-length": Buffer.byteLength(body),
84
- },
85
- body,
86
- }
87
- },
88
- }
89
- const bestContentType = negotiateContentType(
90
- request,
91
- Object.keys(availableContentTypes),
92
- )
93
- return availableContentTypes[bestContentType || "application/json"]()
94
- }
@@ -1,63 +0,0 @@
1
- import { networkInterfaces } from "node:os"
2
- import { lookup } from "node:dns"
3
-
4
- export const getServerOrigins = async ({ protocol, ip, port }) => {
5
- const isInternalIp = ip === "127.0.0.1"
6
- const localhostDnsResolution = await applyDnsResolution("localhost")
7
- const internalOrigin = createServerOrigin({
8
- protocol,
9
- hostname:
10
- localhostDnsResolution.address === "127.0.0.1"
11
- ? "localhost"
12
- : "127.0.0.1",
13
- port,
14
- })
15
- if (isInternalIp) {
16
- return { internal: internalOrigin }
17
- }
18
- const isAnyIp = !ip || ip === "::" || ip === "0.0.0.0"
19
- return {
20
- internal: internalOrigin,
21
- external: createServerOrigin({
22
- protocol,
23
- hostname: isAnyIp ? getExternalIp(ip) : ip,
24
- port,
25
- }),
26
- }
27
- }
28
-
29
- const applyDnsResolution = async (hostname) => {
30
- const dnsResolution = await new Promise((resolve, reject) => {
31
- lookup(hostname, (error, address, family) => {
32
- if (error) {
33
- reject(error)
34
- } else {
35
- resolve({ address, family })
36
- }
37
- })
38
- })
39
- return dnsResolution
40
- }
41
-
42
- const createServerOrigin = ({ protocol, hostname, port }) => {
43
- const url = new URL("https://127.0.0.1:80")
44
- url.protocol = protocol
45
- url.hostname = hostname
46
- url.port = port
47
- return url.origin
48
- }
49
-
50
- const getExternalIp = () => {
51
- const networkInterfaceMap = networkInterfaces()
52
- let internalIPV4NetworkAddress
53
- Object.keys(networkInterfaceMap).find((key) => {
54
- const networkAddressArray = networkInterfaceMap[key]
55
- return networkAddressArray.find((networkAddress) => {
56
- if (networkAddress.internal) return false
57
- if (networkAddress.family !== "IPv4") return false
58
- internalIPV4NetworkAddress = networkAddress
59
- return true
60
- })
61
- })
62
- return internalIPV4NetworkAddress ? internalIPV4NetworkAddress.address : null
63
- }
@@ -1,32 +0,0 @@
1
- import { createDetailedMessage } from "@jsenv/log"
2
-
3
- export const pluginRequestWaitingCheck = ({
4
- requestWaitingMs = 20000,
5
- requestWaitingCallback = ({ request, logger, requestWaitingMs }) => {
6
- logger.warn(
7
- createDetailedMessage(
8
- `still no response found for request after ${requestWaitingMs} ms`,
9
- {
10
- "request url": `${request.origin}${request.ressource}`,
11
- "request headers": JSON.stringify(request.headers, null, " "),
12
- },
13
- ),
14
- )
15
- },
16
- } = {}) => {
17
- return {
18
- plugin_request_waiting_check: {
19
- onRequest: (request, { logger }) => {
20
- const timeout = setTimeout(
21
- () => requestWaitingCallback({ request, logger, requestWaitingMs }),
22
- requestWaitingMs,
23
- )
24
- return {
25
- onResponse: () => {
26
- clearTimeout(timeout)
27
- },
28
- }
29
- },
30
- },
31
- }
32
- }
@@ -1,44 +0,0 @@
1
- import { URL_META } from "@jsenv/url-meta"
2
-
3
- export const pluginRessourceAliases = (ressourceAliases) => {
4
- const aliases = {}
5
- Object.keys(ressourceAliases).forEach((key) => {
6
- aliases[asFileUrl(key)] = asFileUrl(ressourceAliases[key])
7
- })
8
- return {
9
- ressource_aliases: {
10
- onRequest: (request, { redirectRequest }) => {
11
- const ressourceBeforeAlias = request.ressource
12
- const urlAfterAliasing = URL_META.applyAliases({
13
- url: asFileUrl(request.pathname),
14
- aliases,
15
- })
16
- const ressourceAfterAlias = urlAfterAliasing.slice("file://".length)
17
- if (ressourceBeforeAlias === ressourceAfterAlias) {
18
- return
19
- }
20
- const ressource = replaceRessource(
21
- ressourceBeforeAlias,
22
- ressourceAfterAlias,
23
- )
24
- redirectRequest({
25
- ressourceBeforeAlias,
26
- ressource,
27
- })
28
- },
29
- },
30
- }
31
- }
32
-
33
- const asFileUrl = (specifier) => new URL(specifier, "file:///").href
34
-
35
- const replaceRessource = (ressourceBeforeAlias, newValue) => {
36
- const urlObject = new URL(ressourceBeforeAlias, "file://")
37
- const searchSeparatorIndex = newValue.indexOf("?")
38
- if (searchSeparatorIndex > -1) {
39
- return newValue // let new value override search params
40
- }
41
- urlObject.pathname = newValue
42
- const ressource = `${urlObject.pathname}${urlObject.search}`
43
- return ressource
44
- }
@@ -1,28 +0,0 @@
1
- // https://www.w3.org/TR/server-timing/
2
-
3
- import { performance } from "node:perf_hooks"
4
-
5
- import { timingToServerTimingResponseHeaders } from "./timing_header.js"
6
-
7
- export const pluginServerTiming = () => {
8
- return {
9
- server_timing: {
10
- onRequest: () => {
11
- const requestStartEvent = performance.now()
12
- return {
13
- onResponse: (response) => {
14
- const responseEndEvent = performance.now()
15
- const timeToCreateResponse = responseEndEvent - requestStartEvent
16
- const serverTiming = {
17
- ...response.timing,
18
- "time to start responding": timeToCreateResponse,
19
- }
20
- return {
21
- headers: timingToServerTimingResponseHeaders(serverTiming),
22
- }
23
- },
24
- }
25
- },
26
- },
27
- }
28
- }
@@ -1,55 +0,0 @@
1
- import { timeFunction } from "../server_timing/timing_measure.js"
2
- import { composeTwoResponses } from "../internal/response_composition.js"
3
- import { applyRedirectionToRequest } from "../internal/request_factory.js"
4
-
5
- export const composeServices = (namedServices) => {
6
- return async (request, { pushResponse }) => {
7
- const redirectRequest = (requestRedirection) => {
8
- request = applyRedirectionToRequest(request, requestRedirection)
9
- }
10
-
11
- const servicesTiming = {}
12
- const response = await firstOperationMatching({
13
- array: Object.keys(namedServices).map((serviceName) => {
14
- return { serviceName, serviceFn: namedServices[serviceName] }
15
- }),
16
- start: async ({ serviceName, serviceFn }) => {
17
- const [serviceTiming, value] = await timeFunction(serviceName, () =>
18
- serviceFn(request, { pushResponse, redirectRequest }),
19
- )
20
- Object.assign(servicesTiming, serviceTiming)
21
- return value
22
- },
23
- predicate: (returnValue) => {
24
- if (returnValue === null || typeof returnValue !== "object") {
25
- return false
26
- }
27
- return true
28
- },
29
- })
30
- if (response) {
31
- return composeTwoResponses({ timing: servicesTiming }, response)
32
- }
33
- return null
34
- }
35
- }
36
-
37
- const firstOperationMatching = ({ array, start, predicate }) => {
38
- return new Promise((resolve, reject) => {
39
- const visit = (index) => {
40
- if (index >= array.length) {
41
- return resolve()
42
- }
43
- const input = array[index]
44
- const returnValue = start(input)
45
- return Promise.resolve(returnValue).then((output) => {
46
- if (predicate(output)) {
47
- return resolve(output)
48
- }
49
- return visit(index + 1)
50
- }, reject)
51
- }
52
-
53
- visit(0)
54
- })
55
- }