英文:
ioutil.ReadAll(response.Body) blocks forever - Golang
问题
以下是我翻译好的内容:
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
response, err := client.Get(link)
if err != nil {
fmt.Println(err)
}
defer response.Body.Close()
// 在下一行永远阻塞
content, _ = ioutil.ReadAll(response.Body)
上面是我从一个网页中读取内容的代码,该代码位于一个循环中。我发现有时候 ioutil.ReadAll(response.Body)
这一行会永远阻塞。这种情况是随机发生的,但几乎总是发生在这个网页上:http://xkcd.com/55
。有趣的是,当我执行 curl http://xkcd.com/55
时,它返回空白,但是 wget http://xkcd.com/55
返回整个网页。
英文:
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
response, err := client.Get(link)
if err != nil {
fmt.Println(err)
}
defer response.Body.Close()
//block forever at the next line
content, _ = ioutil.ReadAll(response.Body)
The above is my code to read content from a webpage which resides in a loop. I found sometimes the line ioutil.ReadAll(response.Body)
will block forever. This happens randomly, however, it almost always happens on this webpage: http://xkcd.com/55
. It's very interesting that when I do curl http://xkcd.com/55
, it returns nothing, however, wget http://xkcd.com/55
returns the whole webpage.
答案1
得分: 8
我怀疑你的问题在于,即使出现错误,你仍然尝试读取响应体:
if err != nil {
fmt.Println(err)
}
你应该在这之后加上一个else
,或者使用return
、continue
或其他方法。你的ReadAll()
行为是未定义的。
(如果你最初是从Get()
示例代码中复制的,请注意它在错误分支中包含了log.Fatalf()
,这会终止程序。)
我怀疑,正如你所说,偶尔会因为某种原因出现网络错误。你是否检查了Println()
的输出结果?按照你的方式,我可以想象它很容易被其他输出淹没。
正如@twotwotwo所指出的,这个URL返回一个带有尾部斜杠的重定向URL。Get()
会自动处理这个问题,所以这不是问题。默认情况下,curl不会跟随重定向,而wget会。你可以通过将-i
传递给curl来查看头信息。
其他要验证的事项:
-
确保你的
defer
实际上被调用了。记住,defer
是在函数结束时调用的,而不是当前作用域结束时调用的。所以如果你在一个循环中(正如你提到的),你只会积累defer
块,而不会真正关闭这些响应。 -
如果服务器实际上从不关闭连接,那么
io.ReadAll()
将永远不会返回。这是一种特性。如果你想要超时,你需要自己处理。你可以使用curl
等工具来测试这个假设。有关一些解决方案,请参考:
英文:
I suspect your problem is that you try to read the response body even if there's an error:
if err != nil {
fmt.Println(err)
}
You should either have an else
after this, or you should return
or continue
or something else. Your ReadAll()
line is undefined behavior.
(If you originally copied this from the Get()
example code, note that it includes a log.Fatalf()
in the error leg, which terminates the program.)
I suspect that, as you say, occasionally you are getting a network error for one reason or another. Are you checking the output for the result of the Println()
? The way you've done it, I could imagine it easily getting buried in the output.
As @twotwotwo notes, this URL returns a redirect to the same URL with a trailing slash. Get()
will automatically handle this for you, so that's not the problem. By default curl does not follow redirects while wget does. You can see the header information by passing -i
to curl.
Other things to verify:
-
Make sure your
defer
is actually being called. Remember,defer
is called at the end of the function, not the end of the current scope. So if you're in a loop (as you mention), you will just accumulatedefer
blocks and never actually close these responses. -
If the server in fact never closes the connection, then
io.ReadAll()
will never return. This is a feature. If you want a timeout, you need to handle that yourself. You should be able to test this hypothesis with tools likecurl
. For some solutions, see: -
https://stackoverflow.com/questions/20990332/golang-http-timeout-and-goroutines-accumulation
-
https://stackoverflow.com/questions/16895294/how-to-set-timeout-for-http-get-requests-in-golang
-
http.Transport.ResponseHeaderTimeout
答案2
得分: 7
此外,避免在没有内存/缓冲限制控制的情况下使用ReadAll读取响应体,示例如下:
googleResponse := GoogleResponse{}
err = json.NewDecoder(io.LimitReader(resp.Body, MAX_MEMORY)).Decode(&googleResponse)
if err != nil {
return nil, err
}
在以下优秀的博客文章中了解更多相关信息:
Crossing Streams: a Love Letter to io.Reader by Jason Moiron
ioutil.ReadAll(httpResponse.Body) memory consumption
Golang Slices And The Case Of The Missing Memory
英文:
Additionally, avoid read response Body in ReadAll without memory/buffer limits control, example:
googleResponse := GoogleResponse{}
err = json.NewDecoder(io.LimitReader(resp.Body, MAX_MEMORY)).Decode(&googleResponse)
if err != nil {
return nil, err
}
Read more about it in good blog posts:<br/>
Crossing Streams: a Love Letter to io.Reader by Jason Moiron<br/>
ioutil.ReadAll(httpResponse.Body) memory consumption<br/>
Golang Slices And The Case Of The Missing Memory
答案3
得分: 1
你的代码应该按预期工作。我猜测这是一个网络问题。尝试设置更长的超时时间。
package main
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
link := "http://xkcd.com/55"
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
response, err := client.Get(link)
if err != nil {
fmt.Println(err)
}
defer response.Body.Close()
//在下一行永远阻塞
content, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(content))
}
英文:
Your code should work as expected. I am guessing, its a network issue. Try setting a higher timeout.
package main
import (
"crypto/tls"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
link := "http://xkcd.com/55"
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
response, err := client.Get(link)
if err != nil {
fmt.Println(err)
}
defer response.Body.Close()
//block forever at the next line
content, _ := ioutil.ReadAll(response.Body)
fmt.Println(string(content))
}
答案4
得分: 0
我可能已经找到了解决方案,通过在&http.Transport
中添加DisableKeepAlives: true
,像这样:
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: true,
}
自从我做了这个改变,我还没有遇到任何长时间阻塞的情况。但我不能百分之百确定这是解决方案。我将让新代码运行一两天。如果没有阻塞,我认为这个问题就解决了。
英文:
I probably have found the solution by adding DisableKeepAlives: true,
to the `&http.Transport, like this:
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
DisableKeepAlives: true,
}
Since I made this change, I haven't encountered any long blocking, yet. But I'm not 100% sure this is the solution. I will leave the new code running one day or two. If there will be no blocking, I think this problem is solved.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论