tinyhttp icon indicating copy to clipboard operation
tinyhttp copied to clipboard

Default server host not compatibile with IPv6

Open Barnabas opened this issue 4 years ago • 2 comments

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:

  1. install and run the basic example
  2. open http://localhost:3000 in a browser, see "Hello World" message.
  3. http://127.0.0.1:3000/ works too.
  4. 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 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. 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).

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.

Barnabas avatar Mar 16 '22 20:03 Barnabas

would you like to submit a PR?

v1rtl avatar Mar 23 '22 19:03 v1rtl

Hi! Is anybody working on it? Otherwise i could try to fix it if you don't mind

mohammedzeglam-pg avatar Mar 24 '22 14:03 mohammedzeglam-pg