diff --git a/lib/Server.js b/lib/Server.js index 82feaa6a41..82640fb8f0 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -388,10 +388,10 @@ class Server { /** * @param {string} gatewayOrFamily or family - * @param {boolean} [isInternal=false] ip should be internal + * @param {boolean} [isInternal] ip should be internal * @returns {string | undefined} */ - static findIp(gatewayOrFamily, isInternal = false) { + static findIp(gatewayOrFamily, isInternal) { if (gatewayOrFamily === "v4" || gatewayOrFamily === "v6") { let host; @@ -406,14 +406,21 @@ class Server { return false; } - if (network.internal !== isInternal) { + if ( + typeof isInternal !== "undefined" && + network.internal !== isInternal + ) { return false; } if (gatewayOrFamily === "v6") { const range = ipaddr.parse(network.address).range(); - if (range !== "ipv4Mapped" && range !== "uniqueLocal") { + if ( + range !== "ipv4Mapped" && + range !== "uniqueLocal" && + range !== "loopback" + ) { return false; } } @@ -458,7 +465,7 @@ class Server { * @returns {Promise} */ static async internalIP(family) { - return Server.findIp(family); + return Server.findIp(family, false); } // TODO remove me in the next major release, we have `findIp` @@ -467,7 +474,7 @@ class Server { * @returns {string | undefined} */ static internalIPSync(family) { - return Server.findIp(family); + return Server.findIp(family, false); } /** @@ -476,11 +483,13 @@ class Server { */ static async getHostname(hostname) { if (hostname === "local-ip") { - return Server.findIp("v4") || Server.findIp("v6") || "0.0.0.0"; + return ( + Server.findIp("v4", false) || Server.findIp("v6", false) || "0.0.0.0" + ); } else if (hostname === "local-ipv4") { - return Server.findIp("v4") || "0.0.0.0"; + return Server.findIp("v4", false) || "0.0.0.0"; } else if (hostname === "local-ipv6") { - return Server.findIp("v6") || "::"; + return Server.findIp("v6", false) || "::"; } return hostname; @@ -2829,14 +2838,15 @@ class Server { if (parsedIP.range() === "unspecified") { localhost = prettyPrintURL("localhost"); + loopbackIPv6 = prettyPrintURL("::1"); - const networkIPv4 = Server.findIp("v4"); + const networkIPv4 = Server.findIp("v4", false); if (networkIPv4) { networkUrlIPv4 = prettyPrintURL(networkIPv4); } - const networkIPv6 = Server.findIp("v6"); + const networkIPv6 = Server.findIp("v6", false); if (networkIPv6) { networkUrlIPv6 = prettyPrintURL(networkIPv6); diff --git a/test/cli/host-option.test.js b/test/cli/host-option.test.js index f2a0066bc0..7bac842e2c 100644 --- a/test/cli/host-option.test.js +++ b/test/cli/host-option.test.js @@ -4,8 +4,8 @@ const { testBin, normalizeStderr } = require("../helpers/test-bin"); const port = require("../ports-map")["cli-host"]; const Server = require("../../lib/Server"); -const localIPv4 = Server.findIp("v4"); -const localIPv6 = Server.findIp("v6"); +const localIPv4 = Server.findIp("v4", false); +const localIPv6 = Server.findIp("v6", false); describe('"host" CLI option', () => { it('should work using "--host 0.0.0.0" (IPv4)', async () => { diff --git a/test/e2e/host.test.js b/test/e2e/host.test.js index a1bb32c091..4e6c108647 100644 --- a/test/e2e/host.test.js +++ b/test/e2e/host.test.js @@ -1,28 +1,53 @@ "use strict"; +const http = require("http"); const webpack = require("webpack"); const Server = require("../../lib/Server"); const config = require("../fixtures/client-config/webpack.config"); const runBrowser = require("../helpers/run-browser"); const port = require("../ports-map").host; -const ipv4 = Server.findIp("v4"); -const ipv6 = Server.findIp("v6"); -// macos requires root for using ip v6 -const isMacOS = process.platform === "darwin"; +const ipv4 = Server.findIp("v4", false); +const ipv6 = Server.findIp("v6", false); -function getAddress(host, hostname) { +async function getAddress(host, hostname) { let address; if ( typeof host === "undefined" || - (typeof host === "string" && host === "") + (typeof host === "string" && (host === "" || host === "::")) ) { address = "::"; - } else if (typeof host === "string" && host === "0.0.0.0") { + } else if (host === "0.0.0.0") { address = "0.0.0.0"; - } else if (typeof host === "string" && host === "localhost") { - address = parseFloat(process.versions.node) >= 18 ? "::1" : "127.0.0.1"; + } else if (host === "::1") { + address = "::1"; + } else if (host === "localhost") { + // It can be `127.0.0.1` or `::1` on different OS + const server = http.createServer((req, res) => { + res.statusCode = 200; + res.setHeader("Content-Type", "text/plain"); + res.end("Hello World\n"); + }); + + await new Promise((resolve) => { + server.listen({ host: "localhost", port: 23100 }, resolve); + }); + + address = server.address().address; + + await new Promise((resolve, reject) => { + server.close((err) => { + if (err) { + reject(err); + return; + } + + resolve(); + }); + }); + } else if (host === "local-ipv6") { + address = "::"; } else { address = hostname; } @@ -37,28 +62,17 @@ describe("host", () => { undefined, "0.0.0.0", "::", - "localhost", "::1", + "localhost", "127.0.0.1", "local-ip", "local-ipv4", "local-ipv6", ]; - for (let host of hosts) { + for (const host of hosts) { it(`should work using "${host}" host and port as number`, async () => { const compiler = webpack(config); - - if (!ipv6 || isMacOS) { - if (host === "::") { - host = "127.0.0.1"; - } else if (host === "::1") { - host = "127.0.0.1"; - } else if (host === "local-ipv6") { - host = "127.0.0.1"; - } - } - const devServerOptions = { port }; if (host !== "") { @@ -69,24 +83,28 @@ describe("host", () => { let hostname = host; - if (hostname === "0.0.0.0") { - hostname = "127.0.0.1"; - } else if ( - hostname === "" || - typeof hostname === "undefined" || - hostname === "::" || - hostname === "::1" - ) { + if (hostname === "" || typeof hostname === "undefined") { + // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. + hostname = ipv6 ? `[${ipv6}]` : ipv4; + } else if (hostname === "0.0.0.0") { + hostname = ipv4; + } else if (hostname === "::") { + // In most operating systems, listening to the unspecified IPv6 address (::) may cause the net.Server to also listen on the unspecified IPv4 address (0.0.0.0). + hostname = ipv6 ? `[${ipv6}]` : ipv4; + } else if (hostname === "::1") { hostname = "[::1]"; } else if (hostname === "local-ip" || hostname === "local-ipv4") { hostname = ipv4; } else if (hostname === "local-ipv6") { - hostname = `[${ipv6}]`; + // For test env where network ipv6 doesn't work + hostname = ipv6 ? `[${ipv6}]` : "[::1]"; } await server.start(); - expect(server.server.address()).toMatchObject(getAddress(host, hostname)); + expect(server.server.address()).toMatchObject( + await getAddress(host, hostname), + ); const { page, browser } = await runBrowser(); @@ -121,17 +139,6 @@ describe("host", () => { it(`should work using "${host}" host and port as string`, async () => { const compiler = webpack(config); - - if (!ipv6 || isMacOS) { - if (host === "::") { - host = "127.0.0.1"; - } else if (host === "::1") { - host = "127.0.0.1"; - } else if (host === "local-ipv6") { - host = "127.0.0.1"; - } - } - const devServerOptions = { port: `${port}` }; if (host !== "") { @@ -142,24 +149,28 @@ describe("host", () => { let hostname = host; - if (hostname === "0.0.0.0") { - hostname = "127.0.0.1"; - } else if ( - hostname === "" || - typeof hostname === "undefined" || - hostname === "::" || - hostname === "::1" - ) { + if (hostname === "" || typeof hostname === "undefined") { + // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. + hostname = ipv6 ? `[${ipv6}]` : ipv4; + } else if (hostname === "0.0.0.0") { + hostname = ipv4; + } else if (hostname === "::") { + // In most operating systems, listening to the unspecified IPv6 address (::) may cause the net.Server to also listen on the unspecified IPv4 address (0.0.0.0). + hostname = ipv6 ? `[${ipv6}]` : ipv4; + } else if (hostname === "::1") { hostname = "[::1]"; } else if (hostname === "local-ip" || hostname === "local-ipv4") { hostname = ipv4; } else if (hostname === "local-ipv6") { - hostname = `[${ipv6}]`; + // For test env where network ipv6 doesn't work + hostname = ipv6 ? `[${ipv6}]` : "[::1]"; } await server.start(); - expect(server.server.address()).toMatchObject(getAddress(host, hostname)); + expect(server.server.address()).toMatchObject( + await getAddress(host, hostname), + ); const { page, browser } = await runBrowser(); @@ -197,16 +208,6 @@ describe("host", () => { process.env.WEBPACK_DEV_SERVER_BASE_PORT = port; - if (!ipv6 || isMacOS) { - if (host === "::") { - host = "127.0.0.1"; - } else if (host === "::1") { - host = "127.0.0.1"; - } else if (host === "local-ipv6") { - host = "127.0.0.1"; - } - } - const devServerOptions = { port: "auto" }; if (host !== "") { @@ -217,24 +218,28 @@ describe("host", () => { let hostname = host; - if (hostname === "0.0.0.0") { - hostname = "127.0.0.1"; - } else if ( - hostname === "" || - typeof hostname === "undefined" || - hostname === "::" || - hostname === "::1" - ) { + if (hostname === "" || typeof hostname === "undefined") { + // If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. + hostname = ipv6 ? `[${ipv6}]` : ipv4; + } else if (hostname === "0.0.0.0") { + hostname = ipv4; + } else if (hostname === "::") { + // In most operating systems, listening to the unspecified IPv6 address (::) may cause the net.Server to also listen on the unspecified IPv4 address (0.0.0.0). + hostname = ipv6 ? `[${ipv6}]` : ipv4; + } else if (hostname === "::1") { hostname = "[::1]"; } else if (hostname === "local-ip" || hostname === "local-ipv4") { hostname = ipv4; } else if (hostname === "local-ipv6") { - hostname = `[${ipv6}]`; + // For test env where network ipv6 doesn't work + hostname = ipv6 ? `[${ipv6}]` : "[::1]"; } await server.start(); - expect(server.server.address()).toMatchObject(getAddress(host, hostname)); + expect(server.server.address()).toMatchObject( + await getAddress(host, hostname), + ); const address = server.server.address(); const { page, browser } = await runBrowser(); diff --git a/types/lib/Server.d.ts b/types/lib/Server.d.ts index 05e3aec532..4c882938ad 100644 --- a/types/lib/Server.d.ts +++ b/types/lib/Server.d.ts @@ -1126,7 +1126,7 @@ declare class Server< static isAbsoluteURL(URL: string): boolean; /** * @param {string} gatewayOrFamily or family - * @param {boolean} [isInternal=false] ip should be internal + * @param {boolean} [isInternal] ip should be internal * @returns {string | undefined} */ static findIp(