Context timeout implementation on every request using golang

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

Context timeout implementation on every request using golang

问题

我正在尝试为每个请求处理上下文超时。我们有以下服务器结构:

Context timeout implementation on every request using golang

流程概述:

Go服务器:基本上充当[反向代理] 2

Auth服务器:检查请求的身份验证。

应用服务器:核心请求处理逻辑。

现在,如果授权服务器无法在规定的时间内处理请求,我想从内存中关闭goroutine。

这是我尝试过的:

  1. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  2. defer cancel()
  3. req, _ := http.NewRequest("GET", authorizationServer, nil)
  4. req.Header = r.Header
  5. req.WithContext(ctx)
  6. res, error := client.Do(req)
  7. select {
  8. case <-time.After(10 * time.Second):
  9. fmt.Println("overslept")
  10. case <-ctx.Done():
  11. fmt.Println(ctx.Err()) // 打印 "context deadline exceeded"
  12. }

在这里,如果请求在规定的时间内未处理,上下文返回为"deadline exceeded"。但它继续处理该请求,并在超过指定时间后返回响应。那么,当时间超过时,我该如何停止请求流程(goroutine)。

我还需要使用任何单独的上下文实现吗?

注意1:如果我们可以使用上下文管理HTTP服务器创建的每个请求(goroutine)的超时,那将非常棒。

英文:

I am trying to handle a context timeout for every request. We have the following server structures:

Context timeout implementation on every request using golang

Flow overview:

Go Server: Basically, acts as a [Reverse-proxy].2

Auth Server: Check for requests Authentication.

Application Server: Core request processing logic.

Now if Authorization server isn't able to process a request in stipulated time, then I want to close the goroutine from memory.

Here is what I tried:

  1. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  2. defer cancel()
  3. req, _ := http.NewRequest(&quot;GET&quot;, authorizationServer, nil)
  4. req.Header = r.Header
  5. req.WithContext(ctx)
  6. res, error := client.Do(req)
  7. select {
  8. case &lt;-time.After(10 * time.Second):
  9. fmt.Println(&quot;overslept&quot;)
  10. case &lt;-ctx.Done():
  11. fmt.Println(ctx.Err()) // prints &quot;context deadline exceeded&quot;
  12. }

Over here, context returns as "deadline exceeded", if the request is not processed in stipulated time. But it continues to process that request and return response in more than the specified time. So, how can I stop the request flow (goroutine), when time is exceeded.

I've also the complete request needs to be processed in 60 seconds with this code:

  1. var netTransport = &amp;http.Transport{
  2. Dial: (&amp;net.Dialer{
  3. Timeout: 60 * time.Second,
  4. }).Dial,
  5. TLSHandshakeTimeout: 60 * time.Second,
  6. }
  7. client := &amp;http.Client{
  8. Timeout: time.Second * 60,
  9. Transport: netTransport,
  10. CheckRedirect: func(req *http.Request, via []*http.Request) error {
  11. return http.ErrUseLastResponse
  12. },
  13. }

So do I need any separate context implementations as well?

Note1: It would be awesome, if we can manage the timeout on every requests (goroutine) created by HTTP server, using context.

答案1

得分: 26

你的代码中发生的情况非常正确,符合预期。

你使用5秒的超时创建了一个上下文。你将它传递给request并发起了该请求。假设该请求在2秒内返回。然后你使用select语句等待10秒或等待上下文完成。上下文将始终在创建时的初始5秒内完成,并且每次到达结束时都会返回该错误。

上下文与请求是独立的,除非之前被取消,否则它将达到截止时间。你使用defer在函数结束时取消请求。

在你的代码中,请求会考虑你的超时时间。但是,每次到达超时时间时,ctx.Err()都会返回deadline exceeded错误,因为这是在上下文内部发生的情况。多次调用ctx.Err()将返回相同的错误。

  1. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  2. defer cancel()
  3. go func () {
  4. select {
  5. case <-time.After(10 * time.Second):
  6. fmt.Println("overslept")
  7. case <-ctx.Done():
  8. fmt.Println(ctx.Err()) // 输出 "context deadline exceeded"
  9. }
  10. }()
  11. req, _ := http.NewRequest("GET", authorizationServer, nil)
  12. req.Header = r.Header
  13. req = req.WithContext(ctx)
  14. res, error := client.Do(req)

从上下文文档中可以看到:

  1. // Done关闭后,Err返回非nil错误值。如果上下文已取消,则Err返回Canceled;如果上下文的截止时间已过,则Err返回DeadlineExceeded。Err的其他值未定义。Done关闭后,对Err的连续调用将返回相同的值。

在你的代码中,超时将始终到达并且不会被取消,这就是为什么你收到DeadlineExceeded错误。你的代码是正确的,除了select部分会阻塞,直到10秒过去或上下文超时到达。在你的情况下,总是达到上下文超时。

你应该检查client.Do调用返回的error,而不用担心这里的context错误。你是在控制上下文。如果请求超时(当然你应该测试这种情况),那么会返回适当的错误供你验证。

英文:

What happens in your code is very correct and behaves as expected.

You create a context with 5 seconds timeout. You pass it to the request and make that request. Let's say that request returns in 2 seconds. You then do a select and either wait 10 seconds or wait for the context to finish. Context will always finish in the initial 5 seconds from when it was created and will also give that error every time it reaches the end.

The context is independent of the request and it will reach its deadline unless cancelled previously. You cancel the request when the function finishes using defer.

In your code the request takes your timeout in consideration. But the ctx.Err() will return deadline exceeded every time it reaches the timeout. Since that's what happens inside the context. calling ctx.Err() multiple times will return the same error.

  1. ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  2. defer cancel()
  3. go func () {
  4. select {
  5. case &lt;-time.After(10 * time.Second):
  6. fmt.Println(&quot;overslept&quot;)
  7. case &lt;-ctx.Done():
  8. fmt.Println(ctx.Err()) // prints &quot;context deadline exceeded&quot;
  9. }
  10. }()
  11. req, _ := http.NewRequest(&quot;GET&quot;, authorizationServer, nil)
  12. req.Header = r.Header
  13. req = req.WithContext(ctx)
  14. res, error := client.Do(req)

From the context documentation:

  1. // Err returns a non-nil error value after Done is closed. Err returns
  2. // Canceled if the context was canceled or DeadlineExceeded if the
  3. // context&#39;s deadline passed. No other values for Err are defined.
  4. // After Done is closed, successive calls to Err return the same value.

In your code, the timeout will always be reached and not cancelled, that is why you receive DeadlineExceeeded. Your code is correct except the select part which will block until either 10 seconds pass or context timeout is reached. In your case always the context timeout is reached.

You should check the error returned by the client.Do call and not worry about the context error in here. You are the one controlling the context. If the request times out, a case you should test of course, then a proper error would be returned for you to verify.

huangapple
  • 本文由 发表于 2017年4月10日 19:23:18
  • 转载请务必保留本文链接:https://go.coder-hub.com/43321894.html
匿名

发表评论

匿名网友

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

确定