Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mroth/sseserver
:surfer: High-performance Server-Sent Events endpoint for Go
https://github.com/mroth/sseserver
eventsource go golang server sse streaming
Last synced: 3 months ago
JSON representation
:surfer: High-performance Server-Sent Events endpoint for Go
- Host: GitHub
- URL: https://github.com/mroth/sseserver
- Owner: mroth
- License: agpl-3.0
- Created: 2014-07-27T19:03:52.000Z (over 10 years ago)
- Default Branch: master
- Last Pushed: 2024-01-02T20:21:31.000Z (10 months ago)
- Last Synced: 2024-05-22T18:33:02.879Z (6 months ago)
- Topics: eventsource, go, golang, server, sse, streaming
- Language: Go
- Homepage:
- Size: 141 KB
- Stars: 107
- Watchers: 9
- Forks: 15
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# sseserver :surfing_man:
[![PkgGoDev](https://pkg.go.dev/badge/github.com/mroth/sseserver)](https://pkg.go.dev/github.com/mroth/sseserver)
[![Build Status](https://github.com/mroth/sseserver/workflows/test/badge.svg)](https://github.com/mroth/sseserver/actions)
[![CodeFactor](https://www.codefactor.io/repository/github/mroth/sseserver/badge)](https://www.codefactor.io/repository/github/mroth/sseserver)
[![Go Report Card](https://goreportcard.com/badge/github.com/mroth/sseserver)](https://goreportcard.com/report/github.com/mroth/sseserver)> A high performance and thread-safe Server-Sent Events server for Go with
_hierarchical namespacing_ support.This library has powered the streaming API endpoint for
:dizzy: [Emojitracker](https://emojitracker.com) in production since 2014, where
it routinely handles dispatching hundreds of messages per second to thousands of
simultaneous clients, on a single Heroku dyno.## Introduction
### Hierarchical Namespaced Channels*
A client can subscribe to channels reflecting the content they are interested
in. For example, say you are broadcasting events to the namespaces `/pets/cats`
and `/pets/dogs`. A client could subscribe to the parent channel `/pets` in the
hierarchy and receive all messages for either.In **sseserver**, channels have infinite depth and are automatically created on
the fly with zero setup -- just broadcast and it works.(*There's probably a more accurate term for this. If you know it, let me
know.)### Performance
Designed for high throughput as primary performance consideration. In my
preliminary benchmarking (on `v1.0`, circa 2014) this can handle ~100K/sec
messages broadcast across ~1000 open HTTP connections on a 3.4GHz Intel Core i7
(using a single core, e.g. with `GOMAXPROCS=1`). There still remains quite a
bit of optimization to be done so it should be able to get faster if needed.### SSE vs Websockets
SSE is the oft-overlooked *uni-directional* cousin of websockets. Being "just
HTTP" it has some benefits:- Trvially easier to understand, plaintext format.
Can be debugged by a human with `curl`.
- Supported in most major browsers for a long time now.
Everything except IE/Edge, but an easy polyfill!
- Built-in standard support for automatic reconnection, event binding, etc.
- Works with HTTP/2.See also Smashing Magazine: ["Using SSE Instead Of WebSockets For Unidirectional
Data Flow Over HTTP/2"][1].[1]: https://www.smashingmagazine.com/2018/02/sse-websockets-data-flow-http2/
## Documentation
[![GoDoc](https://godoc.org/github.com/mroth/sseserver?status.svg)](https://godoc.org/github.com/mroth/sseserver)
### Namespaces are URLs
For clients, no need to think about protocol. To subscribe to one of the above
namespaces from the previous example, just connect to `http://$SERVER/pets/dogs`.
Done.### Example Usage
A simple Go program utilizing this package:
```go
package mainimport (
"time"
"github.com/mroth/sseserver"
)func main() {
s := sseserver.NewServer() // create a new server instance// broadcast the time every second to the "/time" namespace
go func() {
ticker := time.Tick(time.Duration(1 * time.Second))
for {
// wait for the ticker to fire
t := <-ticker
// create the message payload, can be any []byte value
data := []byte(t.Format("3:04:05 pm (MST)"))
// send a message without an event on the "/time" namespace
s.Broadcast <- sseserver.SSEMessage{"", data, "/time"}
}
}()// simulate sending some scoped events on the "/pets" namespace
go func() {
time.Sleep(5 * time.Second)
s.Broadcast <- sseserver.SSEMessage{"new-dog", []byte("Corgi"), "/pets/dogs"}
s.Broadcast <- sseserver.SSEMessage{"new-cat", []byte("Persian"), "/pets/cats"}
time.Sleep(1 * time.Second)
s.Broadcast <- sseserver.SSEMessage{"new-dog", []byte("Terrier"), "/pets/dogs"}
s.Broadcast <- sseserver.SSEMessage{"new-dog", []byte("Dauchsand"), "/pets/cats"}
time.Sleep(2 * time.Second)
s.Broadcast <- sseserver.SSEMessage{"new-cat", []byte("LOLcat"), "/pets/cats"}
}()s.Serve(":8001") // bind to port and begin serving connections
}
```All these event namespaces are exposed via HTTP endpoint in the
`/subscribe/:namespace` route.On the client, we can easily connect to those endpoints using built-in functions in JS:
```js
// connect to an event source endpoint and print results
var es1 = new EventSource("http://localhost:8001/subscribe/time");
es1.onmessage = function(event) {
console.log("TICK! The time is currently: " + event.data);
};// connect to a different event source endpoint and register event handlers
// note that by subscribing to the "parent" namespace, we get all events
// contained within the pets hierarchy.
var es2 = new EventSource("http://localhost:8001/subscribe/pets")
es2.addEventListener("new-dog", function(event) {
console.log("WOOF! Hello " + event.data);
}, false);es2.addEventListener("new-cat", function(event) {
console.log("MEOW! Hello " + event.data);
}, false);
```Which when connecting to the server would yield results:
TICK! The time is currently: 6:07:17 pm (EDT)
TICK! The time is currently: 6:07:18 pm (EDT)
TICK! The time is currently: 6:07:19 pm (EDT)
TICK! The time is currently: 6:07:20 pm (EDT)
WOOF! Hello Corgi
MEOW! Hello Persian
TICK! The time is currently: 6:07:21 pm (EDT)
WOOF! Hello Terrier
WOOF! Hello Dauchsand
TICK! The time is currently: 6:07:22 pm (EDT)
TICK! The time is currently: 6:07:23 pm (EDT)
MEOW! Hello LOLcat
TICK! The time is currently: 6:07:24 pm (EDT)Of course you could easily send JSON objects in the data payload instead, and
most likely will be doing this often.Another advantage of the SSE protocol is that the wire-format is so simple.
Unlike WebSockets, we can connect with `curl` to an endpoint directly and just
read what's going on:```bash
$ curl http://localhost:8001/subscribe/pets
event:new-dog
data:Corgievent:new-cat
data:Persianevent:new-dog
data:Terrierevent:new-dog
data:Dauchsandevent:new-cat
data:LOLcat
```Yep, it's that simple.
### Keep-Alives
All connections will send periodic `:keepalive` messages as recommended in the
WHATWG spec (by default, every 15 seconds). Any library adhering to the
EventSource standard should already automatically ignore and filter out these
messages for you.### Admin Page
By default, an admin status page is available for easy monitoring.![screenshot](http://f.cl.ly/items/1v2X1k342K3p0K1O2x0B/ssestreamer-admin.png)
It's powered by a simple JSON API endpoint, which you can also use to build your
own reporting. These endpoints can be disabled in the settings (see `Server.Options`).### HTTP Middleware
`sseserver.Server` implements the standard Go `http.Handler` interface, so you
can easily integrate it with most existing HTTP middleware, and easily plug it
into whatever you are using for your current routing, etc.## License
[AGPL-3.0](https://opensource.org/licenses/AGPL-3.0). Dual commercial licensing
available upon request.