@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 [![npm package](https://img.shields.io/npm/v/@jsenv/server.svg?logo=npm&label=package)](https://www.npmjs.com/package/@jsenv/server) [![github workflow](https://github.com/jsenv/server/workflows/main/badge.svg)](https://github.com/jsenv/server/actions?workflow=main) [![codecov coverage](https://codecov.io/gh/jsenv/server/branch/main/graph/badge.svg)](https://codecov.io/gh/jsenv/server)
1
+ # server [![npm package](https://img.shields.io/npm/v/@jsenv/server.svg?logo=npm&label=package)](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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/server",
3
- "version": "12.5.7",
3
+ "version": "12.6.0",
4
4
  "description": "Write your Node.js server using pure functions",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -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 === "client-error") return `${yellow}${status}${close}`
19
- if (statusType === "server-error") return `${red}${status}${close}`
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 "client-error"
29
- if (statusIsServerError(status)) return "server-error"
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
- main: createServerOrigin({ protocol, ip, port }),
7
- external: createServerOrigin({ protocol, ip: getExternalIp(ip), port }),
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, ip, port }) => {
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 = ipToHostname(ip)
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
  }
@@ -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
 
@@ -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 { colorizeResponseStatus } from "./internal/colorizeResponseStatus.js"
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
- ip = "0.0.0.0", // will it work on windows ? https://github.com/nodejs/node/issues/14900
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.main
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: "info",
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
- logger.info(
728
- `${serverName} started at ${serverOrigin} (${serverOrigins.external})`,
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,