Home >Backend Development >Golang >When writing http handlers, do we have to listen for request context cancellation?
When php editor Xinyi processes HTTP requests, whether it is necessary to listen for request context cancellation is a common question. In actual development, there is usually no need to explicitly monitor request context cancellation, because the PHP running environment will automatically handle the related resource release work. However, in some special cases, such as when you need to manually release resources or perform some cleanup operations, listening for request context cancellation can be an effective way. Therefore, whether you need to listen for request context cancellation depends on the specific business requirements and development scenarios. For most cases, we can safely rely on PHP's automatic resource management mechanism.
Assuming I'm writing an http handler that performs other operations before returning the response, do I have to set up a listener to check if the http request context has been canceled? so that it can return immediately, or is there some other way to exit the handler when the request context is canceled?
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 }
I tried to test it, but after the context is cancelled, the dosomething
function does not stop and is still running in the background.
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() } func handleSomething(w http.ResponseWriter, r *http.Request) { done := make(chan error) go func() { if err := doSomething(r.Context()); err != nil { log.Println(err) done <- err } done <- nil }() select { case <-r.Context().Done(): log.Println("context is done!") 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 { return runInContext(ctx, func() { log.Println("doing something") defer log.Println("done doing something") time.Sleep(10 * time.Second) }) } func runInContext(ctx context.Context, fn func()) error { ch := make(chan struct{}) go func() { defer close(ch) fn() }() select { case <-ctx.Done(): return ctx.Err() case <-ch: return nil } }
I just refactored the solution provided a bit and it should work now. Let me guide you through the changes.
dosomething
Functionfunc 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 } }
It waits for cancellation input, or returns to the caller after a delay of 3
seconds. It accepts a context to listen to.
handlesomething
Functionfunc 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()) } }
The logic here is very similar to yours. In the select, we check if the received error is nil
and return the correct http status code to the caller accordingly. If we receive a cancel input, we cancel all context chains.
testhandler
Functionfunc 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() }
Here, we start an http server and make http requests to it via http.client
. You can see that there are two statements to set the context timeout. If you use one with comment // request canceled
then everything will be canceled, otherwise if you use another one the request will be processed.
I hope this clarifies your question!
The above is the detailed content of When writing http handlers, do we have to listen for request context cancellation?. For more information, please follow other related articles on the PHP Chinese website!