猿问

在编写 http 处理程序时,我们是否必须监听请求上下文取消?

假设我正在编写一个 http 处理程序,它在返回响应之前执行其他操作,我是否必须设置一个侦听器来检查 http 请求上下文是否已被取消?以便它可以立即返回,或者在请求上下文取消时是否有其他方法退出处理程序?


func handleSomething(w http.ResponseWriter, r *http.Request) {

    done := make(chan error)


    go func() {

        if err := doSomething(r.Context()); err != nil {

            done <- err

                        return

        }


        done <- nil

    }()


    select {

    case <-r.Context().Done():

        http.Error(w, r.Context().Err().Error(), http.StatusInternalServerError)

        return

    case err := <-done:

        if err != nil {

            http.Error(w, err.Error(), http.StatusInternalServerError)

            return

        }


        w.WriteHeader(http.StatusOK)

        w.Write([]byte("ok"))

    }

}


func doSomething(ctx context.Context) error {

    // simulate doing something for 1 second.

    time.Sleep(time.Second)

    return nil

}


我尝试对其进行测试,但是在上下文被取消后,doSomething功能并没有停止并且仍在后台运行。


func TestHandler(t *testing.T) {

    mux := http.NewServeMux()

    mux.HandleFunc("/something", handleSomething)


    srv := http.Server{

        Addr:    ":8989",

        Handler: mux,

    }


    var wg sync.WaitGroup

    wg.Add(1)

    go func() {

        defer wg.Done()

        if err := srv.ListenAndServe(); err != nil {

            log.Println(err)

        }

    }()


    time.Sleep(time.Second)


    req, err := http.NewRequest(http.MethodGet, "http://localhost:8989/something", nil)

    if err != nil {

        t.Fatal(err)

    }


    cl := http.Client{

        Timeout: 3 * time.Second,

    }


    res, err := cl.Do(req)

    if err != nil {

        t.Logf("error: %s", err.Error())

    } else {

        t.Logf("request is done with status code %d", res.StatusCode)

    }


    go func() {

        <-time.After(10 * time.Second)

        shutdown, cancel := context.WithTimeout(context.Background(), 10*time.Second)

        defer cancel()


        srv.Shutdown(shutdown)

    }()


    wg.Wait()

}

牧羊人nacy
浏览 106回答 1
1回答

慕婉清6462132

我刚刚对提供的解决方案进行了一些重构,现在它应该可以工作了。让我指导您完成相关更改。函数doSomething_func doSomething(ctx context.Context) error {&nbsp; &nbsp; fmt.Printf("%v - doSomething: start\n", time.Now())&nbsp; &nbsp; select {&nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%v - doSomething: cancelled\n", time.Now())&nbsp; &nbsp; &nbsp; &nbsp; return ctx.Err()&nbsp; &nbsp; case <-time.After(3 * time.Second):&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%v - doSomething: processed\n", time.Now())&nbsp; &nbsp; &nbsp; &nbsp; return nil&nbsp; &nbsp; }}它等待取消输入或在延迟几秒后3返回给调用者。它接受一个上下文来监听。函数handleSomething_func handleSomething(w http.ResponseWriter, r *http.Request) {&nbsp; &nbsp; ctx := r.Context()&nbsp; &nbsp; fmt.Printf("%v - handleRequestCtx: start\n", time.Now())&nbsp; &nbsp; done := make(chan error)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; if err := doSomething(ctx); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%v - handleRequestCtx: error %v\n", time.Now(), err)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; done <- err&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; done <- nil&nbsp; &nbsp; }()&nbsp; &nbsp; select {&nbsp; &nbsp; case <-ctx.Done():&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%v - handleRequestCtx: cancelled\n", time.Now())&nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; case err := <-done:&nbsp; &nbsp; &nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%v - handleRequestCtx: error: %v\n", time.Now(), err)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; w.WriteHeader(http.StatusInternalServerError)&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("%v - handleRequestCtx: processed\n", time.Now())&nbsp; &nbsp; }}在这里,逻辑与您的逻辑非常相似。在 select 中,我们检查接收到的错误是否存在nil,并根据此返回正确的 HTTP 状态码给调用者。如果我们收到取消输入,我们将取消所有上下文链。函数TestHandler_func TestHandler(t *testing.T) {&nbsp; &nbsp; r := mux.NewRouter()&nbsp; &nbsp; r.HandleFunc("/demo", handleSomething)&nbsp; &nbsp; srv := http.Server{&nbsp; &nbsp; &nbsp; &nbsp; Addr:&nbsp; &nbsp; ":8000",&nbsp; &nbsp; &nbsp; &nbsp; Handler: r,&nbsp; &nbsp; }&nbsp; &nbsp; var wg sync.WaitGroup&nbsp; &nbsp; wg.Add(1)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; defer wg.Done()&nbsp; &nbsp; &nbsp; &nbsp; if err := srv.ListenAndServe(); err != nil {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(err.Error())&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; ctx := context.Background()&nbsp; &nbsp; ctx, cancel := context.WithTimeout(ctx, 1*time.Second) // request canceled&nbsp; &nbsp; // ctx, cancel := context.WithTimeout(ctx, 5*time.Second) // request processed&nbsp; &nbsp; defer cancel()&nbsp; &nbsp; req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8000/demo", nil)&nbsp; &nbsp; client := http.Client{}&nbsp; &nbsp; res, err := client.Do(req)&nbsp; &nbsp; if err != nil {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println(err.Error())&nbsp; &nbsp; } else {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Printf("res status code: %d\n", res.StatusCode)&nbsp; &nbsp; }&nbsp; &nbsp; srv.Shutdown(ctx)&nbsp; &nbsp; wg.Wait()}在这里,我们启动了一个 HTTP 服务器并通过http.Client. 可以看到有两条语句设置上下文超时。如果您使用带有评论的那个// request canceled,一切都将被取消,否则,如果您使用另一个,请求将被处理。我希望这能澄清你的问题!
随时随地看视频慕课网APP

相关分类

Go
我要回答