Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Golang HTTP Post error: connection refused

Tags:

post

go

I am trying to send a post request to localhost on port 8080 where a PHP app is running.

Curl work fine:

curl --data "key=asdf" http://localhost:8080/

But in Go I get the following error:

Post http://localhost:8080: dial tcp 127.0.0.1:8080: connection refused

Here is the code:

func submitForm(value string){
    resp, err := http.PostForm("http://localhost:8080", url.Values{"key": {value}})
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    _, err = ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println("status = ",resp.Status)
}
submitForm("asdf")

I monitored http traffic with httpry and i found out that no http request is generated but there are some packages:

4 packets received, 0 packets dropped, 0 http packets parsed

Some more facts:

  • My OS is Linux
  • I use the PHP's built-in web server. The servers was started with this command:

    php -S localhost:8080

like image 936
Jaxx Avatar asked Feb 06 '15 23:02

Jaxx


1 Answers

This issue appears to be resolved in Go 1.6. For reference, the original answer follows.


The problem here is that Go's net.Dialer only makes IPv4 connections by default (this appears to be a bug), but your PHP server is listening only on IPv6.

When you run php -S localhost:8080 it is doing the (mostly) right thing and binding to the IPv6 address ::1. It does not bind to IPv4.

This isn't a problem for most software, which knows to attempt IPv6 connections first, but what happens when you call http.PostForm() is that net.http uses its DefaultTransport, which by default uses a net.Dialer Dial() to make outgoing connections. Dial() attempts to resolve the address, and then we wind up deep in the resolver in src/net/ipsock.go, where we find out that the Go developers intentionally screwed it up while trying to work around something else:

        // We'll take any IP address, but since the dialing
        // code does not yet try multiple addresses
        // effectively, prefer to use an IPv4 address if
        // possible. This is especially relevant if localhost
        // resolves to [ipv6-localhost, ipv4-localhost]. Too
        // much code assumes localhost == ipv4-localhost.

This is obviously a problem, since IPv6 is supposed to be the default and preferred protocol. Linux and PHP are behaving correctly, while Go itself is not.

The comment above was found in the 1.4 branch. In the master branch this has been completely rewritten, but in the new code it's non-obvious whether an IPv6 or IPv4 address will be preferred. It may well be nondeterministic; I didn't spend too much time looking at it. Based on the issues I found on GitHub, (see below) it is unlikely to be actually fixed.

In the meantime you can work around this bug by either having Go connect to ::1 or having PHP bind to 127.0.0.1. You could also construct your own RoundTripper with correct behavior, but that's probably too much work, unless you're actually having problems reaching IPv6-enabled services (and eventually we all will, so this really needs to be fixed).

Some relevant Go issues include:

  • net: provide RFC 6724 address sorting functionality (the proper fix for this issue)
  • net: Dial only tries the first address
  • net: Dial should follow getaddrinfo address ordering (this is the issue you are having)
  • net: Dial does not try all resolved addresses (closely related to your issue)

And several other older issues that are no longer relevant since the code has long since been rewritten...

like image 67
Michael Hampton Avatar answered Nov 14 '22 19:11

Michael Hampton