Golang Server Sent Events - Does Server Sent Event Handler Continually Call Itself?

Viewed 592

I'm learning about server sent events and I've a simple program that increments a counter variable and pushes to client. But I don't understand something. I'm told that data is only pushed to the client. Not pulled or requested from client. So what happens in the below code. Does the /sse/dashboard handler call itself every second. Because that's how it would appear. How does it know to continually 'call itself'? Below is my server side code, followed by my client side code:

Server Side:

package main

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

var counter int

func main() {
    http.Handle("/", http.FileServer(http.Dir("client")))
    http.Handle("/sse/dashboard", http.HandlerFunc(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")
        counter++
        fmt.Fprintf(w, "data: %v\n\n", counter)
    }))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Client Side:

<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
</head>

<body>
  <h1>Counter: <span id="counter_val"></span></h1>
  <script>
    var source = new EventSource("/sse/dashboard");
    source.onmessage = function (event) {
        var counter = JSON.parse(event.data);
        document.getElementById("counter_val").innerHTML = counter;
    }
  </script>
</body>
</html>
1 Answers

The handler is only invoked when a client issues a request to /sse/dashboard. A handler does not invoke itself.

The client's EventSource automatically reconnects after the server closes the event stream. The server close the event stream after the first message. You see an incrementing counter because the client reconnects every second or two.

Use a loop to send incrementing counter events. Return from the handler when the client disconnects. Flush the output buffered by the response writer to the network. Use a mutex to prevent the data race on counter.

 var (
     mu sync.Mutex
     counter int
 )


 ...

http.Handle("/sse/dashboard", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    flusher, ok := w.(http.Flusher)
    if !ok {
        http.Error(w, "Internal error", 500)
        return
    }
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")
    t := time.NewTicker(time.Minute)
    defer t.Stop()
    for {
        select {
        case <-t.C:
            mu.Lock()
            counter++
            c := counter
            mu.Unlock()
            fmt.Fprintf(w, "data: %v\n\n", c)
            flusher.Flush()
        case <-r.Context().Done():
            return
        }
    }
}))
Related