When building real-time systems, one must consider what kind of delivery mechanism we should use when it comes to data delivery from the server to the client. We are limited to two general approaches
- Client Pull
- Server Push
In this post, I’m going to focus on particular kind of server push called Server Sent Events(SSE). Server-Sent Events are real-time events emitted by the server and received by the browser. They’re similar to Web-sockets in that they happen in real time, but they’re very much a one-way communication method from the server. Server-Sent Events utilize a regular HTTP octet streams, and therefore are limited to the browser’s connection pool limit of ~6 concurrent HTTP connections per server. But they provide a standard way of pushing data from the server to the clients over HTTP, which load balancers and proxies understand out-of-the-box. The biggest advantage being that, exactly as WebSockets, they utilize only one TCP connection. The biggest disadvantage is that Server-Sent Events don’t provide a mechanism to detect dropped clients until a message is sent.
Go gives you a low level API that you can use in order to implement server sent events. You can cast http.ResponseWriter in to a http.Flusher. All you need to do is write to the response-writer in the right format and after you are done call flusher.Flush().
The first step is to define the connection which contains request, response, buffered channel of outbound messages and namespace.
BroadcastMessage is the event loop that attempts to send all messages on the active HTTP Connection. It will detect if the HTTP Connection is closed and auto exit. It will also exit if the Connection’s send channel(i.e. outboundMessage) is closed (indicating a shutdown). The response is a response writer itself which only required to comply with the basic http.ResponseWriter interface, but for a streaming server we also need to make sure that it supports the http.Flusher interface (so we can flush buffered data down the connection as it comes).We also set up a Keep-Alive tick to prevent connections from being closed by a timeout, Any SSE message beginning with the colon will be ignored, so we use it to determine whether the client has been disconnected or not.
A Broker keeps track of all the active client Connections, and handles the broadcasting messages out to those Connections that matches the appropriate namespace.
Our Server will implement Go’s http.Handler interface to handle HTTP connections. This will allow it to interop with other HTTP handlers such as routers or middleware, etc.
SSEServer is the primary interface of our server. It Exposes a receive-only channel `OutboundChannel`. Any SSEMessage can sent to this channel will be broadcast out to any connected clients subscribed to a namespace that matches the message. The Server implements the http.Handler interface, and can be chained into existing HTTP routing muxes if desired.
Now we’ll set basic headers to support keep-alive HTTP connections (so clients don’t close them early) and the “text/event-stream” content type for browsers that support Server Sent Events via the EventSource API. We’ll also add a Cross-origin Resource Sharing header so browsers on different domains can still connect.
The respective event is passed to the Outbound channel which will be broadcast to all HTTP connection via Broker.
We can connect with curl to an endpoint directly and just read what’s going on.
Also, We can try in EventSource compliant web browser.
The full working code is here
- Writing SSE in go is pretty much fun and it’s so simple, Also SSE is trivially easier to understand, plain-text format. Can be debugged with
- Supported in most major browsers for a long time now. Everything except IE/Edge, but an easy polyfill!
- Works with HTTP/2.
- Built-in standard support for automatic re-connection, event binding, etc.