英文:
Golang http request results in EOF errors when making multiple requests successively
问题
我正在尝试调试一个我写的简单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)
}
我是这样发送请求的:
// 发送HTTP请求,返回数据
func (f *firebaseRoot) SendRequest(method string, path string, body io.Reader) ([]byte, error) {
url := f.BuildURL(path)
// 创建请求
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
// 发送JSON到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
当我发起多个请求时,就会出现这些失败。如果我将除了PUT请求之外的所有代码都注释掉,测试就会一直通过。一旦我包括第二个测试,比如GET请求,其中一个或两个测试就会失败(有时两个都通过)。
英文:
I am trying to debug a very unusual error I am receiving for a simple REST library I wrote.
I am using the standard net/http package to make Get, Post, Put, Delete requests but my tests occasionally fail when I make multiple requests successively. My test looks like this:
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)
}
And I am making the request like this:
// 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
}
Sometimes it works, but most of the time I get 1 or 2 failures:
--- 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
The failures happen when I make more than 1 request. If I comment out everything except for the PUT request, the tests consistently pass. Once I include a second test, such as GET, one or the other fails (sometimes both pass).
答案1
得分: 85
我经历过这个可靠的过程。您需要将Req.Close设置为true(在示例中使用的defer resp.Body.Close()语法是不够的)。像这样:
client := &http.Client{}
req, err := http.NewRequest(method, url, httpBody)
// 注意这个!!
req.Close = true
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("user", "pass")
resp, err := client.Do(req)
if err != nil {
// 无论如何
}
defer resp.Body.Close()
response, err = ioutil.ReadAll(resp.Body)
if err != nil {
// 无论如何
}
英文:
I experienced this reliably. You need to set Req.Close to true (the defer on resp.Body.Close() syntax used in the examples is not enough). Like this:
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
}
答案2
得分: 41
我同意这个说法,你在单元测试中不应该访问外部服务器,为什么不直接使用内置的http.Server并提供要测试的内容呢?(实际上有一个名为httptest的包可以帮助解决这个问题)
最近我在尝试爬取网站地图时遇到了同样的问题,目前我找到了以下解决方法:
Go默认会发送带有Connection: Keep-Alive
头的请求,并保持连接以便重用。我遇到的问题是服务器在响应头中返回Connection: Keep-Alive
,然后立即关闭连接。
关于Go在这种情况下如何实现连接的一些背景知识(你可以在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)
英文:
I agree with the assertion that you shouldn't be hitting outside servers in your unit tests, why not just use the built-in http.Server and serve up the content that you want to test. (There is actually the httptest package to help with this)
I recently ran into this same problem while trying to crawl sitemaps, and this is what I have found so far:
Go by default will send requests with the header Connection: Keep-Alive
and persist connections for re-use. The problem that I ran into is that the server is responding with Connection: Keep-Alive
in the response header and then immediately closing the connection.
As a little background as to how go implements connections in this case (you can look at the full code in net/http/transport.go). There are two goroutines, one responsible for writing and one responsible for reading (readLoop
and writeLoop
) In most circumstances readLoop
will detect a close on the socket, and close down the connection. The problem here occurs when you initiate another request before the readLoop actually detects the close, and the EOF that it reads get interpreted as an error for that new request rather than a close that occurred prior to the request.
Given that this is the case the reason why sleeping in between requests works is that it gives readLoop time to detect the close on the connection before your new request and shut it down, so that your new request will initiate a new connection. (And the reason why it would intermittently fail is because there is some amount code running between your requests and depending of scheduling of goroutines, sometimes the EOF will be properly handled before your next request, sometimes not). And the req.Close = true
, solution works because it prevents the connection from being re-used.
There is a ticket related to this situation: https://code.google.com/p/go/issues/detail?id=4677 (and a dupe ticket that I created that allowed me to reliably reproduce this: https://code.google.com/p/go/issues/detail?id=8122)
答案3
得分: 24
我猜测你的代码没有问题。你遇到问题的最可能原因是服务器关闭了连接。限制访问速率可能是其中一个原因。
你的测试不应该依赖于一个非常脆弱且不具备隔离性的外部服务。相反,你应该考虑在本地启动一个测试服务器。
英文:
I'm going to guess there is no problem with your code. The most likely cause of your problem is because the server is closing the connection. Rate limiting is one possible reason for this.
Your test shouldn't be relying on an external service that's very brittle and not hermetic. Instead you should think about spinning up a test server locally.
答案4
得分: 2
我的经验是当我为我的JSON API输入完全空的输入时发生了这个错误!
我应该发送{}
作为空的JSON,但我发送了
,所以发生了这个错误。
英文:
My experience with this error was when I entered absolutely empty input for my JSON API!
I should send {}
as empty JSON, but I sent
so this error happened
答案5
得分: 1
我正在开发一个图片下载应用程序时遇到了这个问题。尝试了request.Close=true
但没有起作用。60%的请求导致了一个EOF
错误。
我以为可能是图片服务器的问题,而不是我的代码。但是PHP代码运行良好。
然后我使用了以下代码来发送请求:
var client = &http.Client{
Transport: &http.Transport{},
}
client.Do(request)
而不是使用
http.DefaultClient.Do(request)
问题解决了。不确定为什么,我猜可能与RoundTripper
有关。
英文:
I was developing an image download app when this problem occurs.
Tried request.Close=true
but not work.
60% requests resulted in a EOF
error.
I thought maybe it is an image server problem, not my code.
But php code works fine.
Then I use
var client = &http.Client{
Transport: &http.Transport{},
}
client.Do(request)
to make request, instead of
http.DefaultClient.Do(request)
problem gone.
Not sure why,I guess something with RoundTripper
答案6
得分: 0
我在发送一个无效的请求体到一个GET
请求时遇到了这个问题。
我可以通过发送类似下面的请求来复现:
var requestBody interface{}
requestData, _ := json.Marshal(requestBody)
payload = strings.NewReader(string(requestData))
req, err := http.NewRequest("GET", url, payload)
...
英文:
I encountered this issue while sending an invalid body to a GET
request
I could reproduce by making a request similar to below:
var requestBody interface{}
requestData, _ := json.Marshal(requestBody)
payload = strings.NewReader(string(requestData))
req, err := http.NewRequest("GET", url, payload)
...
答案7
得分: 0
我遇到了同样的问题,经过多个小时的调查,发现CDN关闭了连接,导致我们这边出现了EOF错误。而不希望关闭的原因是我设置的请求中的User-Agent。我使用了一个随机选择的浏览器User-Agent,描述了一个8年前的Chrome版本。可能请求被视为不受信任的请求。更新User-Agent后,问题得到解决。
英文:
I faced the same issue, and after investigating for many hours, it turned out that the CDN closed the connection and caused an EOF
error on our side. And the reason of the unwanted close was the User-Agent
what I set to the request. I used a randomly picked browser User-Agent, which described an 8-years old version of the Chrome. Probably the request was treated as an untrusted one. After updating the User-Agent the issue has been solved.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论