@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.
- package/README.md +66 -81
- package/package.json +3 -2
- package/src/content_negotiation/{applyContentNegotiation.js → pick_accepted_content.js} +1 -3
- package/src/content_negotiation/{negotiateContentEncoding.js → pick_content_encoding.js} +3 -3
- package/src/content_negotiation/{negotiateContentLanguage.js → pick_content_language.js} +3 -3
- package/src/content_negotiation/{negotiateContentType.js → pick_content_type.js} +3 -3
- package/src/fetch_filesystem.js +2 -2
- package/src/internal/dns_resolution.js +17 -0
- package/src/internal/hostname_parser.js +48 -0
- package/src/internal/listen.js +15 -13
- package/src/internal/request_factory.js +3 -1
- package/src/internal/server-polyglot.js +1 -0
- package/src/internal/server_ips.js +49 -0
- package/src/main.js +11 -13
- package/src/serve_directory.js +2 -2
- package/src/service_controller.js +174 -0
- package/src/{cors/plugin_cors.js → services/cors/jsenv_service_cors.js} +29 -45
- package/src/services/error_handler/jsenv_service_error_handler.js +98 -0
- package/src/{content_negotiation/checkContentNegotiation.js → services/response_acceptance_check/jsenv_service_response_acceptance_check.js} +16 -7
- package/src/services/ressource_aliases/jsenv_service_ressource_aliases.js +40 -0
- package/src/sse/{createSSERoom.js → sse_room.js} +18 -16
- package/src/start_server.js +1045 -0
- package/src/content_negotiation/plugin_content_negotiation_check.js +0 -15
- package/src/error_to_response_default.js +0 -94
- package/src/internal/getServerOrigins.js +0 -63
- package/src/plugin_request_waiting_check.js +0 -32
- package/src/ressource_aliases/plugin_ressource_aliases.js +0 -44
- package/src/server_timing/plugin_server_timing.js +0 -28
- package/src/service_composition/service_composition.js +0 -55
- package/src/startServer.js +0 -868
package/README.md
CHANGED
|
@@ -8,42 +8,23 @@ import { startServer } from "@jsenv/server"
|
|
|
8
8
|
await startServer({
|
|
9
9
|
protocol: "http",
|
|
10
10
|
port: 8080,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
services: [
|
|
12
|
+
{
|
|
13
|
+
handleRequest: () => {
|
|
14
|
+
return {
|
|
15
|
+
status: 200,
|
|
16
|
+
headers: {
|
|
17
|
+
"content-type": "text/plain",
|
|
18
|
+
},
|
|
19
|
+
body: "Hello world",
|
|
20
|
+
}
|
|
16
21
|
},
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
},
|
|
22
|
+
},
|
|
23
|
+
],
|
|
20
24
|
})
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
_Code starting a server and using [node-fetch](https://github.com/node-fetch/node-fetch) to send a request on that server:_
|
|
26
|
-
|
|
27
|
-
```js
|
|
28
|
-
import fetch from "node-fetch"
|
|
29
|
-
import { startServer } from "@jsenv/server"
|
|
30
|
-
|
|
31
|
-
const server = await startServer({
|
|
32
|
-
requestToResponse: () => {
|
|
33
|
-
return {
|
|
34
|
-
status: 200,
|
|
35
|
-
headers: {
|
|
36
|
-
"content-type": "text/plain",
|
|
37
|
-
},
|
|
38
|
-
body: "Hello world",
|
|
39
|
-
}
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
const response = await fetch(server.origin)
|
|
44
|
-
const responseBodyAsText = await response.text()
|
|
45
|
-
console.log(responseBodyAsText) // "Hello world"
|
|
46
|
-
```
|
|
27
|
+
# Examples
|
|
47
28
|
|
|
48
29
|
_Code starting a server with 2 request handlers:_
|
|
49
30
|
|
|
@@ -57,22 +38,24 @@ _Code starting a server with 2 request handlers:_
|
|
|
57
38
|
*/
|
|
58
39
|
import { startServer, composeServices } from "@jsenv/server"
|
|
59
40
|
|
|
60
|
-
const indexService = (request) => {
|
|
61
|
-
if (request.ressource === "/") {
|
|
62
|
-
return { status: 200 }
|
|
63
|
-
}
|
|
64
|
-
return null // means "I don't handle that request"
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const notFoundService = (request) => {
|
|
68
|
-
return { status: 404 }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
41
|
const server = await startServer({
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
42
|
+
services: [
|
|
43
|
+
{
|
|
44
|
+
name: "index",
|
|
45
|
+
handleRequest: (request) => {
|
|
46
|
+
if (request.ressource === "/") {
|
|
47
|
+
return { status: 200 }
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "otherwise",
|
|
54
|
+
handleRequest: () => {
|
|
55
|
+
return { status: 404 }
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
76
59
|
})
|
|
77
60
|
|
|
78
61
|
const fetch = await import("node-fetch")
|
|
@@ -91,26 +74,25 @@ import { startServer } from "@jsenv/server"
|
|
|
91
74
|
|
|
92
75
|
await startServer({
|
|
93
76
|
protocol: "https",
|
|
94
|
-
certificate:
|
|
95
|
-
privateKey:
|
|
77
|
+
certificate: readFileSync(new URL("./server.crt", import.meta.url), "utf8"),
|
|
78
|
+
privateKey: readFileSync(new URL("./server.key", import.meta.url), "utf8"),
|
|
96
79
|
allowHttpRequestOnHttps: true,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
80
|
+
services: [
|
|
81
|
+
{
|
|
82
|
+
handleRequest: (request) => {
|
|
83
|
+
const clientUsesHttp = request.origin.startsWith("http:")
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
status: 200,
|
|
87
|
+
headers: {
|
|
88
|
+
"content-type": "text/plain",
|
|
89
|
+
},
|
|
90
|
+
body: clientUsesHttp ? `Welcome http user` : `Welcome https user`,
|
|
91
|
+
}
|
|
104
92
|
},
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
108
95
|
})
|
|
109
|
-
|
|
110
|
-
function readFileSyncAsString(relativeUrl) {
|
|
111
|
-
const fileUrl = new URL(relativeUrl, import.meta.url)
|
|
112
|
-
return String(readFileSync(fileUrl))
|
|
113
|
-
}
|
|
114
96
|
```
|
|
115
97
|
|
|
116
98
|
_Code starting a server for static files:_
|
|
@@ -119,28 +101,31 @@ _Code starting a server for static files:_
|
|
|
119
101
|
import { startServer, fetchFileSystem } from "@jsenv/server"
|
|
120
102
|
|
|
121
103
|
await startServer({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
104
|
+
services: [
|
|
105
|
+
{
|
|
106
|
+
handleRequest: async (request) => {
|
|
107
|
+
const fileUrl = new URL(request.ressource.slice(1), import.meta.url)
|
|
108
|
+
const response = await fetchFileSystem(fileUrl, request)
|
|
109
|
+
return response
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
],
|
|
129
113
|
})
|
|
130
114
|
```
|
|
131
115
|
|
|
132
116
|
# Documentation
|
|
133
117
|
|
|
134
|
-
- [
|
|
135
|
-
- [
|
|
136
|
-
- [
|
|
137
|
-
- [
|
|
138
|
-
- [
|
|
139
|
-
- [
|
|
140
|
-
- [Content negotiation](./docs/content_negotiation
|
|
141
|
-
- [
|
|
142
|
-
- [
|
|
143
|
-
- [
|
|
118
|
+
- [Handling requests](./docs/handling_requests.md)
|
|
119
|
+
- [Handling errors](./docs/handling_errors.md)
|
|
120
|
+
- [Server timing](./docs/server_timing.md)
|
|
121
|
+
- [CORS](./docs/cors.md)
|
|
122
|
+
- [https](./docs/https.md)
|
|
123
|
+
- [Serving files](./docs/serving_files.md)
|
|
124
|
+
- [Content negotiation](./docs/content_negotiation.md)
|
|
125
|
+
- [Websocket](./docs/websocket.md)
|
|
126
|
+
- [Server Sent Events](./docs/server_sent_events.md)
|
|
127
|
+
- [Cluster](./docs/cluster.md)
|
|
128
|
+
- [Http2 push](./docs/http2_push.md)
|
|
144
129
|
|
|
145
130
|
# Installation
|
|
146
131
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "14.0.0",
|
|
4
4
|
"description": "Write your Node.js server using pure functions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"@jsenv/abort": "4.2.3",
|
|
38
38
|
"@jsenv/log": "3.1.0",
|
|
39
39
|
"@jsenv/url-meta": "7.0.0",
|
|
40
|
-
"@jsenv/utils": "2.0.1"
|
|
40
|
+
"@jsenv/utils": "2.0.1",
|
|
41
|
+
"ws": "8.8.1"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@jsenv/fetch": "../fetch/",
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const pickAcceptedContent = ({
|
|
2
2
|
availables,
|
|
3
3
|
accepteds,
|
|
4
4
|
getAcceptanceScore,
|
|
5
5
|
}) => {
|
|
6
6
|
let highestScore = -1
|
|
7
7
|
let availableWithHighestScore = null
|
|
8
|
-
|
|
9
8
|
let availableIndex = 0
|
|
10
9
|
while (availableIndex < availables.length) {
|
|
11
10
|
const available = availables[availableIndex]
|
|
@@ -23,6 +22,5 @@ export const applyContentNegotiation = ({
|
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
|
-
|
|
27
25
|
return availableWithHighestScore
|
|
28
26
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseMultipleHeader } from "../internal/multiple-header.js"
|
|
2
|
-
import {
|
|
2
|
+
import { pickAcceptedContent } from "./pick_accepted_content.js"
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const pickContentEncoding = (request, availableEncodings) => {
|
|
5
5
|
const { headers = {} } = request
|
|
6
6
|
const requestAcceptEncodingHeader = headers["accept-encoding"]
|
|
7
7
|
if (!requestAcceptEncodingHeader) {
|
|
@@ -11,7 +11,7 @@ export const negotiateContentEncoding = (request, availableEncodings) => {
|
|
|
11
11
|
const encodingsAccepted = parseAcceptEncodingHeader(
|
|
12
12
|
requestAcceptEncodingHeader,
|
|
13
13
|
)
|
|
14
|
-
return
|
|
14
|
+
return pickAcceptedContent({
|
|
15
15
|
accepteds: encodingsAccepted,
|
|
16
16
|
availables: availableEncodings,
|
|
17
17
|
getAcceptanceScore: getEncodingAcceptanceScore,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseMultipleHeader } from "../internal/multiple-header.js"
|
|
2
|
-
import {
|
|
2
|
+
import { pickAcceptedContent } from "./pick_accepted_content.js"
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const pickContentLanguage = (request, availableLanguages) => {
|
|
5
5
|
const { headers = {} } = request
|
|
6
6
|
const requestAcceptLanguageHeader = headers["accept-language"]
|
|
7
7
|
if (!requestAcceptLanguageHeader) {
|
|
@@ -11,7 +11,7 @@ export const negotiateContentLanguage = (request, availableLanguages) => {
|
|
|
11
11
|
const languagesAccepted = parseAcceptLanguageHeader(
|
|
12
12
|
requestAcceptLanguageHeader,
|
|
13
13
|
)
|
|
14
|
-
return
|
|
14
|
+
return pickAcceptedContent({
|
|
15
15
|
accepteds: languagesAccepted,
|
|
16
16
|
availables: availableLanguages,
|
|
17
17
|
getAcceptanceScore: getLanguageAcceptanceScore,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseMultipleHeader } from "../internal/multiple-header.js"
|
|
2
|
-
import {
|
|
2
|
+
import { pickAcceptedContent } from "./pick_accepted_content.js"
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const pickContentType = (request, availableContentTypes) => {
|
|
5
5
|
const { headers = {} } = request
|
|
6
6
|
const requestAcceptHeader = headers.accept
|
|
7
7
|
if (!requestAcceptHeader) {
|
|
@@ -9,7 +9,7 @@ export const negotiateContentType = (request, availableContentTypes) => {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const contentTypesAccepted = parseAcceptHeader(requestAcceptHeader)
|
|
12
|
-
return
|
|
12
|
+
return pickAcceptedContent({
|
|
13
13
|
accepteds: contentTypesAccepted,
|
|
14
14
|
availables: availableContentTypes,
|
|
15
15
|
getAcceptanceScore: getContentTypeAcceptanceScore,
|
package/src/fetch_filesystem.js
CHANGED
|
@@ -12,7 +12,7 @@ import { bufferToEtag } from "./internal/etag.js"
|
|
|
12
12
|
import { composeTwoResponses } from "./internal/response_composition.js"
|
|
13
13
|
import { convertFileSystemErrorToResponseProperties } from "./internal/convertFileSystemErrorToResponseProperties.js"
|
|
14
14
|
import { timeFunction } from "./server_timing/timing_measure.js"
|
|
15
|
-
import {
|
|
15
|
+
import { pickContentEncoding } from "./content_negotiation/pick_content_encoding.js"
|
|
16
16
|
import { serveDirectory } from "./serve_directory.js"
|
|
17
17
|
|
|
18
18
|
export const fetchFileSystem = async (
|
|
@@ -360,7 +360,7 @@ const getMtimeResponse = async ({ headers, sourceStat }) => {
|
|
|
360
360
|
}
|
|
361
361
|
|
|
362
362
|
const getCompressedResponse = async ({ sourceUrl, headers }) => {
|
|
363
|
-
const acceptedCompressionFormat =
|
|
363
|
+
const acceptedCompressionFormat = pickContentEncoding(
|
|
364
364
|
{ headers },
|
|
365
365
|
Object.keys(availableCompressionFormats),
|
|
366
366
|
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { lookup } from "node:dns"
|
|
2
|
+
|
|
3
|
+
export const applyDnsResolution = async (
|
|
4
|
+
hostname,
|
|
5
|
+
{ verbatim = false } = {},
|
|
6
|
+
) => {
|
|
7
|
+
const dnsResolution = await new Promise((resolve, reject) => {
|
|
8
|
+
lookup(hostname, { verbatim }, (error, address, family) => {
|
|
9
|
+
if (error) {
|
|
10
|
+
reject(error)
|
|
11
|
+
} else {
|
|
12
|
+
resolve({ address, family })
|
|
13
|
+
}
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
return dnsResolution
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isIP } from "node:net"
|
|
2
|
+
|
|
3
|
+
export const parseHostname = (hostname) => {
|
|
4
|
+
if (hostname === "0.0.0.0") {
|
|
5
|
+
return {
|
|
6
|
+
type: "ip",
|
|
7
|
+
label: "unspecified",
|
|
8
|
+
version: 4,
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
if (
|
|
12
|
+
hostname === "::" ||
|
|
13
|
+
hostname === "0000:0000:0000:0000:0000:0000:0000:0000"
|
|
14
|
+
) {
|
|
15
|
+
return {
|
|
16
|
+
type: "ip",
|
|
17
|
+
label: "unspecified",
|
|
18
|
+
version: 6,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (hostname === "127.0.0.1") {
|
|
22
|
+
return {
|
|
23
|
+
type: "ip",
|
|
24
|
+
label: "loopback",
|
|
25
|
+
version: 4,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (
|
|
29
|
+
hostname === "::1" ||
|
|
30
|
+
hostname === "0000:0000:0000:0000:0000:0000:0000:0001"
|
|
31
|
+
) {
|
|
32
|
+
return {
|
|
33
|
+
type: "ip",
|
|
34
|
+
label: "loopback",
|
|
35
|
+
version: 6,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const ipVersion = isIP(hostname)
|
|
39
|
+
if (ipVersion === 0) {
|
|
40
|
+
return {
|
|
41
|
+
type: "hostname",
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
type: "ip",
|
|
46
|
+
version: ipVersion,
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/internal/listen.js
CHANGED
|
@@ -6,7 +6,7 @@ const listen = async ({
|
|
|
6
6
|
server,
|
|
7
7
|
port,
|
|
8
8
|
portHint,
|
|
9
|
-
|
|
9
|
+
hostname,
|
|
10
10
|
}) => {
|
|
11
11
|
const listeningOperation = Abort.startOperation()
|
|
12
12
|
|
|
@@ -17,11 +17,11 @@ const listen = async ({
|
|
|
17
17
|
listeningOperation.throwIfAborted()
|
|
18
18
|
port = await findFreePort(portHint, {
|
|
19
19
|
signal: listeningOperation.signal,
|
|
20
|
-
|
|
20
|
+
hostname,
|
|
21
21
|
})
|
|
22
22
|
}
|
|
23
23
|
listeningOperation.throwIfAborted()
|
|
24
|
-
port = await startListening({ server, port,
|
|
24
|
+
port = await startListening({ server, port, hostname })
|
|
25
25
|
listeningOperation.addAbortCallback(() => stopListening(server))
|
|
26
26
|
listeningOperation.throwIfAborted()
|
|
27
27
|
|
|
@@ -35,7 +35,7 @@ export const findFreePort = async (
|
|
|
35
35
|
initialPort = 1,
|
|
36
36
|
{
|
|
37
37
|
signal = new AbortController().signal,
|
|
38
|
-
|
|
38
|
+
hostname = "127.0.0.1",
|
|
39
39
|
min = 1,
|
|
40
40
|
max = 65534,
|
|
41
41
|
next = (port) => port + 1,
|
|
@@ -46,34 +46,36 @@ export const findFreePort = async (
|
|
|
46
46
|
findFreePortOperation.addAbortSignal(signal)
|
|
47
47
|
findFreePortOperation.throwIfAborted()
|
|
48
48
|
|
|
49
|
-
const testUntil = async (port,
|
|
49
|
+
const testUntil = async (port, host) => {
|
|
50
50
|
findFreePortOperation.throwIfAborted()
|
|
51
|
-
const free = await portIsFree(port,
|
|
51
|
+
const free = await portIsFree(port, host)
|
|
52
52
|
if (free) {
|
|
53
53
|
return port
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const nextPort = next(port)
|
|
57
57
|
if (nextPort > max) {
|
|
58
|
-
throw new Error(
|
|
58
|
+
throw new Error(
|
|
59
|
+
`${hostname} has no available port between ${min} and ${max}`,
|
|
60
|
+
)
|
|
59
61
|
}
|
|
60
|
-
return testUntil(nextPort,
|
|
62
|
+
return testUntil(nextPort, hostname)
|
|
61
63
|
}
|
|
62
|
-
const freePort = await testUntil(initialPort,
|
|
64
|
+
const freePort = await testUntil(initialPort, hostname)
|
|
63
65
|
return freePort
|
|
64
66
|
} finally {
|
|
65
67
|
await findFreePortOperation.end()
|
|
66
68
|
}
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
const portIsFree = async (port,
|
|
71
|
+
const portIsFree = async (port, hostname) => {
|
|
70
72
|
const server = createServer()
|
|
71
73
|
|
|
72
74
|
try {
|
|
73
75
|
await startListening({
|
|
74
76
|
server,
|
|
75
77
|
port,
|
|
76
|
-
|
|
78
|
+
hostname,
|
|
77
79
|
})
|
|
78
80
|
} catch (error) {
|
|
79
81
|
if (error && error.code === "EADDRINUSE") {
|
|
@@ -89,7 +91,7 @@ const portIsFree = async (port, ip) => {
|
|
|
89
91
|
return true
|
|
90
92
|
}
|
|
91
93
|
|
|
92
|
-
const startListening = ({ server, port,
|
|
94
|
+
const startListening = ({ server, port, hostname }) => {
|
|
93
95
|
return new Promise((resolve, reject) => {
|
|
94
96
|
server.on("error", reject)
|
|
95
97
|
server.on("listening", () => {
|
|
@@ -97,7 +99,7 @@ const startListening = ({ server, port, ip }) => {
|
|
|
97
99
|
// https://nodejs.org/api/net.html#net_server_listen_port_host_backlog_callback
|
|
98
100
|
resolve(server.address().port)
|
|
99
101
|
})
|
|
100
|
-
server.listen(port,
|
|
102
|
+
server.listen(port, hostname)
|
|
101
103
|
})
|
|
102
104
|
}
|
|
103
105
|
|
|
@@ -6,7 +6,9 @@ export const fromNodeRequest = (nodeRequest, { serverOrigin, signal }) => {
|
|
|
6
6
|
const body = nodeStreamToObservable(nodeRequest)
|
|
7
7
|
|
|
8
8
|
let requestOrigin
|
|
9
|
-
if (nodeRequest.
|
|
9
|
+
if (nodeRequest.upgrade) {
|
|
10
|
+
requestOrigin = serverOrigin
|
|
11
|
+
} else if (nodeRequest.authority) {
|
|
10
12
|
requestOrigin = nodeRequest.connection.encrypted
|
|
11
13
|
? `https://${nodeRequest.authority}`
|
|
12
14
|
: `http://${nodeRequest.authority}`
|
|
@@ -62,6 +62,7 @@ export const createPolyglotServer = async ({
|
|
|
62
62
|
|
|
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
|
+
// https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions
|
|
65
66
|
const createSecureServer = async ({
|
|
66
67
|
certificate,
|
|
67
68
|
privateKey,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { networkInterfaces } from "node:os"
|
|
2
|
+
|
|
3
|
+
export const createIpGetters = () => {
|
|
4
|
+
const networkAddresses = []
|
|
5
|
+
const networkInterfaceMap = networkInterfaces()
|
|
6
|
+
for (const key of Object.keys(networkInterfaceMap)) {
|
|
7
|
+
for (const networkAddress of networkInterfaceMap[key]) {
|
|
8
|
+
networkAddresses.push(networkAddress)
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
getFirstInternalIp: ({ preferIpv6 }) => {
|
|
13
|
+
const isPref = preferIpv6 ? isIpV6 : isIpV4
|
|
14
|
+
let firstInternalIp
|
|
15
|
+
for (const networkAddress of networkAddresses) {
|
|
16
|
+
if (networkAddress.internal) {
|
|
17
|
+
firstInternalIp = networkAddress.address
|
|
18
|
+
if (isPref(networkAddress)) {
|
|
19
|
+
break
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return firstInternalIp
|
|
24
|
+
},
|
|
25
|
+
getFirstExternalIp: ({ preferIpv6 }) => {
|
|
26
|
+
const isPref = preferIpv6 ? isIpV6 : isIpV4
|
|
27
|
+
let firstExternalIp
|
|
28
|
+
for (const networkAddress of networkAddresses) {
|
|
29
|
+
if (!networkAddress.internal) {
|
|
30
|
+
firstExternalIp = networkAddress.address
|
|
31
|
+
if (isPref(networkAddress)) {
|
|
32
|
+
break
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return firstExternalIp
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const isIpV4 = (networkAddress) => {
|
|
42
|
+
// node 18.5
|
|
43
|
+
if (typeof networkAddress.family === "number") {
|
|
44
|
+
return networkAddress.family === 4
|
|
45
|
+
}
|
|
46
|
+
return networkAddress.family === "IPv4"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isIpV6 = (networkAddress) => !isIpV4(networkAddress)
|
package/src/main.js
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
* Ideally this file should be kept simple to help discovering codebase progressively.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export { startServer } from "./
|
|
8
|
-
export { composeServices } from "./service_composition/service_composition.js"
|
|
7
|
+
export { startServer } from "./start_server.js"
|
|
9
8
|
export { setupRoutes } from "./service_composition/routing.js"
|
|
10
9
|
export { readRequestBody } from "./readRequestBody.js"
|
|
11
10
|
export { fetchFileSystem } from "./fetch_filesystem.js"
|
|
@@ -18,31 +17,30 @@ export {
|
|
|
18
17
|
STOP_REASON_PROCESS_EXIT,
|
|
19
18
|
STOP_REASON_NOT_SPECIFIED,
|
|
20
19
|
} from "./stopReasons.js"
|
|
20
|
+
export { jsenvServiceErrorHandler } from "./services/error_handler/jsenv_service_error_handler.js"
|
|
21
21
|
|
|
22
22
|
// CORS
|
|
23
23
|
export {
|
|
24
|
-
|
|
24
|
+
jsenvServiceCORS,
|
|
25
25
|
jsenvAccessControlAllowedHeaders,
|
|
26
26
|
jsenvAccessControlAllowedMethods,
|
|
27
|
-
} from "./cors/
|
|
27
|
+
} from "./services/cors/jsenv_service_cors.js"
|
|
28
28
|
|
|
29
|
-
// server
|
|
30
|
-
export { pluginServerTiming } from "./server_timing/plugin_server_timing.js"
|
|
29
|
+
// server timing
|
|
31
30
|
export { timeFunction, timeStart } from "./server_timing/timing_measure.js"
|
|
32
31
|
|
|
33
32
|
// SSE
|
|
34
|
-
export { createSSERoom } from "./sse/
|
|
33
|
+
export { createSSERoom } from "./sse/sse_room.js"
|
|
35
34
|
|
|
36
35
|
// content-negotiation
|
|
37
|
-
export {
|
|
38
|
-
export {
|
|
39
|
-
export {
|
|
40
|
-
export {
|
|
36
|
+
export { pickContentType } from "./content_negotiation/pick_content_type.js"
|
|
37
|
+
export { pickContentEncoding } from "./content_negotiation/pick_content_encoding.js"
|
|
38
|
+
export { pickContentLanguage } from "./content_negotiation/pick_content_language.js"
|
|
39
|
+
export { jsenvServiceResponseAcceptanceCheck } from "./services/response_acceptance_check/jsenv_service_response_acceptance_check.js"
|
|
41
40
|
|
|
42
41
|
// others
|
|
43
42
|
export { serveDirectory } from "./serve_directory.js"
|
|
44
43
|
export { fromFetchResponse } from "./from_fetch_response.js"
|
|
45
44
|
export { composeTwoResponses } from "./internal/response_composition.js"
|
|
46
|
-
export {
|
|
47
|
-
export { pluginRequestWaitingCheck } from "./plugin_request_waiting_check.js"
|
|
45
|
+
export { jsenvServiceRessourceAliases } from "./services/ressource_aliases/jsenv_service_ressource_aliases.js"
|
|
48
46
|
export { findFreePort } from "./internal/listen.js"
|
package/src/serve_directory.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readdirSync } from "node:fs"
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { pickContentType } from "./content_negotiation/pick_content_type.js"
|
|
4
4
|
|
|
5
5
|
export const serveDirectory = (
|
|
6
6
|
url,
|
|
@@ -57,7 +57,7 @@ export const serveDirectory = (
|
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
59
|
}
|
|
60
|
-
const bestContentType =
|
|
60
|
+
const bestContentType = pickContentType(
|
|
61
61
|
{ headers },
|
|
62
62
|
Object.keys(responseProducers),
|
|
63
63
|
)
|