为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
牧羊人nacy 2023-03-21 10:12:52
假设我正在编写一个 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()}
查看完整描述

1 回答

?
慕婉清6462132

TA贡献1804条经验 获得超2个赞

我刚刚对提供的解决方案进行了一些重构,现在它应该可以工作了。让我指导您完成相关更改。


函数doSomething_

func doSomething(ctx context.Context) error {

    fmt.Printf("%v - doSomething: start\n", time.Now())

    select {

    case <-ctx.Done():

        fmt.Printf("%v - doSomething: cancelled\n", time.Now())

        return ctx.Err()

    case <-time.After(3 * time.Second):

        fmt.Printf("%v - doSomething: processed\n", time.Now())

        return nil

    }

}

它等待取消输入或在延迟几秒后3返回给调用者。它接受一个上下文来监听。


函数handleSomething_

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

    ctx := r.Context()


    fmt.Printf("%v - handleRequestCtx: start\n", time.Now())


    done := make(chan error)

    go func() {

        if err := doSomething(ctx); err != nil {

            fmt.Printf("%v - handleRequestCtx: error %v\n", time.Now(), err)

            done <- err

        }


        done <- nil

    }()


    select {

    case <-ctx.Done():

        fmt.Printf("%v - handleRequestCtx: cancelled\n", time.Now())

        return

    case err := <-done:

        if err != nil {

            fmt.Printf("%v - handleRequestCtx: error: %v\n", time.Now(), err)

            w.WriteHeader(http.StatusInternalServerError)

            return

        }

        fmt.Printf("%v - handleRequestCtx: processed\n", time.Now())

    }

}

在这里,逻辑与您的逻辑非常相似。在 select 中,我们检查接收到的错误是否存在nil,并根据此返回正确的 HTTP 状态码给调用者。如果我们收到取消输入,我们将取消所有上下文链。


函数TestHandler_

func TestHandler(t *testing.T) {

    r := mux.NewRouter()

    r.HandleFunc("/demo", handleSomething)


    srv := http.Server{

        Addr:    ":8000",

        Handler: r,

    }


    var wg sync.WaitGroup

    wg.Add(1)

    go func() {

        defer wg.Done()

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

            fmt.Println(err.Error())

        }

    }()


    ctx := context.Background()

    ctx, cancel := context.WithTimeout(ctx, 1*time.Second) // request canceled

    // ctx, cancel := context.WithTimeout(ctx, 5*time.Second) // request processed

    defer cancel()


    req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8000/demo", nil)


    client := http.Client{}

    res, err := client.Do(req)

    if err != nil {

        fmt.Println(err.Error())

    } else {

        fmt.Printf("res status code: %d\n", res.StatusCode)

    }

    srv.Shutdown(ctx)


    wg.Wait()

}

在这里,我们启动了一个 HTTP 服务器并通过http.Client. 可以看到有两条语句设置上下文超时。如果您使用带有评论的那个// request canceled,一切都将被取消,否则,如果您使用另一个,请求将被处理。

我希望这能澄清你的问题!


查看完整回答
反对 回复 2023-03-21
  • 1 回答
  • 0 关注
  • 105 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号