Ralph
Ralph

Reputation: 32284

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.

Upvotes: 2

Views: 1541

Answers (2)

Thundercat
Thundercat

Reputation: 120941

Open a websocket connection from the page. On the server, exit when the websocket connection is closed:

func wsHandler(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }
    defer c.Close()

    // Shutdown on exit from handler
    defer server.Shutdown(context.Background())

    // Read  messages from client. NextReader returns an error
    // when the client shuts down.
    for {
        if _, _, err := c.NextReader(); err != nil {
            break
        }
    }
 }

Add this line of code to the client application:

 var conn = new WebSocket("ws://" + document.location.host + "/ws");

Ensure that conn has lifetime of the page.

This is somewhat similar to the SSE solution suggested in another answer, but has the advantage of working in more browsers.

There's no need to send a heartbeat because this is on local host.

Upvotes: 2

Zippo
Zippo

Reputation: 16420

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.

Upvotes: 3

Related Questions