@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 +1 -2
- package/package.json +1 -1
- package/src/fetch_filesystem.js +21 -7
- package/src/fetch_url.js +1 -2
- package/src/internal/getServerOrigins.js +18 -25
- package/src/internal/listen.js +0 -3
- package/src/serve_directory.js +1 -2
- package/src/startServer.js +36 -9
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
package/src/fetch_filesystem.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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:
|
|
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
|
-
|
|
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 {
|
package/src/serve_directory.js
CHANGED
|
@@ -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
|
|
7
|
+
{ headers = {}, rootDirectoryUrl } = {},
|
|
9
8
|
) => {
|
|
10
9
|
url = String(url)
|
|
11
10
|
url = url[url.length - 1] === "/" ? url : `${url}/`
|
package/src/startServer.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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 = (
|
|
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
|
-
|
|
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
|
-
|
|
738
|
-
|
|
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,
|