Server Sent Events Using Go

This post was initially written when I was working on Integrand. It appeared in a series of posts. It performed decently; that’s why I’ve decided to plagiarize my own work. Cheers🍻.

Creating a Server-Sent Events (SSE) server in Go (Golang) can be a great way to push real-time updates to web clients and even other backend services. If your application needs an efficient and straightforward tool to implement streaming, SSE is a great tool to reach for.

Golang SSE Server

In this tutorial, we will implement a simple web SSE web server using Go. The only prerequisite will be to have Go installed on your machine.

What are Server-Sent Events(SSE)

Server-Sent Events are real-time events emitted by the server. Server-sent events are unidirectional; that is, data messages are delivered in one direction, from the server to the client. The client only has to make a single request to the server to recived data. This differs from other real time technologies like WebSockets which is bi-directional. SSE is also lightweight and easier to implement than WebSockets or even long polling.

When using a client in the web browser, we can use the the eventSource interface which is built into the Web API. The eventSource interface is supported by all modern browsers.

Implementing the Go SSE Webserver

We will be setting up a simple HTTP server in Go that will emit events. We will also create a simple web/browser client to consume and display the messages.

Step 1: Set Up the Go Web Server

Set up a basic HTTP server, we will use the net/http library so we don’t have to bring any extra dependencies.

package main

import (
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/events", sseHandler)
	log.Fatal(http.ListenAndServe(":8000", nil))
}

Here we have defined an HTTP server that listens on port 8000 and has a single endpoint, /events, which will handle SSE connections.

Step 2: Implement the SSE Handler

Right now our route does nothing. We need to create a handler function that will implement SSE logic when a client connects to this endpoint.

func sseHandler(w http.ResponseWriter, r *http.Request) {
	// Set the correct headers so our clients can process the events
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.Header().Set("Connection", "keep-alive")

	// Check if SSE is supported...
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "SSE not supported", http.StatusInternalServerError)
		return
	}

	// Simulate sending events (you can replace this with real data)
	for i := 0; i < 10; i++ {
		fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("Event %d", i))
		time.Sleep(1 * time.Second)
		flusher.Flush()
	}
}

Our code sends messages based on a loop. Please note that we are formatting the message in a way that our client will be able to handle it, using the data prefix. For a deeper understanding read more about the Event Stream format.

Our complete server side code looks like:

package main

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

func main() {
	http.HandleFunc("/events", sseHandler)
	log.Fatal(http.ListenAndServe(":8000", nil))
}

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

	// Check if SSE is supported...
	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "SSE not supported", http.StatusInternalServerError)
		return
	}

	// Simulate sending events (you can replace this with real data)
	for i := 0; i < 10; i++ {
		fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("Event %d", i))
		time.Sleep(2 * time.Second)
		flusher.Flush()
	}
}

Step 3: Creating the Client(s)

Any client that speaks http can consume this endpoint provided by the webserver. We will be demoing consuming this endpoint via the browser client and curl which can emulate a client server side.

Web Client

Create an index.html file that will connect to this server and listen for events:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SSE Test</title>
</head>
<body>
    <h1>Server-Sent Events Test</h1>
    <div id="events"></div>

    <script>
        var eventSource = new EventSource('/events');
        eventSource.onmessage = function(event) {
			// Every time we recieve a message, let's add it to the dom
            var newElement = document.createElement("div");
            newElement.textContent = "New event: " + event.data;
            document.getElementById("events").appendChild(newElement);
        };
    </script>
</body>
</html>

This HTML page makes a new EventSource connection to the /events endpoint on your server and listens for messages. When a message is received, it adds the message to the page.

Curl Client

Curl is a super flexible tool that we can use to test our SSE endpoint. For quickly testing an endpoint or simulating the connection server side, this is the tool to reach for.

curl --header "Accept:text/event-stream" \
  --request GET -N  \
  http://localhost:8000/events

Running the Example

  1. Run your Go server by executing go run main.go.
  2. Open index.html in a browser to see live messages from the server.
  3. Open a terminal window and execute the curl command

Conclusion

This tutorial provided a basic introduction to using SSE in a Go application to send live updates to a client. SSE is a powerful, yet simple solution for when you need real-time communication between the server and the client without the complexity of WebSockets.

Have questions about what you read?
Get in touch now