Hey Gophers! Have you ever encountered scenarios like these:
- You're developing a backend monitoring system and want to display real-time data like CPU usage and memory consumption on the frontend, but the only way is to have the frontend send a request every few seconds, exhausting the server?
- You want to build an information feed similar to Facebook or Twitter, where new messages are instantly "dinged" and pushed to the user's page, instead of waiting for them to scratch their heads and manually refresh?
- Or, you simply want to notify a user: "Your delivery has been picked up by [Handsome John Doe] and is speeding your way!", rather than having them stare anxiously at the order page?
If you nodded to any of the questions above, then congratulations, you've probably been using the old method of "polling." It's like sending a subordinate to the kitchen every five seconds to ask, "Is the food ready yet?". Not only does the subordinate run their legs off, but the chef gets annoyed too.
Isn't there a more elegant way? Of course, there is! Today's star is Server-Sent Events (SSE), and it's here to save the day! And the Go SSE library we're about to introduce will give you this superpower with "one click"!
What is SSE? How is it different from WebSocket?
Before diving into the code, let's explain the principle in plain language.
SSE (Server-Sent Events), as the name suggests, are "events sent by the server." It's built on a standard HTTP connection, but this connection is a "long-lived" and unidirectional one.
Think of it as a radio broadcast:
- The server is the radio station that broadcasts 24/7.
- The client (browser) is the radio.
Once you tune your radio to the right channel (establish a connection), the station (server) can send you news and music (data) at any time, and you don't need to call every minute to ask, "Are there any new programs?".
So, how is it different from WebSocket?
- SSE: It's a one-way street. Only the server can push data to the client. It's simple, lightweight, based on standard HTTP, and natively supports auto-reconnect. It's perfect for scenarios that only require server-to-client information pushing.
- WebSocket: It's a two-way highway. The client and server can "shout" at each other at any time. It's more powerful, but the protocol is also more complex. It's suitable for scenarios like online chat and collaborative editing that require frequent two-way communication.
In summary, if your requirement is one-way notification from "server -> client," then SSE is the simpler, more appropriate "wheel" for the job.
What features does this Go library offer?
There are many SSE libraries on the market, but many only offer basic functionality. This sse
library, however, is incredibly thoughtful, like an all-in-one butler:
- High Performance: Excellent underlying design, capable of easily managing thousands of client connections.
- Automatic Reconnection: Network jitter? User accidentally closed and reopened the page? No worries! The library has built-in mechanisms for automatic reconnection and event resending, ensuring no important messages are lost! (Requires persistent storage).
- Message Persistence: You can store historical events in Redis, MySQL, or anywhere you like. Mom no longer has to worry about losing messages after a server restart.
- Built-in Heartbeats: Automatically detects "zombie connections" and cleans them up in time, keeping the connection pool healthy.
- Broadcast and Unicast: You can "whisper" to one or more specific users, or "shout" a broadcast to all online users.
Sounds cool, right? Just wait, seeing the code is even cooler!
Get Started in Three Minutes: Build Your First SSE Service
Let's use a simple example to see how easy it is to quickly set up a service with the sse
library. Suppose we want to build a service that broadcasts "Hello World" to all clients every 5 seconds.
1. Server-side Code (server.go
)
You'll need a Go environment and the Gin framework installed (this example uses Gin, but you can also use Go's native net/http
).
go get github.com/gin-gonic/gin
go get github.com/go-dev-frame/sponge/pkg/sse
Then, create a main.go
file:
package main
import (
"fmt"
"math/rand"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/go-dev-frame/sponge/pkg/sse"
)
func main() {
// 1. Initialize our SSE "Broadcast Center" (Hub)
// Think of it as the main control room of the radio station
hub := sse.NewHub()
defer hub.Close()
// 2. Create a web server with Gin
r := gin.Default()
// 3. Create a "/events" endpoint for clients to "tune in"
r.GET("/events", func(c *gin.Context) {
fmt.Println("New listener joined!")
// For demonstration, we assign a random ID to each connecting client
uid := strconv.Itoa(rand.Intn(999) + 1000)
hub.Serve(c, uid)
})
// 4. [Optional] Create an endpoint to manually trigger a broadcast
// You can test it with a curl command:
// curl -X POST -H "Content-Type: application/json" -d '{"events":[{"event":"message","data":"This is a manual broadcast!"}]}' http://localhost:8080/push
r.POST("/push", hub.PushEventHandler())
// 5. Start a tireless "broadcaster" (goroutine)
go func() {
i := 0
for {
// Prepare a new message every 5 seconds
time.Sleep(time.Second * 5)
i++
msg := "Hello everyone, this is automatic broadcast number " + strconv.Itoa(i) + "!"
// Create a standard event
event := &sse.Event{
Event: "message", // Event type, can be customized
Data: msg,
}
// Call hub.Push to broadcast (passing nil for uids means broadcasting to everyone)
fmt.Printf("Broadcasting: %s\n", msg)
_ = hub.Push(nil, event)
}
}()
fmt.Println("SSE server started at http://localhost:8080")
// Start the server
if err := http.ListenAndServe(":8080", r); err != nil {
panic(err)
}
}
See? It's super clear! Initialize Hub -> Create connection point -> Push message. Done!
2. Client-side Code (client.go
)
Now, we need a "radio" to receive the messages. This library also provides a client implementation, which is very convenient.
package main
import (
"fmt"
"github.com/go-dev-frame/sponge/pkg/sse"
)
func main() {
url := "http://localhost:8080/events"
// 1. Create an SSE client pointing to our server address
client := sse.NewClient(url)
// 2. Register an event listener
// Tell the client: "Once you receive an event of type 'message', execute the following function"
client.OnEvent("message", func(event *sse.Event) {
// event.Data is the message content we receive from the server
fmt.Printf("Received a new broadcast! Content: [%s], ID: %s\n", event.Data, event.ID)
})
// 3. Start connecting!
err := client.Connect()
if err != nil {
fmt.Printf("Connection failed, oh no: %v\n", err)
return
}
fmt.Println("Radio is on, waiting for broadcast... (Press Ctrl+C to exit)")
// Block the main program, waiting for the client to exit
<-client.Wait()
}
Now, first run go run server.go
, then open another terminal and run go run client.go
.
You will see that the client prints a new message from the server every 5 seconds, without the client needing to do anything extra! That's the magic of SSE!
Of course, you can also use other clients for testing.
Advanced Usage: Make Your SSE Service More Powerful
The power of the sse
library goes far beyond this.
Scenario 1: I don't want to lose a single message!
Imagine your service is pushing critical stock prices. If a client disconnects for 10 seconds due to network issues, they could miss out on a fortune!
This is where persistent storage and event resending come into play.
You just need to implement a simple Store
interface to tell the sse
library how to save and read events (e.g., using Redis).
// Pseudo-code: Implement your own Store
type MyRedisStore struct{ /* ... redis client ... */ }
func (s *MyRedisStore) Save(ctx context.Context, e *sse.Event) error {
// Serialize the event into JSON and save it to a Redis List or ZSet
return nil
}
func (s *MyRedisStore) ListByLastID(ctx context.Context, eventType string, lastID string, pageSize int) ([]*sse.Event, string, error) {
// Based on the lastID received by the client, query for new events from Redis
return events, nextLastID, nil
}
// When initializing the Hub, include your storage and resend configuration
hub := sse.NewHub(
sse.WithStore(&MyRedisStore{}), // Use your Redis storage
sse.WithEnableResendEvents(), // Enable the disconnect-and-resend feature!
)
It's that simple! Now, when a client disconnects and reconnects, it will automatically include the ID of the last message it received. The server, upon seeing this, will fetch all the missed messages from your Redis and send them all at once. The fortune is saved!
Scenario 2: I want to know if a message was successfully delivered.
Sometimes, you want to know if a message pushed to a specific user failed (for example, if that user has gone offline). You can set up a "failure callback function."
failedHandler := func(uid string, event *sse.Event) {
// The code here will be executed when a push fails
log.Printf("Oops, failed to push message %s to user %s! You can log it and retry later.", uid, event.ID)
}
hub := sse.NewHub(sse.WithPushFailedHandleFn(failedHandler))
This way, you can log, alert, or perform other compensatory actions for failed push events.
Summary
Server-Sent Events (SSE) is a powerful tool for building modern real-time applications. Especially when dealing with one-way data streams from the server to the client, it is lighter and simpler than WebSocket.
And this sse
library is like a well-equipped Swiss Army knife. It not only provides the core functionality of SSE but also thoughtfully prepares a series of "deluxe features" for you, such as persistence, auto-reconnection, failure handling, and performance monitoring. It frees developers from the tedious tasks of connection management and exception handling, allowing them to focus on implementing business logic.
So, the next time your product manager comes up with a "real-time update" requirement, don't frown and write polling code anymore. Confidently puff out your chest and tell them, "No problem, I'll get it done in minutes!" Then, gracefully import "github.com/go-dev-frame/sponge/pkg/sse"
and let the magic happen!
GitHub Address: Sponge SSE
Top comments (0)