Inexplicable node.js http throwing connect ECONNREFUSED (IPv6?)
Asked Answered
M

3

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?

Macarthur answered 5/3, 2013 at 15:6 Comment(9)
Do you get the same error if you use localhost instead of myhost.local?Lujan
Is something running on localhost:80 ?Nikko
@JohnnyHK, @Floby: The application being connected to is Google App Engine, and it is bound to myhost. Let me post my netstat output though - it may be illuminatingMacarthur
I was thinking that the get method wasn't parsing the port part correctly and was connecting to something else instead. Try as answered below.Nikko
Based on your netstat, your 8080 service is bound to the IPv6 localhost, not the IPv4 one. That may be the issue.Lujan
@Nikko Thanks, I had tried that as well, and have posted the results.Macarthur
@JohnnyHK: I think you are on to something ...Macarthur
What host name are you using in the program listening on port 8080? Is it server.listen(8080, "myhost.local") ?Melanimelania
@HectorCorrea: It's running Google App Engine's dev_appserver2, so I am not sure how it is binding to the address, but from the netstat output it seems to be binding on IPv6 ::1Macarthur
G
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

Grange answered 6/3, 2013 at 10:33 Comment(3)
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.Macarthur
I should note that the solution is to bind the server to IPv4, but that is of course the wrong solution. :)Macarthur
"Node prefers ipv4 over ipv6" is definitely wrong behavior. See RFC 6724.Feint
M
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))]
Macarthur answered 22/4, 2013 at 18:1 Comment(0)
C
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) 
});
Concertgoer answered 5/3, 2013 at 15:13 Comment(1)
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.Macarthur

© 2022 - 2024 — McMap. All rights reserved.