在客户端超时时终止服务器处理 [英] Terminate server processing on client timeout

查看:46
本文介绍了在客户端超时时终止服务器处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否可以通过某种方式使Go HTTP服务器意识到客户端的超时,并立即终止正在进行的请求的处理.目前,我已经尝试在客户端上设置超时,该超时实际上可以在客户端上按预期方式工作,并且在达到超时后,请求将以上下文截止日期结束(在等待标头时超过Client.Timeout)结束.

I would like to know if there's any way of making a Go HTTP server aware of a timeout in the client, and immediately terminate the processing of the ongoing request. Currently, I've tried setting timeouts on the client side that actually work as expected on their side and the request finishes with context deadline exceeded (Client.Timeout exceeded while awaiting headers) after the timeout is reached.

    req, err := http.NewRequest(http.MethodGet, URL, nil)
    if err != nil {
        log.Fatal(err)
    }
    client := http.Client{Timeout: time.Second}
    _, err = client.Do(req)
    if err != nil {
        log.Fatal(err)
    }

我还尝试了不同版本的客户端代码,例如使用带有上下文的请求,并且得到了相同的结果,这对于客户端来说是可以的.

I've also tried with different versions of the client code, like using a request with context, and got the same result, which is ok for the client side.

但是,当要检测服务器端的超时时,事实证明,无论客户端中的超时如何,以及我想要发生什么,请求的处理一直持续到服务器完成其工作为止(我不知道是否有可能)在客户端超时后立即终止并中止处理.

However, when it comes to detect the timeout on the server side, it turns out that the processing of the request continues until the server finishes its work, regardless of the timeout in the client, and what I would like to happen (I don't know if it's even possible) is to immediately terminate and abort the processing once the client has timed out.

服务器端代码将是这样的(仅出于示例的目的,在生产代码中它将是更复杂的东西):

The sever side code would be something like this (just for the sake of the example, in production code it would be something more sophisticated):

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Println("before sleep")
    time.Sleep(3 * time.Second)
    fmt.Println("after sleep")

    fmt.Fprintf(w, "Done!")
}

func main() {
    http.HandleFunc("/", handler)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

运行前面的代码,并且请求到达HTTP服务器时,发生以下事件序列:

When the previous code is run, and a request hits the HTTP server, the following sequence of events occurs:

  1. 服务器在睡眠前打印
  2. 服务器进入睡眠状态
  3. 客户端超时并终止,错误为超出上下文期限(等待标头时超过Client.Timeout)
  4. 服务器唤醒并在睡眠后打印

但是我想发生的是在第3步终止该过程.

But what I would like to happen is to terminate the process at step 3.

感谢您,我想知道您对此的想法,以及您是否认为我想做的事是否可行.

Thank being said, I'd like to know your thoughts about it, and whether you think what I want to do is feasible or not.

推荐答案

这里有一些不同的想法.首先,要确认您的要求,就好像您要使客户端断开连接触发整个服务器关闭一样.为此,您可以执行以下操作:

There are a few different ideas at play here. First, to confirm what you are asking for, it looks like you want to make a client disconnection trigger the whole server to be shut down. To do this you can do the following:

  1. 添加 context.WithCancel channel 用于传播关闭事件
  2. 在您的http处理程序中注意断开连接并取消上下文
  3. 添加一个goroutine,当频道关闭时,该例程将关闭您的服务器

这是一个完整的示例程序,可产生以下输出:

Here is a complete sample program that produces the following output:

go run ./main.go
2021/03/04 17:56:44 client: starting request
2021/03/04 17:56:44 server: handler started
2021/03/04 17:56:45 client: deadline exceeded
2021/03/04 17:56:45 server: client request canceled
2021/03/04 17:56:45 server: performing server shutdown
2021/03/04 17:56:45 waiting for goroutines to finish
2021/03/04 17:56:45 All exited!

// main.go

package main

import (
    "context"
    "errors"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "sync"
    "time"
)

func main() {
    wg := &sync.WaitGroup{}
    srvContext, srvCancel := context.WithCancel(context.Background())
    defer srvCancel()

    srv := http.Server{
        Addr: ":8000",
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Printf("server: handler started")
            select {
            case <-time.After(2 * time.Second):
                log.Printf("server: completed long request")
                w.WriteHeader(http.StatusOK)
                w.Write([]byte("OK"))
            case <-r.Context().Done():
                log.Printf("server: client request canceled")
                srvCancel()
                return
            }
        }),
    }

    // add a goroutine that watches for the server context to be canceled
    // as a signal that it is time to stop the HTTP server.
    wg.Add(1)
    go func() {
        defer wg.Done()
        <-srvContext.Done()
        log.Printf("server: performing server shutdown")
        // optionally add a deadline context to avoid waiting too long
        if err := srv.Shutdown(context.TODO()); err != nil {
            log.Printf("server: shutdown failed with context")
        }
    }()

    // just simulate making the request after a brief delay
    wg.Add(1)
    go makeClientRequest(wg)

    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        fmt.Fprintf(os.Stderr, "Server failed listening with error: %v\n", err)
        return
    }

    log.Printf("waiting for goroutines to finish")
    wg.Wait()
    log.Printf("All exited!")
}

func makeClientRequest(wg *sync.WaitGroup) {
    defer wg.Done()
    // delay client request
    time.Sleep(500 * time.Millisecond)
    log.Printf("client: starting request")

    ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
    defer cancel()

    req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1:8000", http.NoBody)
    if err != nil {
        log.Fatalf("failed making client request")
    }
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            log.Printf("client: deadline exceeded")
        } else {
            log.Printf("client: request error: %v", err)
        }
        return
    }

    // got a non-error response
    defer resp.Body.Close()
    body, _ := ioutil.ReadAll(resp.Body)
    log.Printf("client: got response %d %s", resp.StatusCode, string(body))
}

这篇关于在客户端超时时终止服务器处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆