Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Apabila menulis pengendali http, adakah kita perlu mendengar pembatalan konteks permintaan?

Apabila menulis pengendali http, adakah kita perlu mendengar pembatalan konteks permintaan?

WBOY
WBOYke hadapan
2024-02-08 23:03:281191semak imbas

Apabila menulis pengendali http, adakah kita perlu mendengar pembatalan konteks permintaan?

php Editor Xinyi Apabila memproses permintaan HTTP, sama ada perlu untuk memantau pembatalan konteks permintaan adalah soalan biasa. Dalam pembangunan sebenar, biasanya tidak perlu memantau pembatalan konteks permintaan secara eksplisit, kerana persekitaran berjalan PHP secara automatik akan mengendalikan kerja keluaran sumber yang berkaitan. Walau bagaimanapun, dalam beberapa kes khas, seperti apabila anda perlu mengeluarkan sumber secara manual atau melakukan beberapa operasi pembersihan, mendengar pembatalan konteks permintaan boleh menjadi cara yang berkesan. Oleh itu, sama ada anda perlu mendengar pembatalan konteks permintaan bergantung pada keperluan perniagaan khusus dan senario pembangunan. Untuk kebanyakan kes, kami boleh bergantung pada mekanisme pengurusan sumber automatik PHP dengan selamat.

Kandungan soalan

Andaikan saya sedang menulis pengendali http yang menjalankan operasi lain sebelum mengembalikan respons, adakah saya perlu menyediakan pendengar untuk menyemak sama ada konteks permintaan http telah dibatalkan? supaya ia boleh kembali serta-merta, atau adakah terdapat cara lain untuk keluar dari pengendali apabila konteks permintaan dibatalkan?

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
}

Saya cuba mengujinya, tetapi selepas konteks dibatalkan, fungsi dosomething tidak berhenti dan masih berjalan di latar belakang.

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
    }
}

Penyelesaian

Saya baru sahaja memfaktorkan semula penyelesaian yang disediakan dan ia sepatutnya berfungsi sekarang. Biar saya membimbing anda melalui perubahan.

dosomething fungsi

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
    }
}

Ia menunggu input pembatalan, atau kembali kepada pemanggil selepas kelewatan selama 3 saat. Ia menerima konteks untuk didengari.

handlesomething fungsi

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())
    }
}

Logik di sini sangat serupa dengan anda. Dalam pilihan kami menyemak sama ada ralat yang diterima ialah nil dan mengembalikan kod status http yang betul kepada pemanggil dengan sewajarnya. Jika kami menerima input batal, kami membatalkan semua rantaian konteks.

testhandler fungsi

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()
}

Di sini kita mulakan pelayan http dan lulus http.client 向它发出 http 请求。可以看到有两条语句来设置上下文超时。如果您使用带有注释 // request canceled maka semuanya akan dibatalkan, sebaliknya jika anda menggunakan yang lain permintaan akan diproses.
Saya harap ini menjelaskan soalan anda!

Atas ialah kandungan terperinci Apabila menulis pengendali http, adakah kita perlu mendengar pembatalan konteks permintaan?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:stackoverflow.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam