Default server host not compatibile with IPv6
Describe the bug
If you create a basic server and don't specify an address to bind to, the server will bind to 0.0.0.0. On an OS with both IPv4 and IPv6, this may mean it will reject requests to localhost.
To Reproduce
Steps to reproduce the behavior:
- install and run the basic example
- open http://localhost:3000 in a browser, see "Hello World" message.
- http://127.0.0.1:3000/ works too.
- open
http://[::1]:3000/(IPv6 loopback address) - fails
Expected behavior
You should see the same "Hello World" in all cases. A server without a specified host should listen on all available interfaces.
Workaround: change line 25 of index.js to this:
.listen(3000, () => console.log(`Listening on http://localhost:3000`), "::")
Versions
-
node: 17.6.0 -
@tinyhttp/app: 2.0.19
Additional context
I personally found this while trying to use tinyhttp to power the API backend for a Vite frontend, and doing local testing using Vite's built-in proxy. The frontend was running on http://localhost:3000 and the backend was using http://localhost:3001. Accessing both services through a browser was fine, and also running the whole stack inside of docker compose was OK too. But on my MacBook (macOS 12.2.1) using Node v17.6.0, attempts to proxy to the backend resulted in this error:
Error: connect ECONNREFUSED ::1:3001
at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1158:16)
...until I changed the app.listen line in my server to add the last argument with the value "::".
app.listen(PORT, () => console.log("Started on http://localhost:" + PORT), "::");
It turns out that a blank string "" also works. On my machine at least, IPv6 seems to have higher priority than IPv4. AFAICT, this behavior appears to have been changed in GH-86 to fix #85. According to the relevant Node documentation for server.listen:
If
hostis 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. In most operating systems, listening to the unspecified IPv6 address (::) may cause thenet.Serverto also listen on the unspecified IPv4 address (0.0.0.0).
This suggests that in addition to the workaround above (that is, using host value "::" instead of "0.0.0.0"), perhaps omitting the host by making the default host an empty string is the right call, and let Node sort it out.
The reasoning behind dual-stack interface binding is discussed further in nodejs/node#9390.
By the way, great library and documentation. Thanks for your work on this over the years.
would you like to submit a PR?
Hi! Is anybody working on it? Otherwise i could try to fix it if you don't mind