Golang http请求在连续进行多个请求时会导致EOF错误

nqwrtyyt  于 2023-04-09  发布在  Go
关注(0)|答案(7)|浏览(928)

我正在尝试调试一个非常不寻常的错误,我收到了一个简单的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

当我发出一个以上的请求时,失败就会发生。如果我注解掉除了PUT请求之外的所有内容,测试就会一直通过。一旦我包含第二个测试,比如GET,一个或另一个失败(有时两个都通过)。

cidc1ykv

cidc1ykv1#

我确实经历过这种情况。你需要将Req.Close设置为true(示例中使用的resp.Body.Close()语法上的defer是不够的)。像这样:

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
}
mw3dktmi

mw3dktmi2#

我同意在单元测试中不应该使用外部服务器的说法,为什么不使用内置的http.Server来提供您想要测试的内容呢?(实际上有httptest包可以帮助实现这一点)
我最近在尝试抓取网站Map时遇到了同样的问题,这是我到目前为止发现的:
Go默认会发送头部为Connection: Keep-Alive的请求,并持久化连接以供重用。我遇到的问题是,服务器在响应头部中使用Connection: Keep-Alive进行响应,然后立即关闭连接。
作为一点背景知识,关于go在这种情况下如何实现连接(你可以在net/http/transport.go中查看完整代码)。这里有两个goroutine,一个负责写,一个负责阅读(readLoopwriteLoop)在大多数情况下,readLoop将检测到套接字上的关闭,并关闭连接。当您在readLoop实际检测到close之前发起另一个请求时,就会出现问题。并且它读取的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

k0pti3hp

k0pti3hp3#

我猜你的代码没有问题。最有可能的原因是服务器正在关闭连接。速率限制是一个可能的原因。
你的测试不应该依赖于一个非常脆弱的外部服务,相反,你应该考虑在本地启动一个测试服务器。

r55awzrz

r55awzrz4#

我的经验与此错误是当我输入绝对空输入我的JSON API!
我应该将{}作为空JSON发送,但我发送了``,因此发生了此错误

mepcadol

mepcadol5#

我在向GET请求发送无效正文时遇到此问题
我可以通过提出类似于下面的请求来复制:

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

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

fquxozlt6#

我正在开发一个图像下载应用程序时发生了这个问题。
尝试request.Close=true但不工作。
60%的请求导致EOF错误。
我想这可能是一个图像服务器的问题,而不是我的代码。
PHP代码运行良好。
然后我用

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

提出请求,而不是

http.DefaultClient.Do(request)

问题解决了。不知道为什么,我猜是RoundTripper的问题

wi3ka0sx

wi3ka0sx7#

我也遇到了同样的问题,经过几个小时的调查,原来是CDN关闭了连接,导致我们这边的EOF错误。而不必要的关闭的原因是我设置的User-Agent请求。我使用了随机选择的浏览器User-Agent,其中描述了一个8年前的Chrome版本。可能该请求被视为不可信请求。更新用户代理后,该问题已得到解决。

相关问题