javascriptgobackendserver-sent-events

Trigger Server Sent Event every 2 seconds in golang


My server code in golang

package main

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

    "github.com/rs/cors"
)

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

    mux.HandleFunc("/sse" , handleSse)

    c  := cors.New(cors.Options{
            AllowedOrigins:   []string{"*"},
            AllowedMethods:   []string{http.MethodGet, http.MethodPost,
                        http.MethodDelete , http.MethodPut},
            AllowCredentials: true,
        })

    handler := c.Handler(mux)

    log.Fatal(http.ListenAndServe(":6969" , handler))
}

func handleSse(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")

    f , ok := w.(http.Flusher);
    if !ok{
    http.Error( w , "SSE not supported" ,
        http.StatusBadRequest)
    return;
    }

    fmt.Fprintf(w,"data:%v\n\n","sample data");
    f.Flush();
}

client side code

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>SSE</title>
  </head>
  <body>
    SSE running
    <script>
    const event = new EventSource("http://localhost:6969/sse");
    event.onmessage = () =>{
        console.log("this dude is slow");
    };
    </script>
  </body>
</html>

The problem I have that in network tab new text stream or response comes after 5.4 sec later. I want server to send response every 2 second I have tried infinite for loop in server code which is shown in some tutorial and it doesn't works

EDIT : The for loop handler function


func handleSse(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")
    w.WriteHeader(http.StatusOK)

    f , ok := w.(http.Flusher);
    if !ok{
    http.Error( w , "SSE not supported , IE6 bruh" ,
        http.StatusBadRequest)
    return;
    }

    for i := 0 ; i < 10 ; i++{
        fmt.Fprintln(w,"retry : 1000"); //This line also doesnot help
        fmt.Fprintf(w,"data :%v\n\n","Sorry");
        f.Flush();
    //time.Sleep(1 * time.Second) //This line increase delay to 25 secs
    }
}

Solution

  • The spacing is important - fmt.Fprintf(w,"data :%v\n\n","Sorry"); will not work; it needs to be fmt.Fprintf(w, "data:%v\n\n", "Sorry").

    Following is a working example; it will send 10 messages and then drop the connection. Because it also sets retry: 10000 (retry in 10000ms i.e. 10s) the browser will reconnect after 10 seconds (and receive another 10 mesages a second apart). Note that I added a timestamp to the messages (makes the output clearer because browsers tend to combine identical output lines).

    package main
    
    import (
        "fmt"
        "log"
        "net/http"
        "time"
    
        "github.com/rs/cors"
    )
    
    func main() {
        mux := http.NewServeMux()
    
        mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
            w.Write([]byte(`<!DOCTYPE html>
    <html lang="en">
      <head>
        <title>SSE</title>
      </head>
      <body>
        SSE running
        <script>
        const event = new EventSource("http://localhost:6969/sse");
        event.onmessage = () =>{
            console.log("this dude is slow");
        };
        </script>
      </body>
    </html>
    `))
        })
    
        mux.HandleFunc("/sse", handleSse)
    
        c := cors.New(cors.Options{
            AllowedOrigins: []string{"*"},
            AllowedMethods: []string{http.MethodGet, http.MethodPost,
                http.MethodDelete, http.MethodPut},
            AllowCredentials: true,
        })
    
        handler := c.Handler(mux)
    
        log.Fatal(http.ListenAndServe(":6969", handler))
    }
    
    func handleSse(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")
        w.WriteHeader(http.StatusOK)
    
        f, ok := w.(http.Flusher)
        if !ok {
            http.Error(w, "SSE not supported , IE6 bruh",
                http.StatusBadRequest)
            return
        }
    
        for i := 0; i < 10; i++ {
            fmt.Fprintln(w, "retry: 10000") // This means that after the connection drops the browser will wait 10 seconds before reconnecting
            fmt.Fprintf(w, "data:%v\n\n", "Sorry")
            f.Flush()
            time.Sleep(1 * time.Second) // This means a message will be sent every second (until 10 have been sent when the connection will drop)
        }
    }
    

    In a real system, the handler (handleSse in this case) may well only exit if it receives an error when writing, otherwise it will stay alive as long as the program is running. Such a handler will receive data from somewhere, perhaps via a channel, and send it to the browser.

    Note that my original suggestion to add fmt.Fprintln(w, "retry: 1000") was an attempt to make it clearer what was happening. With your initial code the browser was connecting, receiving a single message (after which the server dropped the connection), and then reconnecting after 5 seconds (to receive another single message...).