Golang的HTTP客户端和服务器无法保持可靠的连接。

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

golang http client and server can't maintain reliable connection

问题

我有一个非常基本的用例,一个HTTP客户端每隔5秒向HTTP服务器发送一个POST请求。

客户端代码如下:

client := &http.Client{Timeout: 1 * time.Second}

...

for {
    time.Sleep(5 * time.Second)
    body := bytes.NewBuffer([]byte("foo"))
    req, err := http.NewRequest(http.MethodPost, "http://localhost:8080/foo", body)
    
    ...
    
    resp, err := client.Do(req)

    ...

    _ = resp.Body.Close()
}

服务器代码如下:

srv := &http.Server{
    ReadTimeout:  5 * time.Second,
    WriteTimeout: 10 * time.Second,
    Addr:         ":9090",
    Handler:      mux,
}

大多数情况下,请求是成功的。但是很随机地,它们会失败,并显示不同的错误消息:

# 服务器似乎关闭了连接
Post http://localhost:8080/foo: read tcp 127.0.0.1:41312->127.0.0.1:9090: read: connection reset by peer
# 客户端在执行请求时不幸关闭了自己的连接
Post http://localhost:8080/foo: EOF
# 这是一种经常发生的情况,但会导致重试,而POST请求无法重试
Post http://localhost:8080/foo: http: server closed idle connection

我可以通过在请求中设置req.Close = true来避免这个问题,或者在HTTP客户端中禁用keep-alive,或者将请求的频率从每5秒改为每秒执行。这似乎是一个时间问题。

我无法理解的是,即使这似乎是世界上最基本的任务,我仍然无法在两者之间保持连接,而不是为每个请求创建一个新的连接。

我漏掉了什么?

英文:

I have a very basic usecase of an http client making a POST request to an http server periodically, every 5 seconds.

the client:

client := &http.Client{Timeout: 1 * time.Second}

...

for {
	time.Sleep(5 * time.Second)
    body := bytes.NewBuffer([]byte("foo"))
	req, err := http.NewRequest(http.MethodPost, "http://localhost:8080/foo", body)
    
    ...
	
    resp, err := client.Do(req)

    ...

	_ = resp.Body.Close()
}

the server:

	srv := &http.Server{
		ReadTimeout:  5 * time.Second,
		WriteTimeout: 10 * time.Second,
		Addr:         ":9090",
		Handler:      mux,
	}

Most of the times the requests are successful. But very randomly they fail with different error message:

# the server seems to close the connection
Post http://localhost:8080/foo: read tcp 127.0.0.1:41312->127.0.0.1:9090: read: connection reset by peer
# the client seems to close its own connection unfortunately while performing a request
Post http://localhost:8080/foo: EOF
# this is something that tends to happen, but would result in a retry, which cant work with a POST
Post http://localhost:8080/foo: http: server closed idle connection

I can avoid this problem by closing the connection in the request with req.Close = true, by disabling keep-alive in the http client, or executing the request every second, instead of every 5 seconds. It seems to be a timing issue.

I can not wrap my head around the fact that I simply cannot maintain a connection between the two without creating a new connection for every request, even though this seems to be the most basic task in the world.

What am I missing?

答案1

得分: 1

可能是因为你将服务器的ReadTimeout设置为5秒,而这恰好等于请求之间的间隔时间,所以你遇到了竞态条件。根据服务器文档的说明:

Server struct {
...
	// IdleTimeout是在启用keep-alive时等待下一个请求的最长时间。
	// 如果IdleTimeout为零,则使用ReadTimeout的值。如果两者都为零,则没有超时。
	IdleTimeout time.Duration

这意味着当客户端在休眠5秒后发送进一步的请求时,连接可能已经关闭,也可能未关闭。或者,它可能会被单方面关闭。因此会出现各种错误消息。

将服务器的IdleTimeout值设置为大于5秒的值应该会有所帮助。

英文:

Probably you encounter race condition because you have set server ReadTimeout to 5s and this is exactly equal to interval between requests. According to the Server documentation:

Server struct {
...
	// IdleTimeout is the maximum amount of time to wait for the
	// next request when keep-alives are enabled. If IdleTimeout
	// is zero, the value of ReadTimeout is used. If both are
	// zero, there is no timeout.
	IdleTimeout time.Duration

This means that when the client sends further requests, after 5 seconds of sleep, the connection may or may not already be closed. Alternatively, it may be unilaterally closed. Hence the various error messages.

Setting Server IdleTimeout value to something higher than 5s should help.

huangapple
  • 本文由 发表于 2021年8月29日 21:54:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/68973613.html
匿名

发表评论

匿名网友

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

确定