为什么net/http不尊重超过30秒的超时持续时间?

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

Why doesn't net/http respect timeout durations longer than 30 seconds?

问题

使用golang 1.20.1版本。

在golang的net/http和context包中,我无法设置超过30秒的超时时间。设置较短的超时时间是可以的,例如:

代码:

log.Infof("Elasticsearch URL is %v", elasticsearchURL)
client := &http.Client{Timeout: time.Duration(time.Second * 60)}
req, err := http.NewRequest("GET", listBackupsURL, nil)
if err != nil {
    internalError(w, fmt.Sprintf("error creating request: %v", err))
    return
}
req.SetBasicAuth(username, password)
resp, err := client.Do(req)
if err != nil {
    // 处理错误
    internalError(w, fmt.Sprintf("error accessing elasticsearch: %v", err))
    return
}

日志:

I0503 23:01:55.973821       1 somecode.go:85] URL is http://elasticsearch.example.ingest:9200
E0503 23:02:25.976345       1 caller_handler.go:63] 500 Internal Server Error: error accessing elasticsearch: Get "http://elasticsearch.example.ingest:9200/_cat/snapshots/object-store-repo?v&s=id": dial tcp 1.2.3.4:9200: i/o timeout

超时时间是30秒,而不是60秒。

如果我在任一方法中将超时时间更改为3秒(time.Duration(time.Second * 3)),则会按预期工作:

I0503 23:44:17.622121       1 somecode.go:85] Elasticsearch URL is http://elasticsearch.example.ingest:9200
E0503 23:44:20.624795       1 caller_handler.go:63] 500 Internal Server Error: error accessing elasticsearch: Get "http://elasticsearch.example.ingest:9200/_cat/snapshots/object-store-repo?v&s=id": context deadline exceeded (Client.Timeout exceeded while awaiting headers)
英文:

Using golang 1.20.1.

In golang's net/http and context packages, I'm unable to set a timeout longer than thirty seconds. Setting a shorter timeout works fine, e.g.

Code:

	log.Infof("Elasticsearch URL is %v", elasticsearchURL)
    client := &http.Client{Timeout: time.Duration(time.Second * 60)}
	req, err := http.NewRequest("GET", listBackupsURL, nil)
	if err != nil {
		internalError(w, fmt.Sprintf("error creating request: %v", err))
		return
	}
	req.SetBasicAuth(username, password)
	resp, err := client.Do(req)
	if err != nil {
		// handle error
		internalError(w, fmt.Sprintf("error accessing elasticsearch: %v", err))
		return
	}

Logs:

I0503 23:01:55.973821       1 somecode.go:85] URL is http://elasticsearch.example.ingest:9200
E0503 23:02:25.976345       1 caller_handler.go:63] 500 Internal Server Error: error accessing elasticsearch: Get "http://elasticsearch.example.ingest:9200/_cat/snapshots/object-store-repo?v&s=id": dial tcp 1.2.3.4:9200: i/o timeout

The timeout is thirty seconds, not sixty.

I get the same behavior if I use http.NewRequestWithContext(...) and use a context setting the same timeouts:

Code:

   	log.Infof("Elasticsearch URL is %v", elasticsearchURL)
    ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second * 60)) 
    defer cancel()
	req, err := http.NewRequestWithContext(ctx, "GET", listBackupsURL, nil)
	if err != nil {
		internalError(w, fmt.Sprintf("error creating request: %v", err))
		return
	}
	req.SetBasicAuth(username, password)
	resp, err := client.Do(req)
	if err != nil {
		// handle error
		internalError(w, fmt.Sprintf("error accessing elasticsearch: %v", err))
		return
	}

Logs:

I0503 23:31:10.941169       1 somecode.go:85] Elasticsearch URL is http://elasticsearch.example.ingest:9200
E0503 23:31:40.941642       1 caller_handler.go:63] 500 Internal Server Error: error accessing elasticsearch: Get "http://elasticsearch.example.ingest:9200/_cat/snapshots/object-store-repo?v&s=id": dial tcp 1.2.3.4:9200: i/o timeout

However, if I change the timeout to three seconds (time.Duration(time.Second * 3)) in either method, it works as expected:

I0503 23:44:17.622121       1 somecode.go:85] Elasticsearch URL is http://elasticsearch.example.ingest:9200
E0503 23:44:20.624795       1 caller_handler.go:63] 500 Internal Server Error: error accessing elasticsearch: Get "http://elasticsearch.example.ingest:9200/_cat/snapshots/object-store-repo?v&s=id": context deadline exceeded (Client.Timeout exceeded while awaiting headers)

答案1

得分: 3

我会为你翻译以下内容:

我会将问题隔离,排除可能性,以帮助缩小问题发生的原因。如果可能的话,使用一小段代码,只处理你想要的部分。

要测试你的基础设施,可以使用 httpstat 来模拟远程超时。示例代码如下:

func main() {

	client := &http.Client{Timeout: time.Duration(time.Second * 200)}
	req, err := http.NewRequest("GET", "https://httpstat.us/504?sleep=120000", nil)
	if err != nil {
		log.Fatal(err)
		return
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
		return
	}

	fmt.Println(resp)
}

如果你在这一点上遇到 dial tcp IP:PORT: i/o timeout 超时错误,那么你需要检查你的操作系统和防火墙设置。无论你在 Go 中设置了什么超时时间,它都应该覆盖操作系统的默认设置,如果你以这种方式遇到超时错误,可能是由于防火墙(本地或远程)引起的。

另外,如果你能够进行外部连接,那么有可能是 Elasticsearch 超时了,尽管根据文档,你应该期望从 Elasticsearch 直接返回一个有信息的错误。你可以在 URL 中直接设置 Elasticsearch 的超时时间,并进行测试。

希望对你有所帮助。

英文:

I'd isolate the problem to eliminate possibilities to help narrow what is causing it to happen. If possible, use a small piece of code so you deal with only what you want.

To test your infrastructure httpstat will help you simulate a remote timeout. Example:

func main() {

	client := &http.Client{Timeout: time.Duration(time.Second * 200)}
	req, err := http.NewRequest("GET", "https://httpstat.us/504?sleep=120000", nil)
	if err != nil {
		log.Fatal(err)
		return
	}

	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
		return
	}

	fmt.Println(resp)
}

If you're getting dial tcp IP:PORT: i/o timeout timeout at this point, then you need to check your OS and and the firewall(s). Whatever timeout you set in Go should override the OS defaults, and if you're getting timeout in that way it is possible that there is a firewall (local or remote) causing that.

Alternatively, if you're able to connect externally, then there is a possibility that ES is timing out, although according to the docs you should expect an informative error coming from ES directly. You can se the timeout for ES directly in the URL and test it as well.

Hope this helps.

huangapple
  • 本文由 发表于 2023年5月5日 00:10:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/76175248.html
匿名

发表评论

匿名网友

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

确定