Heim >Backend-Entwicklung >Golang >Müssen wir beim Schreiben von HTTP-Handlern auf die Stornierung des Anforderungskontexts achten?

Müssen wir beim Schreiben von HTTP-Handlern auf die Stornierung des Anforderungskontexts achten?

WBOY
WBOYnach vorne
2024-02-08 23:03:281221Durchsuche

Müssen wir beim Schreiben von HTTP-Handlern auf die Stornierung des Anforderungskontexts achten?

php Editor Xinyi Bei der Verarbeitung von HTTP-Anforderungen stellt sich häufig die Frage, ob die Löschung des Anforderungskontexts überwacht werden muss. In der tatsächlichen Entwicklung besteht normalerweise keine Notwendigkeit, die Stornierung des Anforderungskontexts explizit zu überwachen, da die PHP-Laufumgebung die damit verbundene Arbeit zur Ressourcenfreigabe automatisch übernimmt. In einigen Sonderfällen, beispielsweise wenn Sie Ressourcen manuell freigeben oder einige Bereinigungsvorgänge durchführen müssen, kann das Abhören des Anforderungskontexts jedoch eine effektive Möglichkeit sein. Daher hängt es von den spezifischen Geschäftsanforderungen und Entwicklungsszenarien ab, ob Sie auf die Stornierung des Anforderungskontexts warten müssen. In den meisten Fällen können wir uns getrost auf den automatischen Ressourcenverwaltungsmechanismus von PHP verlassen.

Frageninhalt

Angenommen, ich schreibe einen HTTP-Handler, der andere Vorgänge ausführt, bevor die Antwort zurückgegeben wird. Muss ich einen Listener einrichten, um zu überprüfen, ob der HTTP-Anforderungskontext abgebrochen wurde? damit es sofort zurückkehren kann, oder gibt es eine andere Möglichkeit, den Handler zu verlassen, wenn der Anforderungskontext abgebrochen wird?

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
}

Ich habe versucht, es zu testen, aber nachdem der Kontext abgebrochen wurde, stoppt die dosomething-Funktion nicht und läuft weiterhin im Hintergrund.

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

Workaround

Ich habe gerade die bereitgestellte Lösung ein wenig überarbeitet und es sollte jetzt funktionieren. Lassen Sie sich von mir durch die Veränderungen führen.

dosomething Funktion

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

Es wartet auf die Stornierungseingabe oder kehrt nach einer Verzögerung von 3 Sekunden zum Anrufer zurück. Es akzeptiert einen Kontext zum Anhören.

handlesomething Funktion

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

Die Logik hier ist Ihrer sehr ähnlich. Bei der Auswahl prüfen wir, ob der empfangene Fehler nil ist und geben dem Anrufer entsprechend den richtigen http-Statuscode zurück. Wenn wir eine Abbrucheingabe erhalten, brechen wir alle Kontextketten ab.

testhandler Funktion

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

Hier starten wir einen http-Server und übergeben http.client 向它发出 http 请求。可以看到有两条语句来设置上下文超时。如果您使用带有注释 // request canceled dann wird alles abgebrochen, andernfalls wird die Anfrage bearbeitet, wenn man einen anderen verwendet.
Ich hoffe, das klärt Ihre Frage!

Das obige ist der detaillierte Inhalt vonMüssen wir beim Schreiben von HTTP-Handlern auf die Stornierung des Anforderungskontexts achten?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen