Golang HTTP Post 错误:连接被拒绝

huangapple go评论83阅读模式
英文:

Golang HTTP Post error: connection refused

问题

我正在尝试向运行在本地主机端口8080上的PHP应用程序发送POST请求。

使用Curl命令可以正常工作:

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

但是在Go语言中,我遇到了以下错误:

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

以下是代码:

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

我使用httpry监视了HTTP流量,并发现没有生成任何HTTP请求,但是有一些数据包:

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

一些其他事实:

  • 我的操作系统是Linux

  • 我使用PHP的内置Web服务器。服务器是使用以下命令启动的:

    php -S localhost:8080

英文:

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

答案1

得分: 10

这个问题在Go 1.6中似乎已经解决。原始答案如下:


问题在于Go的net.Dialer默认只建立IPv4连接(这似乎是一个bug),但是你的PHP服务器只监听IPv6。

当你运行php -S localhost:8080时,它会绑定到IPv6地址::1。它不会绑定到IPv4。

对于大多数软件来说,这不是一个问题,因为它们会首先尝试IPv6连接,但是当你调用http.PostForm()时,net.http使用它的DefaultTransport,默认使用net.Dialer的Dial()来建立出站连接。Dial()会尝试解析地址,然后我们会深入到src/net/ipsock.go中的解析器中,我们发现Go开发人员在尝试解决其他问题时故意搞砸了它:

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

显然,这是一个问题,因为IPv6应该是默认和首选的协议。Linux和PHP的行为是正确的,而Go本身则不是。

上面的注释是在1.4分支中找到的。在主分支中,这已经完全重写了,但在新代码中,无法确定是首选IPv6还是IPv4地址。它可能是不确定的;我没有花太多时间去研究它。根据我在GitHub上找到的问题(见下文),它可能不会被真正修复。

与此同时,你可以通过让Go连接到::1或让PHP绑定到127.0.0.1来解决这个bug。你也可以构建自己的RoundTripper来实现正确的行为,但这可能太麻烦了,除非你真的遇到了无法访问启用了IPv6的服务的问题(而最终我们都会遇到,所以这个问题真的需要修复)。

一些相关的Go问题包括:

还有一些其他旧问题,但由于代码早已重写,所以不再相关...

英文:

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:

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

huangapple
  • 本文由 发表于 2015年2月7日 07:16:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/28376472.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定