Search code examples
goshutdown

Shutting down a Go server on browser close


I wrote a small checkbook ledger in Go as a server that runs in localhost and opens the default browser to a small web app front-end (https://bitbucket.org/grkuntzmd/checks-and-balances).

To automatically shut down the server when the browser tab is closed, I have the browser call a "heartbeat" URL every few seconds. If that heartbeat does not arrive, the server uses (*Server) Shutdown to stop running.

Is there any way to do the same thing using contexts (https://golang.org/pkg/context/)? It is my understanding from watching this episode of JustForFunc that a context passed down to a handler will be notified if the client cancels a request.


Solution

  • Instead of sending a "heartbeat" request every so often, you could take advantage of server-sent events.

    Server-sent events is a web technology where the browser makes a HTTP request and keeps the connection open to receive events from the server. This could replace your need for repeated heartbeat requests by having the server shutdown when the connection to the event source is closed.

    Here's a basic server implementation in Go:

    package main
    
    import (
        "fmt"
        "log"
        "net/http"
        "time"
    )
    
    func main() {
        http.HandleFunc("/heartbeat", func(w http.ResponseWriter, r *http.Request) {
            w.Header().Set("Content-Type", "text/event-stream")
            w.Header().Set("Cache-Control", "no-cache")
            w.Header().Set("Connection", "keep-alive")
    
            flusher, ok := w.(http.Flusher)
            if !ok {
                http.Error(w, "your browser doesn't support server-sent events")
                return
            }
    
            // Send a comment every second to prevent connection timeout.
            for {
                _, err := fmt.Fprint(w, ": ping")
                if err != nil {
                    log.Fatal("client is gone, shutting down")
                    return
                }
                flusher.Flush()
                time.Sleep(time.Second)
            }
        })
        fmt.Println(http.ListenAndServe(":1323", nil))
    }
    

    See using server-sent events for a guide on the client side.