使用Golang进行多次连续请求时,HTTP请求会出现EOF错误。

62

我正在尝试调试一个我编写的简单REST库出现的非常不寻常的错误。

我使用标准的net/http包来进行Get、Post、Put、Delete请求,但是当我连续发起多个请求时,我的测试有时会失败。我的测试代码如下:

func TestGetObject(t *testing.T) {
    firebaseRoot := New(firebase_url)
    body, err := firebaseRoot.Get("1")
    if err != nil {
        t.Errorf("Error: %s", err)
    }
    t.Logf("%q", body)
}  

func TestPushObject(t *testing.T) {
    firebaseRoot := New(firebase_url)
    msg := Message{"testing", "1..2..3"}
    body, err := firebaseRoot.Push("/", msg)
    if err != nil {
        t.Errorf("Error: %s", err)
    }
    t.Logf("%q", body)
}

我这样发送请求:

// Send HTTP Request, return data
func (f *firebaseRoot) SendRequest(method string, path string, body io.Reader) ([]byte, error) {
url := f.BuildURL(path)

// create a request
req, err := http.NewRequest(method, url, body)
if err != nil {
    return nil, err
}

// send JSON to firebase
resp, err := http.DefaultClient.Do(req)
if err != nil {
    return nil, err
}

if resp.StatusCode != http.StatusOK {
    return nil, fmt.Errorf("Bad HTTP Response: %v", resp.Status)
}

defer resp.Body.Close()
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
    return nil, err
}

return b, nil
} 

有时它能正常工作,但大多数情况下我会遇到1或2次失败:
--- FAIL: TestGetObject (0.00 seconds)
firebase_test.go:53: Error: Get https://go-firebase-test.firebaseio.com/1.json: EOF
firebase_test.go:55: ""

--- FAIL: TestPushObject (0.00 seconds)
firebase_test.go:63: Error: Post https://go-firebase-test.firebaseio.com/.json: EOF
firebase_test.go:65: ""
FAIL
exit status 1
FAIL    github.com/chourobin/go.firebase    3.422s

当我发送超过1个请求时,会发生故障。如果我注释掉除PUT请求以外的所有内容,则测试始终通过。一旦我包括第二个测试,例如GET请求,其中一个或两个测试就会失败(有时两个都会通过)。

请展示完整的代码。 - Volker
1
你所访问的服务器返回了什么信息?也许是因为速率限制等原因导致连接被中断了?你收到EOF错误的最可能原因是服务器关闭了连接。实际上,在单元测试中为什么要访问外部服务器呢? - Jeremy Wall
@JeremyWall 他没有提到单元测试。对于他的测试来说,向服务器发送请求可能是合适的。 - Alexei Sholik
1
在 Go 项目中,test.go 文件 90% 的时间都是单元测试,实际上,他的代码使用的 go test 库大部分都是假设单元测试。如果它们是集成测试,那么它们可能不应该在那个位置。 - Jeremy Wall
@JeremyWall 在测试中访问外部服务器有点不可靠(可以说是链条上的太多链接),但这并不罕见。net/http 包的一些测试会访问外部 URL 作为测试。 - Intermernet
显示剩余3条评论
7个回答

85

我亲身经历过这件事。你需要将Req.Close设置为true(例子中使用的在resp.Body.Close()上的延迟语法是不够的)。像这样:

我可靠地经历了这个问题。您需要将Req.Close设置为true(在示例中使用的在resp.Body.Close()上的延迟语法不足以解决问题)。就像这样:

client := &http.Client{}
req, err := http.NewRequest(method, url, httpBody)

// NOTE this !!
req.Close = true

req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("user", "pass")
resp, err := client.Do(req)
if err != nil {
    // whatever
}
defer resp.Body.Close()

response, err = ioutil.ReadAll(resp.Body)
if err != nil {
    // Whatever
}

7
来自Go文档的一条提示:request.Close [布尔类型] 表示是否在响应这个请求后关闭连接(对于服务器)或者在发送请求后关闭连接(对于客户端)。 - jsherer
这个 bug 应该在 Go 1.6 中修复,详情请见 https://go-review.googlesource.com/#/c/3210/ - petrkotek
我可以确认这一点。我的应用程序也在进行大量的连续请求,并且偶尔会遇到这个错误。升级到Go 1.6解决了我的问题。 - Nathan Osman
19
即使将req.Close设置为true,我仍然遇到了这个错误。有什么想法吗?这是我的Go版本:go version go1.8 darwin/amd64。 - Patrick
@Patrick,你解决了你的问题吗?上面的解决方案在我使用go 1.16进行POST请求时没有起作用。 - thedarklord47
显示剩余4条评论

43
我同意这种说法:在单元测试中不应该访问外部服务器,而是应该使用内置的http.Server并提供要测试的内容(实际上有httptest包可以帮助此类情况)。
我最近在尝试爬取网站地图时遇到了同样的问题,以下是我目前找到的解决方法:
默认情况下,Go会发送带有头文件“Connection: Keep-Alive”的请求,并持久连接以重复使用。我遇到的问题是服务器在响应头中发送了“Connection: Keep-Alive”,然后立即关闭了连接。
在这种情况下,Go实现连接的方式是有两个goroutine,一个负责写入,另一个负责读取(readLoop和writeLoop)。在大多数情况下,readLoop将检测到套接字上的关闭,并关闭连接。在这里出现问题的原因是,当您在readLoop实际检测到关闭之前启动另一个请求时,它读取的EOF被解释为新请求的错误,而不是先前发生的关闭。
因此,在请求之间暂停工作的原因是它给readLoop时间来检测连接上的关闭,然后关闭它,以便您的新请求将启动新连接。req.Close = true的解决方案有效的原因是它防止连接被重复使用。
与此情况相关的问题有一个票据:https://code.google.com/p/go/issues/detail?id=4677(我创建了一个重复的票据,用于可靠地重现:https://code.google.com/p/go/issues/detail?id=8122

我正在使用httptest包,但我的测试仍然存在问题。然后我向上滚动输出,找到了运行时恐慌,导致我的测试服务器无法关闭连接。 - Omn
1
这很有道理。在我发送的每个第二个请求上都会出现EOF错误,这让我疯狂,因为如果我在Python CLI中执行相同的请求系列,一切都能正常工作,所以我知道这是Go特定的问题。我只是担心是目标服务器在每个第二个请求上执行了某些操作,而这些操作会特别影响Go的http实现。 - jeteon
@cgilling 你好,运行以下代码 https://play.golang.org/p/hxuIOC49ac,但是所有请求都返回成功,而不是“四分之一到一半的请求有错误”,你有什么想法吗? - DiveInto
@DiveInto 哇,我刚看到这个评论,非常抱歉回复晚了。我实际上没有进一步调查,但我可以想到两种可能性,按可能性排序:(1)adobe.com支持保持连接并且在未事先通知您的情况下不会关闭连接(2)go http代码已更改(我认为(1)是远远最有可能的答案)<-哈哈,读一些下面的评论,看起来我在可能性排序方面错了,这个错误已经在go 1.6中得到修复 :) - cgilling

24

我猜测你的代码没有问题。导致问题最有可能的原因是服务器关闭了连接,其中限速可能是其中一个原因。

你的测试不应该依赖于一个非常脆弱且非自给自足的外部服务。相反,你应该考虑在本地启动一个测试服务器。


1
谢谢,我在测试之间加了一个延时,看起来它又可以工作了。速率限制可能是原因。 - chourobin
18
这个答案是错误的(而且传达了错误的理由)。@Alex Davies的答案是正确的。 - mwag

3

我正在开发一个图片下载应用程序时遇到了这个问题。
尝试了request.Close=true但没有起作用。
60%的请求导致EOF错误。

我认为可能是图片服务器的问题,而不是我的代码。
但PHP代码运行良好。

然后我使用了

var client = &http.Client{
    Transport: &http.Transport{},
}
client.Do(request)

发起请求时,不要使用

http.DefaultClient.Do(request)

问题解决了。 不确定为什么,我猜是与RoundTripper有关。


2

我遇到这个错误的经历是当我为我的JSON API输入绝对空的输入时!

应该发送{}作为空的JSON,但我发送了 ,因此出现了这个错误。


0

我在向 GET 请求发送无效的 body 时遇到了这个问题。

我可以通过发出类似以下请求来复现:

var requestBody interface{}
requestData, _ := json.Marshal(requestBody)
payload = strings.NewReader(string(requestData))

req, err := http.NewRequest("GET", url, payload)
...

0

我遇到了同样的问题,经过多个小时的调查后,发现CDN关闭了连接并在我们这一端引起了EOF错误。而不想要的关闭的原因是我设置给请求的User-Agent。我使用了一个随机选择的浏览器User-Agent,它描述了Chrome的8年前版本。可能该请求被视为不受信任的请求。更新User-Agent后,问题得到解决。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接