我正在尝试调试一个非常不寻常的错误,我收到的是一个简单的REST库我wrote。
我正在使用标准的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,一个或另一个失败(有时两个都通过)。
感谢任何帮助,谢谢!
答案 0 :(得分:44)
我可靠地体验到了这一点。您需要将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
}
答案 1 :(得分:19)
我同意你不应该在单元测试中击中外部服务器的断言,为什么不使用内置的http.Server并提供你想要测试的内容。 (实际上有httptest包可以帮助解决这个问题)
我最近在尝试抓取站点地图时遇到了同样的问题,这是我到目前为止所发现的:
默认情况下,将发送带有标头Connection: Keep-Alive
的请求并保留连接以便重复使用。我遇到的问题是服务器在响应头中以Connection: Keep-Alive
响应,然后立即关闭连接。
作为在这种情况下如何实现连接的一个小背景(您可以查看net / http / transport.go中的完整代码)。有两个goroutine,一个负责编写,一个负责阅读(readLoop
和writeLoop
)在大多数情况下,readLoop
将检测套接字上的关闭,并关闭连接。当您在readLoop实际检测到关闭之前发起另一个请求时,会发生此问题,并且它读取的EOF被解释为该新请求的错误,而不是请求之前发生的关闭。
鉴于这种情况,请求在两个请求之间工作的原因是,它提供了readLoop时间来检测新请求之前的连接关闭并将其关闭,以便新请求将启动新连接。 (并且它会间歇性地失败的原因是因为在您的请求之间运行了一些代码,并且根据goroutine的调度,有时EOF将在您的下一个请求之前正确处理,有时不会。)并且req.Close = true
解决方案有效,因为它可以防止连接被重复使用。
有一个与此情况相关的故障单:https://code.google.com/p/go/issues/detail?id=4677(以及我创建的一张可以让我可靠地重现此信息的致命车票:https://code.google.com/p/go/issues/detail?id=8122)
答案 2 :(得分:13)
我猜你的代码没问题。导致问题的最可能原因是服务器正在关闭连接。速率限制是一个可能的原因。
您的测试不应该依赖于非常脆弱且不密封的外部服务。相反,你应该考虑在本地启动测试服务器。