5

I am running node.js as follows:

> http = require('http')
> http.get('http://myhost.local:8080',
    function (res) { console.log("RES" + res) }
  ).on('error', function (e) { console.log("Error:", e) })

> uri = require('url').parse("http://myhost.local:8080")
{ protocol: 'http:',
  slashes: true,
  auth: null,
  host: 'myhost.local:8080',
  port: '8080',
  hostname: 'myhost.local',
  hash: null,
  search: null,
  query: null,
  pathname: '/',
  path: '/',
  href: 'http://myhost.local:8080/' }
> http.get(uri,
    function (res) { console.log("RES" + res) }
  ).on('error', function (e) { console.log("Error:", e) })

An error is thrown for both the implicit and explicitly parsed URI and I get the following output for both:

Error: { [Error: connect ECONNREFUSED] code: 'ECONNREFUSED', errno: 'ECONNREFUSED', syscall: 'connect' }

The host myhost.local is an alias for localhost in /etc/hosts, being:

127.0.0.1   localhost myhost.local myhost
255.255.255.255 broadcasthost
::1             localhost myhost.local myhost
fe80::1%lo0 localhost myhost.local myhost

EDIT: I tried virtually every permutation for the hosts file, including the most obvious:

127.0.0.1   localhost 
255.255.255.255 broadcasthost
::1             localhost myhost.local myhost
fe80::1%lo0 localhost

EDIT I should also mention that I have tried this on more than one Mac now.

Although it seems this is a rather common error, I have seen no useful explanations or workarounds. Here are some notable related facts:

  1. Running $ wget http://myhost.local:8080 works as expected, so it isn't a firewall problem.
  2. Running $ telnet myhost.local 8080 and then manually GET'ing the url works fine, so it's not a weird HTTP problem.
  3. I have no trouble using node.js to connect to other hosts e.g. http://www.google.com

I expect the useful system information would include:

$ node -v
v0.9.11

$ uname -a
Darwin myhost.local 12.2.1 Darwin Kernel Version 12.2.1:
Thu Oct 18 12:13:47 PDT 2012; root:xnu-2050.20.9~1/RELEASE_X86_64 x86_64

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.8.2
BuildVersion:   12C3104

$ sudo  netstat -nalt | grep LISTEN | grep 8080
tcp6       0      0  ::1.8080  *.*    LISTEN

Does anyone have any idea what is going on here, and what a fix might be?

Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343

3 Answers3

13

I'm going to post this here in case somebody else has the problem.

Bert Belder, Node.js mailing list:

On your system "myhost.local" resolves to three different addresses (127.0.0.1, ::1, and fe80::1). Node prefers ipv4 over ipv6 so it'll try to connect to 127.0.0.1. Nothing is listening on 127.0.0.1:8080 so the connect() syscall fails with ECONNREFUSED. Node doesn't retry with any of the other resolved IPs - it just reports the error to you. A simple solution would be to replace 'localhost' by the intended destination ip address, '::1'.

Whether this behavior is right is somewhat open for debate, but this is what causes it.

Bert

alessioalex
  • 62,577
  • 16
  • 155
  • 122
  • Thanks alessioalex. I am sorry, but I was remiss in mentioning that I tried every permutation of the `/etc/hosts` file, including a having just the `::1 localhost myhost.local`. To no avail. – Brian M. Hunt Mar 06 '13 at 12:06
  • I should note that the solution is to bind the server to IPv4, but that is of course the wrong solution. :) – Brian M. Hunt Mar 06 '13 at 12:15
  • "Node prefers ipv4 over ipv6" is definitely wrong behavior. See [RFC 6724](https://tools.ietf.org/html/rfc6724). – Michael Hampton Mar 09 '13 at 04:10
3

This stemmed from an issue with Node (though there are ways to work around it), as per the discussion on nodejs/Google Groups, as @alessioalex alluded in his answer. A useful comment per Bert Belder:

there should be a getaddrinfo wrapper that returns more that just the first result

For example,

> require('dns').lookup('myhost.local', console.log)
{ oncomplete: [Function: onanswer] }
> null '127.0.0.1' 4

This is the first of multiple getaddrinfo results passed to Node. It seems that nodejs only uses the first item of the getaddrinfo call. Bert and Ben Noordhuis agreed in the Groups discussion that there should be a way to return more than just the first result with the getaddrinfo wrapper.

Contrast python, which returns all results from getaddrinfo:

>>> import socket
>>> socket.getaddrinfo("myhost.local", 8080)
[(30, 2, 17, '', ('::1', 8080, 0, 0)),
 (30, 1, 6, '',  ('::1', 8080, 0, 0)),
 (2, 2, 17, '',  ('127.0.0.1', 8080)),
 (2, 1, 6, '',   ('127.0.0.1', 8080)),
 (30, 2, 17, '', ('fe80::1%lo0', 8080, 0, 1)),
 (30, 1, 6, '',  ('fe80::1%lo0', 8080, 0, 1))]
Brian M. Hunt
  • 81,008
  • 74
  • 230
  • 343
0

does this work?

var http = require('http');
var options = {
  host: 'myhost.local',
  port: 8080,
  path: '/'
};

http.get(options, function (res) { 
  console.log("RES" + res) 
}).on('error', function (e) { 
  console.log("Error:", e) 
});
Manuel van Rijn
  • 10,170
  • 1
  • 29
  • 52
  • Thanks Manuel. That is effectively the same as `require('url').parse("http://myhost.local:8080/")`, which is what `http.get` calls when it is passed a string. I have updated my question to reflect this for clarity. – Brian M. Hunt Mar 05 '13 at 15:22