DEV Community

Cover image for Golang and Server-Sent Events (SSE)
Rafael Firmino
Rafael Firmino

Posted on • Edited on

Golang and Server-Sent Events (SSE)

Server-Sent Events (SSE) is a server push technology enabling a client to receive automatic updates from a server via HTTP connection, and describes how servers can initiate data transmission towards clients once an initial client connection has been established.
Server Sent Event is a good choice when we need to notify the browser.
-- wikipedia

If you're working with Amazon API Gateway and WebSocket in order to notify your clients this connection can be more expensive.

SSE can be a good choice when our application needs to send a message to it's clients.

This is a simple example how we can implementing SSE in Go.

//main.go
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "math/rand"
    "net/http"
    "time"

    "github.com/gofiber/adaptor/v2"
    "github.com/gofiber/fiber/v2"
)

type Client struct {
    name   string
    events chan *DashBoard
}
type DashBoard struct {
    User uint
}

func main() {
    app := fiber.New()
    app.Get("/sse", adaptor.HTTPHandler(handler(dashboardHandler)))
    app.Listen(":3000")
}

func handler(f http.HandlerFunc) http.Handler {
    return http.HandlerFunc(f)
}
func dashboardHandler(w http.ResponseWriter, r *http.Request) {
    client := &Client{name: r.RemoteAddr, events: make(chan *DashBoard, 10)}
    go updateDashboard(client)

    w.Header().Set("Access-Control-Allow-Origin", "*")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    w.Header().Set("Content-Type", "text/event-stream")
    w.Header().Set("Cache-Control", "no-cache")
    w.Header().Set("Connection", "keep-alive")

    timeout := time.After(1 * time.Second)
    select {
    case ev := <-client.events:
        var buf bytes.Buffer
        enc := json.NewEncoder(&buf)
        enc.Encode(ev)
        fmt.Fprintf(w, "data: %v\n\n", buf.String())
        fmt.Printf("data: %v\n", buf.String())
    case <-timeout:
        fmt.Fprintf(w, ": nothing to sent\n\n")
    }

    if f, ok := w.(http.Flusher); ok {
        f.Flush()
    }
}

func updateDashboard(client *Client) {
    for {
        db := &DashBoard{
            User: uint(rand.Uint32()),
        }
        client.events <- db
    }
}

Enter fullscreen mode Exit fullscreen mode
// JS client
const source = new EventSource("http://localhost:3000/sse")
      source.onmessage = (event) => {
        console.log("OnMessage Called:")
        console.log(event)
        console.log(JSON.parse(event.data))
      }
Enter fullscreen mode Exit fullscreen mode

Repository link

Oldest comments (5)

Collapse
 
dev117uday profile image
Uday Yadav

Nice

Collapse
 
danwick profile image
wukong0111

Thanks for the post.
Why are you doing a keepalive every second?

Collapse
 
tuptaker profile image
Fabrizio Machado

My experience is if your server is behind a load balancer, the load balancer may sever idle connections after a short timeout, so the "no-op" keepalive gets pushed every second to prevent things like load balancers and gateways from terminating the connection.

Collapse
 
der_gopher profile image
Alex Pliutau

Great write up! Does anyone use Server-Sent Events in their projects? If yes, for which use cases? This video dives into the main building blocks of Server-Sent Events in Go.
youtu.be/nvijc5J-JAQ

Collapse
 
rafaelgfirmino profile image
Rafael Firmino

Hi Alex, first of all, thanks for commenting—it’s such a pleasure to have you in the comments! 🙌 I watch a lot of your videos here in Brazil (I’m already subscribed to your channel).

Unfortunately, I don’t use SSE with Go; My company uses C#. But since I’m really into Go, I wrote this article on Go. We use SSE to handle requested file downloads, where the files take a while to be assembled and made available to our client.