Skip to content

Commit 15e45e7

Browse files
authored
Update README.md
1 parent 3fccc89 commit 15e45e7

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

README.md

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,140 @@
11
# EventSource
22
Swiss Army Knife for SSE in Golang
3+
4+
# Up and running in 30 seconds
5+
So you want to publish events to client that connect to your server?
6+
```go
7+
func main() {
8+
stream := &eventsource.Stream{}
9+
10+
go func(s *eventsource.Stream) {
11+
for {
12+
time.Sleep(time.Second)
13+
stream.Broadcast(eventsource.DataEvent("tick"))
14+
}
15+
}(stream)
16+
http.ListenAndServe(":8080", stream)
17+
}
18+
```
19+
The `Stream` object implements an `http.Handler` for you so it can be registered directly to a server. Broadcast events to it and it'll forward them to every client that's connected to it.
20+
21+
`DataEvent` is shorthand for creating a new `*Event` object and assigning `Data()` to it
22+
23+
# More control on the `Stream`
24+
You got it! What do you need?
25+
26+
## Multiplexing / Topics / Rooms / Channels
27+
We call them "topics" but the gist is the same. All Clients always recieve `Broadcast` events, but you can `Publish` events to a specific topic, and then only clients that have `Subscribe`d to that topic will recieve the event.
28+
29+
### Auto subscribe certain routes to topics
30+
`Stream` implements an `http.Handler` but by default just registers clients for broadcasts.
31+
32+
Use `TopicHandler` to create another handler for that stream that will subscribe clients to topics as well as broadcasts.
33+
```go
34+
stream := &eventsource.Stream{}
35+
catsHandler := stream.TopicHandler([]string{"cat"})
36+
37+
http.ListenAndServe(":8080", catsHandler)
38+
```
39+
40+
## Subscribe/Unsubscribe
41+
Use the stream's `Register`, `Subscribe`, `Remove`, `Unsubscribe`, and `CloseTopic` functions to control which clients are registered where.
42+
43+
## Tell me when clients connect
44+
Register a callback for the `Stream` to envoke everytime a new client connects with `Stream.ClientConnectHook`. It'll give you a handle to the resulting Client and the http request that created it, letting you do whatever you please.
45+
46+
```go
47+
stream := &eventsource.Stream{}
48+
stream.ClientConnectHook(func(r *http.Requset, c *eventsource.Client){
49+
fmt.Println("Recieved connection from", r.Host)
50+
fmt.Println("Hate that guy")
51+
stream.Remove(c)
52+
c.Shutdown()
53+
})
54+
```
55+
56+
The callback will be on the same goroutine as the incoming web request that created it, but the Client is live and functioning so it'll start recieving broadcasts and publishments immediately before your callback has returned.
57+
58+
## Graceful shutdown
59+
The stream's `Shutdown` command will unsubscribe and disconnect all connected clients. However the `Stream` itself is not running any background routines, and may continue to register new clients if it's still registed as an http handler.
60+
61+
## Get out of my way
62+
Fine! The `Stream` object is entirely convinience. It runs no background routines and does no special handling. It just adds the topics abstraction and calls `NewClient` for you when it's connected to. Feel free not to use it.
63+
64+
# More control of the `Client`
65+
You betcha.
66+
67+
## Create my own clients
68+
Clients have to be created off an `http.ResponseWriter` that supports the `http.Flusher` and `http.CloseNotifier` interfaces.
69+
70+
`NewClient` _does_ kick off a background routine to handle sending events, so constructing an object literal will not work. This is done because it's assumed you will likely be calling `NewClient` on an http handler routine, and will likely not be doing any interesting work on that routine.
71+
72+
```go
73+
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
74+
client := eventsource.NewClient(w)
75+
if client == nil {
76+
http.Error(...)
77+
return
78+
}
79+
80+
client.Wait()
81+
}
82+
```
83+
Letting the http handler routine that created the Client return may cause the underlying connection to be closed by the server. Since `NewClient` does not block, use `Wait` to block until the client is shutdown.
84+
85+
## Shutdown the client
86+
The client's `Shutdown` function terminates the background routine and marks the client as closed. It does not actually sever the connection. It does unblock any routines waiting on `Wait`, which assuming the main http hander routine was waiting there, will cause the connection to close as it returns.
87+
88+
Attempts to `Send` events to a client after it has been shutdown will result in an error
89+
90+
# More control of Events
91+
Events are the most critical part of the library, and are the most versitile.
92+
93+
## Write my own events from scratch
94+
Events implement the `io.ReadWriter` interface so that data can be written to it from practically any source. However the `Write` interface _does not_ write an entire event in wire format. It writes the provided buffer into `data:` sections in the resulting event.
95+
96+
```go
97+
ev := &eventsource.Event{}
98+
io.WriteString(ev, "This is an event! How exciting.")
99+
100+
fmt.Fprintf(ev, "This is the %d time I've had to update this readme...", 42)
101+
```
102+
103+
If you'd like to write bytes exactly as you'd like them to be written to the wire, use the `WriteRaw` function.
104+
105+
`Read` on the other hand, _does_ return the event as it would have been written to the wire.
106+
107+
## Deep copy an event
108+
You can use `Read` and `WriteRaw` to create a perfect deep copy, _however_ since this writes to the underlying data buffer and not to the "assembly area", any calls to `Data`, `ID`, `Type` etc will clobber the buffer and not give you the expected result.
109+
110+
```go
111+
evData, _ := ioutil.ReadAll(oldEvent) // full wire format
112+
newEvent.Write(evData) // ... smushed into the `data:` section. Not what you wanted
113+
114+
io.Copy(newEvent, oldEvent) // still not what you wanted
115+
116+
newEvent.WriteRaw(evData) // that will work
117+
newEvent.AppendData("Moar") // ... and you ruined it
118+
```
119+
120+
Use `Clone` to create a perfect deep copy that survives further mutation. Though this is less efficient in memeory.
121+
122+
## Create events more easily
123+
Since you will probably be creating more than just a few events, the `EventFactory` interface and a couple helper factories and functions have been provided to speed things up.
124+
125+
```go
126+
// Create events of the same type
127+
typeFact := &eventsource.EventTypeFactory{
128+
Type: "message",
129+
}
130+
131+
// then with incrementing ID's
132+
idFact := &eventsource.EventIdFactory{
133+
Next: 0,
134+
NewFunc: typeFact.New,
135+
}
136+
137+
// then generate as many events as you want with
138+
// type: message, and an ID that increments
139+
ev := idFact.New()
140+
```

0 commit comments

Comments
 (0)