How to implement Server-Sent Events in Go
Building Real-Time Applications with Efficient, Unidirectional Communication
Introduction
Server-Sent Events (SSE) is a powerful technology that enables real-time, unidirectional communication from servers to clients. In this article, we'll explore how to implement SSE in Go, discussing its benefits, use cases, and providing practical examples.
What are Server-Sent Events?
SSE is a web technology that allows servers to push data to clients over a single HTTP connection.
Unlike WebSockets, SSE is unidirectional, making it simpler to implement and ideal for scenarios where real-time updates from the server are required, but client-to-server communication is not necessary.
Developing a web application that uses SSE is straightforward. You'll need a bit of code on the server to stream events to the front-end, but the client side code works almost identically to websockets in part of handling incoming events. This is a one-way connection, so you can't send events from a client to a server.
Benefits of SSE
Simplicity: SSE is easier to implement compared to WebSockets.
Native browser support: Most modern browsers support SSE out of the box.
Automatic reconnection: Clients automatically attempt to reconnect if the connection is lost.
Efficient: Uses a single HTTP connection, reducing overhead.
Implementing SSE in Go
Let's create a simple SSE server in Go:
package main
import (
"fmt"
"net/http"
"time"
)
func sseHandler(w http.ResponseWriter, r *http.Request) {
// Set http headers required for SSE
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
// You may need this locally for CORS requests
w.Header().Set("Access-Control-Allow-Origin", "*")
// Create a channel for client disconnection
clientGone := r.Context().Done()
rc := http.NewResponseController(w)
t := time.NewTicker(time.Second)
defer t.Stop()
for {
select {
case <-clientGone:
fmt.Println("Client disconnected")
return
case <-t.C:
// Send an event to the client
// Here we send only the "data" field, but there are few others
_, err := fmt.Fprintf(w, "data: The time is %s\n\n", time.Now().Format(time.UnixDate))
if err != nil {
return
}
err = rc.Flush()
if err != nil {
return
}
}
}
}
func main() {
http.HandleFunc("/events", sseHandler)
fmt.Println("server is running on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println(err.Error())
}
}
Event Stream Format
Each event is sent as a block of text terminated by a pair of newlines - \n\n
The server-side script that sends events needs to respond using the MIME type text/event-stream
You can add comments for debugging. A colon as the first character of a line is in essence a comment, and is ignored.
Each message received has some combination of the following fields, one per line:
event - A string identifying the type of event described.
data - The data field for the message.
id - The event ID to set the EventSource object's last event ID value.
retry - The reconnection time.
On the frontend side, you will have to use something called EventSource:
<!doctype html>
<html>
<body>
<ul id="list"></ul>
</body>
<script type="text/javascript">
const eventSrc = new EventSource("http://127.0.0.1:8080/events");
const list = document.getElementById("list");
eventSrc.onmessage = (event) => {
const li = document.createElement("li");
li.textContent = `message: ${event.data}`;
list.appendChild(li);
};
</script>
</html>
More details here. Here is how it may look in your browser console:
Key Components of the SSE Implementation
Headers: We set specific headers to establish an SSE connection.
Event Loop: The server continuously sends events to the client.
Flushing: We use
http.Flusher
to ensure data is sent immediately.Client Disconnection: We handle client disconnection gracefully.
Best Practices for SSE in Golang
Error Handling: Implement robust error handling for connection issues.
Event Formatting: Use a structured format for your events (e.g., JSON).
Reconnection Strategy: Implement a backoff strategy for client reconnections.
Load Balancing: Consider load balancing for high-traffic applications.
Use Cases for SSE
Real-time dashboards
Live sports scores
Social media feeds
Stock market tickers
Progress indicators for long-running tasks
Conclusion
Server-Sent Events provide an efficient and straightforward way to implement real-time, server-to-client communication in Golang applications. By leveraging SSE, developers can create responsive and dynamic web applications with minimal overhead and complexity.
As you build your SSE-powered applications, remember to consider scalability, error handling, and client-side implementation to ensure a robust and efficient real-time communication system.
Sponsor
Stream is a DevTools company focused on making real-time chat, video, and activity feeds integrations as simple as possible. We take care of the hard part, allowing developers to focus on the parts of their app that help them stand out by providing them with an intuitive API and a rich set of UI components across all popular UI frameworks.
We are looking for Go engineers to join our backend team to help build and scale our systems to a global audience. Our stack is made up of GO, RocksDB, Redis, WebRTC and more. Check out one of our many backend opening to find your fit.
Beautiful article. Concise to the point but with plenty of information.
I've learned about SSE events a while ago but never thought it was so easy implementing it.
I'll definitely use this article as reference in the future.