php小編新一在處理HTTP請求時,是否必須監聽請求上下文取消是常見的疑問。在實際開發中,通常情況下並不需要明確地監聽請求上下文取消,因為PHP的運作環境會自動處理相關的資源釋放工作。但是,在某些特殊情況下,例如需要手動釋放資源或執行一些清理操作時,監聽請求上下文取消可以是一種有效的方式。因此,是否需要監聽請求上下文取消取決於特定的業務需求和開發場景。對於大部分情況下,我們可以放心地依賴PHP的自動資源管理機制。
假設我正在編寫一個 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() } 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 } }
我剛剛對提供的解決方案進行了一些重構,現在它應該可以工作了。讓我指導您完成相關變更。
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
向它發出 http 請求。可以看到有兩個語句來設定上下文超時。如果您使用帶有註釋 // request canceled
的,則所有內容都會被取消,否則,如果您使用另一個,則請求將被處理。
我希望這能澄清您的問題!
以上是當編寫http處理程序時,我們是否必須監聽請求上下文取消?的詳細內容。更多資訊請關注PHP中文網其他相關文章!