@jsenv/server 12.5.9 → 12.6.2

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.9",
3
+ "version": "12.6.2",
4
4
  "description": "Write your Node.js server using pure functions",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -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,
@@ -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}/`
@@ -40,6 +40,7 @@ import {
40
40
  export const startServer = async ({
41
41
  signal = new AbortController().signal,
42
42
  logLevel,
43
+ startLog = true,
43
44
  serverName = "server",
44
45
 
45
46
  protocol = "http",
@@ -47,7 +48,8 @@ export const startServer = async ({
47
48
  http1Allowed = true,
48
49
  redirectHttpToHttps,
49
50
  allowHttpRequestOnHttps = false,
50
- 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",
51
53
  port = 0, // assign a random available port
52
54
  portHint,
53
55
  privateKey,
@@ -207,7 +209,7 @@ export const startServer = async ({
207
209
 
208
210
  status = "opened"
209
211
  const serverOrigins = getServerOrigins({ protocol, ip, port })
210
- const serverOrigin = serverOrigins.main
212
+ const serverOrigin = serverOrigins.internal
211
213
 
212
214
  const removeConnectionErrorListener = listenServerConnectionError(
213
215
  nodeServer,
@@ -297,25 +299,47 @@ export const startServer = async ({
297
299
  const prefixLines = (string, prefix) => {
298
300
  return string.replace(/^(?!\s*$)/gm, prefix)
299
301
  }
300
- const writeLog = ({ type, value }, depth) => {
302
+ const writeLog = (
303
+ { type, value },
304
+ { someLogIsError, someLogIsWarn, depth },
305
+ ) => {
301
306
  if (depth > 0) {
302
307
  value = prefixLines(value, " ".repeat(depth))
303
308
  }
309
+ if (type === "info") {
310
+ if (someLogIsError) {
311
+ type = "error"
312
+ } else if (someLogIsWarn) {
313
+ type = "warn"
314
+ }
315
+ }
304
316
  logger[type](value)
305
317
  }
306
318
  const visitRequestNodeToLog = (requestNode, depth) => {
319
+ let someLogIsError = false
320
+ let someLogIsWarn = false
321
+ requestNode.logs.forEach((log) => {
322
+ if (log.type === "error") {
323
+ someLogIsError = true
324
+ }
325
+ if (log.type === "warn") {
326
+ someLogIsWarn = true
327
+ }
328
+ })
329
+
307
330
  const firstLog = requestNode.logs.shift()
308
331
  const lastLog = requestNode.logs.pop()
309
332
  const middleLogs = requestNode.logs
310
- writeLog(firstLog, depth)
333
+
334
+ writeLog(firstLog, { someLogIsError, someLogIsWarn, depth })
311
335
  middleLogs.forEach((log) => {
312
- writeLog(log, depth)
336
+ writeLog(log, { someLogIsError, someLogIsWarn, depth })
313
337
  })
314
338
  requestNode.children.forEach((child) => {
315
339
  visitRequestNodeToLog(child, depth + 1)
316
340
  })
317
341
  if (lastLog) {
318
- writeLog(lastLog, depth + 1)
342
+ writeLog(lastLog, { someLogIsError, someLogIsWarn, depth: depth + 1 })
319
343
  }
320
344
  }
321
345
  visitRequestNodeToLog(rootRequestNode, 0)
@@ -734,9 +758,11 @@ export const startServer = async ({
734
758
  // ensure we don't try to handle new requests while server is stopping
735
759
  stopCallbackList.add(removeRequestListener)
736
760
 
737
- logger.info(
738
- `${serverName} started at ${serverOrigin} (${serverOrigins.external})`,
739
- )
761
+ if (startLog) {
762
+ logger.info(
763
+ `${serverName} started at ${serverOrigin} (${serverOrigins.external})`,
764
+ )
765
+ }
740
766
 
741
767
  const addEffect = (callback) => {
742
768
  const cleanup = callback()
@@ -748,6 +774,7 @@ export const startServer = async ({
748
774
  return {
749
775
  getStatus: () => status,
750
776
  origin: serverOrigin,
777
+ origins: serverOrigins,
751
778
  nodeServer,
752
779
  stop,
753
780
  stoppedPromise,