@jsenv/server 12.5.7 → 12.6.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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# server [](https://www.npmjs.com/package/@jsenv/server)
|
|
1
|
+
# server [](https://www.npmjs.com/package/@jsenv/server)
|
|
2
2
|
|
|
3
3
|
`@jsenv/server` helps to write flexible server code with a declarative API.
|
|
4
4
|
|
|
@@ -7,7 +7,6 @@ import { startServer } from "@jsenv/server"
|
|
|
7
7
|
|
|
8
8
|
await startServer({
|
|
9
9
|
protocol: "http",
|
|
10
|
-
ip: "127.0.0.1",
|
|
11
10
|
port: 8080,
|
|
12
11
|
requestToResponse: () => {
|
|
13
12
|
return {
|
package/package.json
CHANGED
|
@@ -11,22 +11,21 @@ const cyan = "\x1b[36m"
|
|
|
11
11
|
|
|
12
12
|
export const colorizeResponseStatus = (status) => {
|
|
13
13
|
const statusType = statusToType(status)
|
|
14
|
-
|
|
15
14
|
if (statusType === "information") return `${cyan}${status}${close}`
|
|
16
15
|
if (statusType === "success") return `${green}${status}${close}`
|
|
17
16
|
if (statusType === "redirection") return `${magenta}${status}${close}`
|
|
18
|
-
if (statusType === "
|
|
19
|
-
if (statusType === "
|
|
17
|
+
if (statusType === "client_error") return `${yellow}${status}${close}`
|
|
18
|
+
if (statusType === "server_error") return `${red}${status}${close}`
|
|
20
19
|
return status
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
|
24
|
-
const statusToType = (status) => {
|
|
23
|
+
export const statusToType = (status) => {
|
|
25
24
|
if (statusIsInformation(status)) return "information"
|
|
26
25
|
if (statusIsSuccess(status)) return "success"
|
|
27
26
|
if (statusIsRedirection(status)) return "redirection"
|
|
28
|
-
if (statusIsClientError(status)) return "
|
|
29
|
-
if (statusIsServerError(status)) return "
|
|
27
|
+
if (statusIsClientError(status)) return "client_error"
|
|
28
|
+
if (statusIsServerError(status)) return "server_error"
|
|
30
29
|
return "unknown"
|
|
31
30
|
}
|
|
32
31
|
|
|
@@ -2,43 +2,37 @@ import { networkInterfaces } from "os"
|
|
|
2
2
|
import { URL } from "url"
|
|
3
3
|
|
|
4
4
|
export const getServerOrigins = ({ protocol, ip, port }) => {
|
|
5
|
+
const isInternalIp = ip === "127.0.0.1"
|
|
6
|
+
const internalOrigin = createServerOrigin({
|
|
7
|
+
protocol,
|
|
8
|
+
hostname: "localhost",
|
|
9
|
+
port,
|
|
10
|
+
})
|
|
11
|
+
if (isInternalIp) {
|
|
12
|
+
return { internal: internalOrigin }
|
|
13
|
+
}
|
|
14
|
+
const isAnyIp = !ip || ip === "::" || ip === "0.0.0.0"
|
|
5
15
|
return {
|
|
6
|
-
|
|
7
|
-
external: createServerOrigin({
|
|
16
|
+
internal: internalOrigin,
|
|
17
|
+
external: createServerOrigin({
|
|
18
|
+
protocol,
|
|
19
|
+
hostname: isAnyIp ? getExternalIp(ip) : ip,
|
|
20
|
+
port,
|
|
21
|
+
}),
|
|
8
22
|
}
|
|
9
23
|
}
|
|
10
24
|
|
|
11
|
-
const createServerOrigin = ({ protocol,
|
|
25
|
+
const createServerOrigin = ({ protocol, hostname, port }) => {
|
|
12
26
|
const url = new URL("https://127.0.0.1:80")
|
|
13
|
-
|
|
14
27
|
url.protocol = protocol
|
|
15
|
-
url.hostname =
|
|
28
|
+
url.hostname = hostname
|
|
16
29
|
url.port = port
|
|
17
|
-
|
|
18
30
|
return url.origin
|
|
19
31
|
}
|
|
20
32
|
|
|
21
|
-
const ipToHostname = (
|
|
22
|
-
ip,
|
|
23
|
-
{
|
|
24
|
-
preferLocalhost = true,
|
|
25
|
-
preferLocalIp = false,
|
|
26
|
-
preferExternalIp = false,
|
|
27
|
-
} = {},
|
|
28
|
-
) => {
|
|
29
|
-
if (ip === "0.0.0.0" || !ip) {
|
|
30
|
-
if (preferLocalhost) return "localhost"
|
|
31
|
-
if (preferLocalIp) return "127.0.0.1"
|
|
32
|
-
if (preferExternalIp) return getExternalIp(ip) || "0.0.0.0"
|
|
33
|
-
return "0.0.0.0"
|
|
34
|
-
}
|
|
35
|
-
return ip
|
|
36
|
-
}
|
|
37
|
-
|
|
38
33
|
const getExternalIp = () => {
|
|
39
34
|
const networkInterfaceMap = networkInterfaces()
|
|
40
35
|
let internalIPV4NetworkAddress
|
|
41
|
-
|
|
42
36
|
Object.keys(networkInterfaceMap).find((key) => {
|
|
43
37
|
const networkAddressArray = networkInterfaceMap[key]
|
|
44
38
|
return networkAddressArray.find((networkAddress) => {
|
|
@@ -48,6 +42,5 @@ const getExternalIp = () => {
|
|
|
48
42
|
return true
|
|
49
43
|
})
|
|
50
44
|
})
|
|
51
|
-
|
|
52
45
|
return internalIPV4NetworkAddress ? internalIPV4NetworkAddress.address : null
|
|
53
46
|
}
|
package/src/internal/listen.js
CHANGED
|
@@ -20,7 +20,6 @@ const listen = async ({
|
|
|
20
20
|
ip,
|
|
21
21
|
})
|
|
22
22
|
}
|
|
23
|
-
|
|
24
23
|
listeningOperation.throwIfAborted()
|
|
25
24
|
port = await startListening({ server, port, ip })
|
|
26
25
|
listeningOperation.addAbortCallback(() => stopListening(server))
|
|
@@ -43,7 +42,6 @@ export const findFreePort = async (
|
|
|
43
42
|
} = {},
|
|
44
43
|
) => {
|
|
45
44
|
const findFreePortOperation = Abort.startOperation()
|
|
46
|
-
|
|
47
45
|
try {
|
|
48
46
|
findFreePortOperation.addAbortSignal(signal)
|
|
49
47
|
findFreePortOperation.throwIfAborted()
|
|
@@ -61,7 +59,6 @@ export const findFreePort = async (
|
|
|
61
59
|
}
|
|
62
60
|
return testUntil(nextPort, ip)
|
|
63
61
|
}
|
|
64
|
-
|
|
65
62
|
const freePort = await testUntil(initialPort, ip)
|
|
66
63
|
return freePort
|
|
67
64
|
} finally {
|
|
@@ -44,6 +44,34 @@ export const populateNodeResponse = async (
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
await new Promise((resolve) => {
|
|
47
|
+
const observable = bodyMethods.asObservable()
|
|
48
|
+
const subscription = observable.subscribe({
|
|
49
|
+
next: (data) => {
|
|
50
|
+
try {
|
|
51
|
+
responseStream.write(data)
|
|
52
|
+
} catch (e) {
|
|
53
|
+
// Something inside Node.js sometimes puts stream
|
|
54
|
+
// in a state where .write() throw despites nodeResponse.destroyed
|
|
55
|
+
// being undefined and "close" event not being emitted.
|
|
56
|
+
// I have tested if we are the one calling destroy
|
|
57
|
+
// (I have commented every .destroy() call)
|
|
58
|
+
// but issue still occurs
|
|
59
|
+
// For the record it's "hard" to reproduce but can be by running
|
|
60
|
+
// a lot of tests against a browser in the context of @jsenv/core testing
|
|
61
|
+
if (e.code === "ERR_HTTP2_INVALID_STREAM") {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
responseStream.emit("error", e)
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
error: (value) => {
|
|
68
|
+
responseStream.emit("error", value)
|
|
69
|
+
},
|
|
70
|
+
complete: () => {
|
|
71
|
+
responseStream.end()
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
|
|
47
75
|
raceCallbacks(
|
|
48
76
|
{
|
|
49
77
|
abort: (cb) => {
|
|
@@ -104,34 +132,6 @@ export const populateNodeResponse = async (
|
|
|
104
132
|
raceEffects[winner.name](winner.data)
|
|
105
133
|
},
|
|
106
134
|
)
|
|
107
|
-
|
|
108
|
-
const observable = bodyMethods.asObservable()
|
|
109
|
-
const subscription = observable.subscribe({
|
|
110
|
-
next: (data) => {
|
|
111
|
-
try {
|
|
112
|
-
responseStream.write(data)
|
|
113
|
-
} catch (e) {
|
|
114
|
-
// Something inside Node.js sometimes puts stream
|
|
115
|
-
// in a state where .write() throw despites nodeResponse.destroyed
|
|
116
|
-
// being undefined and "close" event not being emitted.
|
|
117
|
-
// I have tested if we are the one calling destroy
|
|
118
|
-
// (I have commented every .destroy() call)
|
|
119
|
-
// but issue still occurs
|
|
120
|
-
// For the record it's "hard" to reproduce but can be by running
|
|
121
|
-
// a lot of tests against a browser in the context of @jsenv/core testing
|
|
122
|
-
if (e.code === "ERR_HTTP2_INVALID_STREAM") {
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
responseStream.emit("error", e)
|
|
126
|
-
}
|
|
127
|
-
},
|
|
128
|
-
error: (value) => {
|
|
129
|
-
responseStream.emit("error", value)
|
|
130
|
-
},
|
|
131
|
-
complete: () => {
|
|
132
|
-
responseStream.end()
|
|
133
|
-
},
|
|
134
|
-
})
|
|
135
135
|
})
|
|
136
136
|
}
|
|
137
137
|
|
package/src/startServer.js
CHANGED
|
@@ -16,7 +16,10 @@ import {
|
|
|
16
16
|
applyRedirectionToRequest,
|
|
17
17
|
} from "./internal/request_factory.js"
|
|
18
18
|
import { populateNodeResponse } from "./internal/populateNodeResponse.js"
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
statusToType,
|
|
21
|
+
colorizeResponseStatus,
|
|
22
|
+
} from "./internal/colorizeResponseStatus.js"
|
|
20
23
|
import { getServerOrigins } from "./internal/getServerOrigins.js"
|
|
21
24
|
import { listen, stopListening } from "./internal/listen.js"
|
|
22
25
|
import { composeTwoResponses } from "./internal/response_composition.js"
|
|
@@ -37,6 +40,7 @@ import {
|
|
|
37
40
|
export const startServer = async ({
|
|
38
41
|
signal = new AbortController().signal,
|
|
39
42
|
logLevel,
|
|
43
|
+
startLog = true,
|
|
40
44
|
serverName = "server",
|
|
41
45
|
|
|
42
46
|
protocol = "http",
|
|
@@ -44,7 +48,8 @@ export const startServer = async ({
|
|
|
44
48
|
http1Allowed = true,
|
|
45
49
|
redirectHttpToHttps,
|
|
46
50
|
allowHttpRequestOnHttps = false,
|
|
47
|
-
|
|
51
|
+
listenAnyIp = false,
|
|
52
|
+
ip = listenAnyIp ? undefined : "127.0.0.1",
|
|
48
53
|
port = 0, // assign a random available port
|
|
49
54
|
portHint,
|
|
50
55
|
privateKey,
|
|
@@ -204,7 +209,7 @@ export const startServer = async ({
|
|
|
204
209
|
|
|
205
210
|
status = "opened"
|
|
206
211
|
const serverOrigins = getServerOrigins({ protocol, ip, port })
|
|
207
|
-
const serverOrigin = serverOrigins.
|
|
212
|
+
const serverOrigin = serverOrigins.internal
|
|
208
213
|
|
|
209
214
|
const removeConnectionErrorListener = listenServerConnectionError(
|
|
210
215
|
nodeServer,
|
|
@@ -676,8 +681,15 @@ export const startServer = async ({
|
|
|
676
681
|
onRequestHandled(requestNode)
|
|
677
682
|
},
|
|
678
683
|
onHeadersSent: ({ status, statusText }) => {
|
|
684
|
+
const statusType = statusToType(status)
|
|
679
685
|
addRequestLog(requestNode, {
|
|
680
|
-
type:
|
|
686
|
+
type: {
|
|
687
|
+
information: "info",
|
|
688
|
+
success: "info",
|
|
689
|
+
redirection: "info",
|
|
690
|
+
client_error: "warn",
|
|
691
|
+
server_error: "error",
|
|
692
|
+
}[statusType],
|
|
681
693
|
value: `${colorizeResponseStatus(status)} ${
|
|
682
694
|
responseProperties.statusMessage || statusText
|
|
683
695
|
}`,
|
|
@@ -724,9 +736,11 @@ export const startServer = async ({
|
|
|
724
736
|
// ensure we don't try to handle new requests while server is stopping
|
|
725
737
|
stopCallbackList.add(removeRequestListener)
|
|
726
738
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
739
|
+
if (startLog) {
|
|
740
|
+
logger.info(
|
|
741
|
+
`${serverName} started at ${serverOrigin} (${serverOrigins.external})`,
|
|
742
|
+
)
|
|
743
|
+
}
|
|
730
744
|
|
|
731
745
|
const addEffect = (callback) => {
|
|
732
746
|
const cleanup = callback()
|
|
@@ -738,6 +752,7 @@ export const startServer = async ({
|
|
|
738
752
|
return {
|
|
739
753
|
getStatus: () => status,
|
|
740
754
|
origin: serverOrigin,
|
|
755
|
+
origins: serverOrigins,
|
|
741
756
|
nodeServer,
|
|
742
757
|
stop,
|
|
743
758
|
stoppedPromise,
|