Detecting Closed Connections in Golang HTTP Handlers

Edit: I don’t know where my mind was when I wrote the post below, but the most obvious way to detect a closed connection is of course to use the context that net/http already has put in the request. See here for an example: https://gobyexample.com/context .

In Go we write handler functions to gather and send out the data we want in the response to an HTTP request. In that handler function, we get a ResponseWriter and we write to it. When we’re done, we return from the function. Simple as that.

In the case of a streaming response, it gets a little bit trickier. Our handler function sends data.. and sends data… and sends data… and it does not know that the user already closed the browser tab, or the API client had enough and closed the connection. This can fill up your server pretty quick and can cause severe performance problems.

When writing a handler that takes a long time complete, or does not ever complete, one can detect when the underlying connection is closed by using a CloseNotifier. From the documentation:

// CloseNotify returns a channel that receives at most a single value (true) when the client connection has gone away.

ResponseWriters implement this CloseNotifier interface, so we can get that channel in our handler and listen to it:

func StreamHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("opened streaming connection")
	cn, ok := w.(http.CloseNotifier)
	if !ok {
		http.Error(w, "cannot stream", http.StatusInternalServerError)
		return
	}

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "expected http.responsewriter to be an http.flusher", http.StatusInternalServerError)
	}

	ticker := time.NewTicker(StreamingInterval)
	for {
		select {
		case <-cn.CloseNotify():
			log.Println("closed connection")
			return
		case <-ticker.C:

			// do some stuff, write to w

			flusher.Flush()
			log.Println("sent some data")
		}
	}
}

Sources