@jsenv/server 12.5.8 → 12.6.1

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.8",
3
+ "version": "12.6.1",
4
4
  "description": "Write your Node.js server using pure functions",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -16,8 +16,7 @@
16
16
  "node": ">=16.13.0"
17
17
  },
18
18
  "publishConfig": {
19
- "access": "public",
20
- "registry": "https://registry.npmjs.org"
19
+ "access": "public"
21
20
  },
22
21
  "type": "module",
23
22
  "exports": {
@@ -68,4 +67,4 @@
68
67
  "playwright": "1.18.1",
69
68
  "prettier": "2.5.1"
70
69
  }
71
- }
70
+ }
@@ -39,20 +39,37 @@ export const fetchFileSystem = async (
39
39
  ? "private,max-age=0,must-revalidate"
40
40
  : "no-store",
41
41
  canReadDirectory = false,
42
- rootDirectoryUrl,
42
+ rootDirectoryUrl, // = `${pathToFileURL(process.cwd())}/`,
43
43
  } = {},
44
44
  ) => {
45
45
  const urlString = asUrlString(filesystemUrl)
46
46
  if (!urlString) {
47
- return send500DueToFilesystemUrlParameter(
47
+ return create500Response(
48
48
  `fetchFileSystem first parameter must be a file url, got ${filesystemUrl}`,
49
49
  )
50
50
  }
51
51
  if (!urlString.startsWith("file://")) {
52
- return send500DueToFilesystemUrlParameter(
52
+ return create500Response(
53
53
  `fetchFileSystem url must use "file://" scheme, got ${filesystemUrl}`,
54
54
  )
55
55
  }
56
+ if (rootDirectoryUrl) {
57
+ let rootDirectoryUrlString = asUrlString(rootDirectoryUrl)
58
+ if (!rootDirectoryUrlString) {
59
+ return create500Response(
60
+ `rootDirectoryUrl must be a string or an url, got ${rootDirectoryUrl}`,
61
+ )
62
+ }
63
+ if (!rootDirectoryUrlString.endsWith("/")) {
64
+ rootDirectoryUrlString = `${rootDirectoryUrlString}/`
65
+ }
66
+ if (!urlString.startsWith(rootDirectoryUrlString)) {
67
+ return create500Response(
68
+ `fetchFileSystem url must be inside root directory, got ${urlString}`,
69
+ )
70
+ }
71
+ rootDirectoryUrl = rootDirectoryUrlString
72
+ }
56
73
 
57
74
  // here you might be tempted to add || cacheControl === 'no-cache'
58
75
  // but no-cache means ressource can be cache but must be revalidated (yeah naming is strange)
@@ -180,7 +197,7 @@ export const fetchFileSystem = async (
180
197
  }
181
198
  }
182
199
 
183
- const send500DueToFilesystemUrlParameter = (message) => {
200
+ const create500Response = (message) => {
184
201
  return {
185
202
  status: 500,
186
203
  headers: {
@@ -419,12 +436,10 @@ const asUrlString = (value) => {
419
436
  if (value instanceof URL) {
420
437
  return value.href
421
438
  }
422
-
423
439
  if (typeof value === "string") {
424
440
  if (isFileSystemPath(value)) {
425
441
  return fileSystemPathToUrl(value)
426
442
  }
427
-
428
443
  try {
429
444
  const urlObject = new URL(value)
430
445
  return String(urlObject)
@@ -432,6 +447,5 @@ const asUrlString = (value) => {
432
447
  return null
433
448
  }
434
449
  }
435
-
436
450
  return null
437
451
  }
package/src/fetch_url.js CHANGED
@@ -28,12 +28,11 @@ export const fetchUrl = async (
28
28
  }
29
29
 
30
30
  if (url.startsWith("file://")) {
31
- const origin = new URL(url).origin
32
31
  const responseProperties = await fetchFileSystem(url, {
33
32
  signal,
34
33
  method,
35
34
  headers,
36
- rootDirectoryUrl: origin,
35
+ rootDirectoryUrl: "file://",
37
36
  cacheStrategy,
38
37
  canReadDirectory,
39
38
  contentTypeMap,
@@ -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 {
@@ -1,11 +1,10 @@
1
1
  import { readdirSync } from "node:fs"
2
- import { pathToFileURL } from "node:url"
3
2
 
4
3
  import { negotiateContentType } from "./content_negotiation/negotiateContentType.js"
5
4
 
6
5
  export const serveDirectory = (
7
6
  url,
8
- { headers = {}, rootDirectoryUrl = `${pathToFileURL(process.cwd())}/` } = {},
7
+ { headers = {}, rootDirectoryUrl } = {},
9
8
  ) => {
10
9
  url = String(url)
11
10
  url = url[url.length - 1] === "/" ? url : `${url}/`
@@ -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,