http-proxy-middleware icon indicating copy to clipboard operation
http-proxy-middleware copied to clipboard

[Bug] not working at node 17

Open davidmeirlevy opened this issue 2 years ago • 24 comments

Checks

Describe the bug (be clear and concise)

use the library to proxy into another service. not working when upgraded to node 17.

Step-by-step reproduction instructions

1. using node 14.17 - works.
2. replace node 17.x - doesn't work.

console error:

[HPM] Error occurred while proxying request localhost:3000/aaa/ to http://localhost:3001/ [ECONNREFUSED] 


### Expected behavior (be clear and concise)

proxy should work and return the data from another proxied service

### How is http-proxy-middleware used in your project?

```shell
└── [email protected]

What http-proxy-middleware configuration are you using?

{
    target,
    changeOrigin: true,
    headers: {
      tenant,
    },
  }

What OS/version and node/version are you seeing the problem?

System:
    OS: macOS 12.1
    CPU: (12) x64 Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
    Memory: 492.53 MB / 16.00 GB
    Shell: 5.8 - /bin/zsh
  Binaries:
    Node: 17.4.0 - ~/.nvm/versions/node/v17.4.0/bin/node
    Yarn: 1.22.4 - /usr/local/bin/yarn
    npm: 8.3.1 - ~/.nvm/versions/node/v17.4.0/bin/npm
    Watchman: 2021.10.18.00 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.10.1 - /Users/XXX/.rvm/rubies/ruby-head/bin/pod
    Homebrew: 3.3.9 - /usr/local/bin/brew
    pip3: 21.2.4 - /usr/local/bin/pip3
    RubyGems: 2.7.6 - /Users/XXX/.rvm/rubies/ruby-head/bin/gem
  Utilities:
    CMake: 3.16.4 - /usr/local/bin/cmake
    Make: 3.81 - /usr/bin/make
    GCC: 4.2.1 - /usr/bin/gcc
    Git: 2.32.0 - /usr/bin/git
    Clang: 13.0.0 - /usr/bin/clang
  Servers:
    Apache: 2.4.51 - /usr/sbin/apachectl
  Virtualization:
    Docker: 20.10.12 - /usr/local/bin/docker
  SDKs:
    iOS SDK:
      Platforms: DriverKit 21.2, iOS 15.2, macOS 12.1, tvOS 15.2, watchOS 8.3
  IDEs:
    Nano: 2.0.6 - /usr/bin/nano
    Vim: 8.2 - /usr/bin/vim
    WebStorm: 2021.3.1
    Xcode: 13.2.1/13C100 - /usr/bin/xcodebuild
  Languages:
    Bash: 3.2.57 - /bin/bash
    Perl: 5.30.3 - /usr/bin/perl
    Python: 2.7.18 - /usr/bin/python
    Python3: 3.9.7 - /usr/local/bin/python3
    Ruby: 2.6.0 - /Users/XXX/.rvm/rubies/ruby-head/bin/ruby
  Databases:
    MongoDB: 4.2.1 - /usr/local/bin/mongo
    PostgreSQL: 14.0 - /usr/local/bin/postgres
    SQLite: 3.36.0 - /usr/bin/sqlite3
  Browsers:
    Chrome: 97.0.4692.99
    Firefox: 57.0.4
    Safari: 15.2

Additional context (optional)

No response

davidmeirlevy avatar Feb 02 '22 16:02 davidmeirlevy

BTW, working great on node 16.x

davidmeirlevy avatar Feb 02 '22 16:02 davidmeirlevy

This example works in node v17.4.0.

https://github.com/chimurai/http-proxy-middleware/blob/master/examples/express/index.js

Just clone the repo and run node examples/express from the project root folder.

Can you provide a minimal reproducible project with the error? Without one there is little to investigate.

chimurai avatar Feb 07 '22 18:02 chimurai

This could be caused by: https://github.com/nodejs/node/issues/40702. I don’t understand the DNS stuff though, so I’m not sure about anything.

This seems to reproduce the problem:

    app.use(
      "/api",
      proxy.createProxyMiddleware({
        target: "http://localhost:3000",
      })
    );

I think the reason the linked example works is because it doesn’t use localhost.

Note:

  • require("http").get("http://localhost:3000/", res => console.log(res.statusCode)) works on Node.js 16 but not 17 (ECONNREFUSED).
  • curl -i http://localhost:3000 works.

EDIT: It seems like if I run the official Node.js HTTP hello world example on port 3000, require("http").get("http://localhost:3000/", res => console.log(res.statusCode)) works just fine. However, when I run a Suave server I run into ECONNREFUSED. So there’s something about what the server is doing as well. I have no reproduction going here.

EDIT2: Figured it out. I was running the Suave server explicitly on 0.0.0.0, which is ipv4. Node.js uses :: by default.

https://nodejs.org/api/net.html#serverlistenport-host-backlog-callback

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).

When setting host to 0.0.0.0 I have a reproduction:

const http = require('http');

const requestListener = function (req, res) {
  res.writeHead(200);
  res.end('Hello, World!');
}

const server = http.createServer(requestListener);
server.listen(3000, '0.0.0.0');

Sorry, about the confusion – this is not my area of expertise.

lydell avatar Apr 14 '22 11:04 lydell

You shouldn't be using localhost but 127.0.0.1 instead. Reason is that name resolution for localhost varies a lot between different systems and implementations of their network stacks.

treysis avatar Apr 14 '22 17:04 treysis

You shouldn't be using localhost but 127.0.0.1 instead. Reason is that name resolution for localhost varies a lot between different systems and implementations of their network stacks.

Thank you, this was the issue for me on Node 17 and 18, while the pattern was working on previous node versions

krpeacock avatar Apr 19 '22 20:04 krpeacock

@treysis that's weird, isn't it? every domain is something that is made up to locate a target IP somewhere for someone. doesn't matter if it's localhost or google.com. it's either configured in my hosts file, at the ISP, or ISOC.

davidmeirlevy avatar Apr 20 '22 06:04 davidmeirlevy

@krpeacock Yes, that's why it was mentioned as a possibly breaking change and not introduced in 16, but in the short-term release 17, and also not backported to 16.

@davidmeirlevy Kind of. I haven't figured out what's the differenc when opening a listening socket and when opening a socket for connecting. However, even if querying localhost returns both IPv4 and IPv6, the host OS/libraries still have a say in ordering the results. E.g. if a host or even just a single interface doesn't have IPv6 aside from the default link-local address, the system might decide that returning ::1 is not feasible and will still give precedence to 127.0.0.1.

treysis avatar Apr 20 '22 12:04 treysis

Support for node17 will stop in june 2022... Not sure if it worth the effort to investigate the issue for node 17.

Node 18 just got released: https://nodejs.org/en/blog/announcements/v18-release-announce/

Do you have the same issue when you're using node 18?

chimurai avatar Apr 21 '22 22:04 chimurai

@chimurai I tested with Node.js 18.1.0 now, and yes, I have the same issue with Node.js 18.

lydell avatar May 14 '22 20:05 lydell

Looks like Node v17+ changed to way localhost is being looked-up:

  • https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V17.md#17.0.0
  • https://github.com/nodejs/node/issues/40537
  • https://github.com/nodejs/node/pull/39793
  • https://nodejs.org/docs/latest/api/cli.html#--dns-result-orderorder

Can you try replacing localhost with 127.0.0.1?


There is a --dns-result-order flag which you can use to force old behaviour:

--dns-result-order=ipv4first

https://www.redhat.com/en/blog/welcome-nodejs-18

For dns lookups, Node.js no longer prefers IPv4 over IPV6. Instead, it will now respect the order that is returned based on the dns entries. For properly configured hosts this should not make a difference but if you have a partially or incorrectly configured IPv6 stack on your hosts you might start seeing problems that were hidden before. The command line option --dns-result-order=ipv4first can be used to revert to the old behavior if necessary.

chimurai avatar May 15 '22 12:05 chimurai

Looks like Node v17+ changed to way localhost is being looked-up

Yes, partly. It just doesn't reorder the DNS results anymore. That's what we said all the time. The lookup works the same as before. Don't use the DNS flag as it will prevent IPv6-connectivity. Just instead of localhost use 127.0.0.1.

treysis avatar May 15 '22 12:05 treysis

@chimurai the way we're using http-proxy-middleware is through a node utility, so we can't dictate what domain/ip-address they use specifically. In the meantime we have this workaround/hack in place for users on Node 17+:

  /**
   * Workaround for http-proxy-middleware DNS lookup issue with Nodejs 17+
   * [See here for details]{@link https://github.com/chimurai/http-proxy-middleware/issues/705#issuecomment-1126925635}
   **/
  target: target.replace('http://localhost', 'http://127.0.0.1'),

Any plans to fix this internally in the http-proxy-middleware library?

raspy8766 avatar May 25 '22 23:05 raspy8766

@raspy8766 that's a neat solution unfortunately for me, it won't work since I have a case with multi-tenancy, and there's a difference in dev between loading localhost to 127.0.0.1 to 0.0.0.0 (the URLs in my system apply the relevant tenant to the system and act with different results).

but probably for most cases - your solution is excellent.

I think I will close this bug since it's actually a bug mostly in implementing dev environments with node, and not specifically for this middleware.

davidmeirlevy avatar May 26 '22 09:05 davidmeirlevy

Indeed not a bug in http-proxy-middleware but a configuration issue when upgrading to node 17/18.

chimurai avatar May 26 '22 09:05 chimurai

@chimurai Is it worth updating README.md (and examples) to use 127.0.0.1 instead of localhost? And/or mention that one should probably proxy to 127.0.0.1 instead of localhost etc? I’m thinking there are lots of people using this module during development, proxying to their API local server at localhost:1234. Could save some time for people, and avoid unnecessary issues being opened. 🤷‍♂️

Tricky stuff, it’s all about whether the server I proxy to accepts IPv4, IPv6 or both.

Here are some more details: https://github.com/nodejs/node/issues/40702#issuecomment-1103623246

lydell avatar May 26 '22 10:05 lydell

Definitely makes sense to update the documentation with a note to new the Node behaviour in node 17+. 👍

chimurai avatar May 26 '22 10:05 chimurai

@chimurai I created a PR for changing the docs accordingly: https://github.com/chimurai/http-proxy-middleware/pull/783

davidmeirlevy avatar May 26 '22 10:05 davidmeirlevy

for me, node 17 doesn't work because somehow the example code simply doesn't create the proxy from the right base url, it keeps using / to proxy for some reason. Using express, node 17, and used the example code provided.

The result is simply proxy didn't work. I guess I have to use another library, was expecting this work out of box but somehow it just doesn't work no matter what I do.

pencilcheck avatar Sep 14 '22 05:09 pencilcheck

This kind of bugs, have headache symptoms. 😄

theteladras avatar Dec 15 '22 10:12 theteladras

You shouldn't be using localhost but 127.0.0.1 instead. Reason is that name resolution for localhost varies a lot between different systems and implementations of their network stacks.

You can't combine IP with subdomain

Chukwu3meka avatar Feb 14 '23 00:02 Chukwu3meka

@Chukwu3meka But localhost isn't a subdomain or what are you referring to?

treysis avatar Feb 14 '23 11:02 treysis

@Chukwu3meka But localhost isn't a subdomain or what are you referring to?

Something like dev.127.0.0.1:3000 would not work, unlike dev.localhost:3000

Chukwu3meka avatar Feb 14 '23 18:02 Chukwu3meka

@Chukwu3meka I understand. Is there any reason for using subdomains of localhost?

treysis avatar Feb 14 '23 20:02 treysis

Please use the Discussions for project configuration issues. Thanks.

chimurai avatar Feb 14 '23 23:02 chimurai