@jsenv/server
Advanced tools
Comparing version 13.1.0 to 14.0.0
{ | ||
"name": "@jsenv/server", | ||
"version": "13.1.0", | ||
"version": "14.0.0", | ||
"description": "Write your Node.js server using pure functions", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -9,3 +9,3 @@ import { createServer } from "node:net" | ||
portHint, | ||
host, | ||
hostname, | ||
}) => { | ||
@@ -21,7 +21,7 @@ const listeningOperation = Abort.startOperation() | ||
signal: listeningOperation.signal, | ||
host, | ||
hostname, | ||
}) | ||
} | ||
listeningOperation.throwIfAborted() | ||
port = await startListening({ server, port, host }) | ||
port = await startListening({ server, port, hostname }) | ||
listeningOperation.addAbortCallback(() => stopListening(server)) | ||
@@ -40,3 +40,3 @@ listeningOperation.throwIfAborted() | ||
signal = new AbortController().signal, | ||
host = "127.0.0.1", | ||
hostname = "127.0.0.1", | ||
min = 1, | ||
@@ -62,8 +62,8 @@ max = 65534, | ||
throw new Error( | ||
`${host} has no available port between ${min} and ${max}`, | ||
`${hostname} has no available port between ${min} and ${max}`, | ||
) | ||
} | ||
return testUntil(nextPort, host) | ||
return testUntil(nextPort, hostname) | ||
} | ||
const freePort = await testUntil(initialPort, host) | ||
const freePort = await testUntil(initialPort, hostname) | ||
return freePort | ||
@@ -75,3 +75,3 @@ } finally { | ||
const portIsFree = async (port, host) => { | ||
const portIsFree = async (port, hostname) => { | ||
const server = createServer() | ||
@@ -83,3 +83,3 @@ | ||
port, | ||
host, | ||
hostname, | ||
}) | ||
@@ -100,3 +100,3 @@ } catch (error) { | ||
const startListening = ({ server, port, host }) => { | ||
const startListening = ({ server, port, hostname }) => { | ||
return new Promise((resolve, reject) => { | ||
@@ -109,3 +109,3 @@ server.on("error", reject) | ||
}) | ||
server.listen(port, host) | ||
server.listen(port, hostname) | ||
}) | ||
@@ -112,0 +112,0 @@ } |
@@ -0,1 +1,2 @@ | ||
import { isIP } from "node:net" | ||
import http from "node:http" | ||
@@ -26,3 +27,2 @@ import cluster from "node:cluster" | ||
} from "./internal/colorizeResponseStatus.js" | ||
import { getServerOrigins } from "./internal/getServerOrigins.js" | ||
import { listen, stopListening } from "./internal/listen.js" | ||
@@ -44,2 +44,6 @@ import { composeTwoResponses } from "./internal/response_composition.js" | ||
import { createIpGetters } from "./internal/server_ips.js" | ||
import { parseHostname } from "./internal/hostname_parser.js" | ||
import { applyDnsResolution } from "./internal/dns_resolution.js" | ||
export const startServer = async ({ | ||
@@ -57,3 +61,4 @@ signal = new AbortController().signal, | ||
acceptAnyIp = false, | ||
host = acceptAnyIp ? undefined : "localhost", | ||
preferIpv6, | ||
hostname = "localhost", | ||
port = 0, // assign a random available port | ||
@@ -89,2 +94,4 @@ portHint, | ||
} = {}) => { | ||
const logger = createLogger({ logLevel }) | ||
if (protocol !== "http" && protocol !== "https") { | ||
@@ -105,3 +112,2 @@ throw new Error(`protocol must be http or https, got ${protocol}`) | ||
const logger = createLogger({ logLevel }) | ||
if ( | ||
@@ -145,2 +151,5 @@ redirectHttpToHttps === undefined && | ||
const stopCallbackList = createCallbackListNotifiedOnce() | ||
const serverOrigins = { | ||
local: "", // favors hostname when possible | ||
} | ||
@@ -172,2 +181,58 @@ try { | ||
const createOrigin = (hostname) => { | ||
if (isIP(hostname) === 6) { | ||
return `${protocol}://[${hostname}]` | ||
} | ||
return `${protocol}://${hostname}` | ||
} | ||
const ipGetters = createIpGetters() | ||
let hostnameToListen | ||
if (acceptAnyIp) { | ||
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 }) | ||
serverOrigins.local = createOrigin(firstInternalIp) | ||
serverOrigins.localip = createOrigin(firstInternalIp) | ||
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 }) | ||
serverOrigins.externalip = createOrigin(firstExternalIp) | ||
hostnameToListen = preferIpv6 ? "::" : "0.0.0.0" | ||
} else { | ||
hostnameToListen = hostname | ||
} | ||
const hostnameInfo = parseHostname(hostname) | ||
if (hostnameInfo.type === "ip") { | ||
if (acceptAnyIp) { | ||
throw new Error( | ||
`hostname cannot be an ip when acceptAnyIp is enabled, got ${hostname}`, | ||
) | ||
} | ||
preferIpv6 = hostnameInfo.version === 6 | ||
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 }) | ||
serverOrigins.local = createOrigin(firstInternalIp) | ||
serverOrigins.localip = createOrigin(firstInternalIp) | ||
if (hostnameInfo.label === "unspecified") { | ||
const firstExternalIp = ipGetters.getFirstExternalIp({ preferIpv6 }) | ||
serverOrigins.externalip = createOrigin(firstExternalIp) | ||
} else if (hostnameInfo.label === "loopback") { | ||
} else { | ||
serverOrigins.local = createOrigin(hostname) | ||
} | ||
} else { | ||
const hostnameDnsResolution = await applyDnsResolution(hostname, { | ||
verbatim: true, | ||
}) | ||
if (hostnameDnsResolution) { | ||
const hostnameIp = hostnameDnsResolution.address | ||
serverOrigins.localip = createOrigin(hostnameIp) | ||
serverOrigins.local = createOrigin(hostname) | ||
} else { | ||
const firstInternalIp = ipGetters.getFirstInternalIp({ preferIpv6 }) | ||
// fallback to internal ip because there is no ip | ||
// associated to this hostname on operating system (in hosts file) | ||
hostname = firstInternalIp | ||
hostnameToListen = firstInternalIp | ||
serverOrigins.local = createOrigin(firstInternalIp) | ||
} | ||
} | ||
port = await listen({ | ||
@@ -178,5 +243,10 @@ signal: startServerOperation.signal, | ||
portHint, | ||
host, | ||
hostname: hostnameToListen, | ||
}) | ||
// normalize origins (remove :80 when port is 80 for instance) | ||
Object.keys(serverOrigins).forEach((key) => { | ||
serverOrigins[key] = new URL(`${serverOrigins[key]}:${port}`).origin | ||
}) | ||
serviceController.callHooks("serverListening", { port }) | ||
@@ -191,2 +261,14 @@ startServerOperation.addAbortCallback(async () => { | ||
// the main server origin | ||
// - when protocol is http | ||
// node-fetch do not apply local dns resolution to map localhost back to 127.0.0.1 | ||
// despites localhost being mapped so we prefer to use the internal ip | ||
// (127.0.0.1) | ||
// - when protocol is https | ||
// using the hostname becomes important because the certificate is generated | ||
// for hostnames, not for ips | ||
// so we prefer https://locahost or https://local_hostname | ||
// over the ip | ||
const serverOrigin = serverOrigins.local | ||
// now the server is started (listening) it cannot be aborted anymore | ||
@@ -230,4 +312,2 @@ // (otherwise an AbortError is thrown to the code calling "startServer") | ||
status = "opened" | ||
const serverOrigins = await getServerOrigins({ protocol, host, port }) | ||
const serverOrigin = serverOrigins.local | ||
@@ -842,3 +922,5 @@ const removeConnectionErrorListener = listenServerConnectionError( | ||
const websocketOrigin = | ||
protocol === "https" ? `wss://${host}:${port}` : `ws://${host}:${port}` | ||
protocol === "https" | ||
? `wss://${hostname}:${port}` | ||
: `ws://${hostname}:${port}` | ||
server.websocketOrigin = websocketOrigin | ||
@@ -898,2 +980,3 @@ const upgradeCallback = (nodeRequest, socket, head) => { | ||
port, | ||
hostname, | ||
origin: serverOrigin, | ||
@@ -900,0 +983,0 @@ origins: serverOrigins, |
149115
53
4719