Search code examples
goservermiddleware

golang server middlerware request cancellation


I created in the example at the bottom a little server which is running on port 3000. You can access it over "htto://localhost:3000/time". The whole Request is covered with two middlewares. First "cancelHandler" and Second "otherHandler" is called - which is responding with some dummy data after 4 seconds.

To my problem: When i request the page in a browser and then cancel the request (before the 4sec). The server is still handling the goroutine/request in the background. I spent already hours to find a solution on google but i can just not wrap my head around the context. (context.WithCancel()) I get that i have to create a chan and listen to it but how does this work with the requests. thats already a goroutine, do i have to create another goroutine in the request/goroutine? Also another question is, should i really use Context for that or is there an easier solution with the cancelNotifier?

Maybe someone can describe it for me and others which maybe have the same understanding problem.

Solution should be that the cancel Handler is stopping the goroutine/request, when the browser cancels the request.

Thank you very much for your time!

package main

import (
    "log"
    "net/http"
    "time"
   "fmt"
)

func otherHandler(format string) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(time.Duration(4)*time.Second)
        tm := time.Now().Format(format)
        w.Write([]byte("The time is: " + tm))
        fmt.Println("response:", "The time is: "+tm)

    }
    return http.HandlerFunc(fn)
}

func cancelHandler(h http.Handler) http.Handler {
    fn := func(w http.ResponseWriter, r *http.Request) {
        fmt.Println("start: called cancelHandler")

        h.ServeHTTP(w, r)

        fmt.Println("end: called cancelHandler")

    }
    return http.HandlerFunc(fn)
}

func main() {
    mux := http.NewServeMux()

    th := otherHandler(time.RFC1123)
    mux.Handle("/time", cancelHandler(th))

    log.Println("Listening...")
    http.ListenAndServe(":3000", mux)
}

Solution

  • The only way to "stop" a function is to return from it. Thus, time.Sleep cannot be interrupted. Use a select statement instead:

    package main
    
    import (
        "fmt"
        "net/http"
        "time"
    )
    
    func main() {
        http.ListenAndServe(":3000", otherHandler(time.RFC1123))
    }
    
    func otherHandler(format string) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
                select {
                case <-time.After(4 * time.Second):
                        // time's up
                case <-r.Context().Done():
                        // client gave up
                        return
                }
    
                tm := time.Now().Format(format)
                w.Write([]byte("The time is: " + tm))
                fmt.Println("response:", "The time is: "+tm)
        }
    }
    

    In general, check the request context (or one that is derived from it) in strategic places. If the context is canceled, don't proceed any further and return.