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.
Watch a video on Server-Sent Events on our YouTube Channel.
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.