Maison  >  Article  >  développement back-end  >  Lors de l'écriture de gestionnaires http, devons-nous écouter l'annulation du contexte de la demande ?

Lors de l'écriture de gestionnaires http, devons-nous écouter l'annulation du contexte de la demande ?

WBOY
WBOYavant
2024-02-08 23:03:281152parcourir

Lors de lécriture de gestionnaires http, devons-nous écouter lannulation du contexte de la demande ?

php Editor Xinyi Lors du traitement des requêtes HTTP, la question de savoir s'il est nécessaire de surveiller l'annulation du contexte de la requête est une question courante. Dans le développement réel, il n'est généralement pas nécessaire de surveiller explicitement l'annulation du contexte de la requête, car l'environnement d'exécution PHP gérera automatiquement le travail de libération des ressources associé. Cependant, dans certains cas particuliers, par exemple lorsque vous devez libérer manuellement des ressources ou effectuer certaines opérations de nettoyage, l'écoute de l'annulation du contexte de demande peut être un moyen efficace. Par conséquent, la nécessité ou non d’écouter l’annulation du contexte de demande dépend des exigences métier spécifiques et des scénarios de développement. Dans la plupart des cas, nous pouvons compter en toute sécurité sur le mécanisme de gestion automatique des ressources de PHP.

Contenu de la question

Supposons que j'écrive un gestionnaire http qui effectue d'autres opérations avant de renvoyer la réponse, dois-je configurer un écouteur pour vérifier si le contexte de la requête http a été annulé ? afin qu'il puisse revenir immédiatement, ou existe-t-il un autre moyen de quitter le gestionnaire lorsque le contexte de la demande est annulé ?

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
}

J'ai essayé de le tester, mais une fois le contexte annulé, la fonction dosomething ne s'arrête pas et s'exécute toujours en arrière-plan.

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

Solution de contournement

Je viens de refactoriser un peu la solution fournie et elle devrait fonctionner maintenant. Laissez-moi vous guider à travers les changements.

dosomething fonction

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

Il attend l'entrée d'annulation ou revient à l'appelant après un délai de 3 secondes. Il accepte un contexte à écouter.

handlesomething fonction

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

La logique ici est très similaire à la vôtre. Dans select, nous vérifions si l'erreur reçue est nil et renvoyons le code d'état http correct à l'appelant en conséquence. Si nous recevons une entrée d'annulation, nous annulons toutes les chaînes de contexte.

testhandler fonction

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

Ici on démarre un serveur http et on passe http.client 向它发出 http 请求。可以看到有两条语句来设置上下文超时。如果您使用带有注释 // request canceled puis tout sera annulé, sinon si vous en utilisez un autre la demande sera traitée.
J'espère que cela clarifie votre question!

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer