ioutil.ReadAll(response.Body)永远阻塞 – Golang

huangapple go评论107阅读模式
英文:

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,或者使用returncontinue或其他方法。你的ReadAll()行为是未定义的。

(如果你最初是从Get()示例代码中复制的,请注意它在错误分支中包含了log.Fatalf(),这会终止程序。)

我怀疑,正如你所说,偶尔会因为某种原因出现网络错误。你是否检查了Println()的输出结果?按照你的方式,我可以想象它很容易被其他输出淹没。

正如@twotwotwo所指出的,这个URL返回一个带有尾部斜杠的重定向URL。Get()会自动处理这个问题,所以这不是问题。默认情况下,curl不会跟随重定向,而wget会。你可以通过将-i传递给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:

答案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 (
	&quot;crypto/tls&quot;
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;net/http&quot;
)

func main() {

	link := &quot;http://xkcd.com/55&quot;

	tr := &amp;http.Transport{
		TLSClientConfig: &amp;tls.Config{InsecureSkipVerify: true},
	}
	client := &amp;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

我可能已经找到了解决方案,通过在&amp;http.Transport中添加DisableKeepAlives: true,像这样:

tr := &amp;http.Transport{
    TLSClientConfig:   &amp;tls.Config{InsecureSkipVerify: true},
    DisableKeepAlives: true,
}

自从我做了这个改变,我还没有遇到任何长时间阻塞的情况。但我不能百分之百确定这是解决方案。我将让新代码运行一两天。如果没有阻塞,我认为这个问题就解决了。

英文:

I probably have found the solution by adding DisableKeepAlives: true, to the `&http.Transport, like this:

tr := &amp;http.Transport{
	TLSClientConfig:   &amp;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.

huangapple
  • 本文由 发表于 2014年5月31日 15:35:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/23967638.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定