README.md in angelo-0.1.14 vs README.md in angelo-0.1.15
- old
+ new
@@ -6,11 +6,12 @@
A [Sinatra](https://github.com/sinatra/sinatra)-esque DSL for [Reel](https://github.com/celluloid/reel).
### tl;dr
* websocket support via `websocket('/path'){|s| ... }` route builder
-* contextual websocket stashing via `websockets` helper
+* SSE support via `eventsource('/path'){|s| ... }` route builder
+* contextual websocket/sse stashing via `websockets` and `sses` helpers
* `task` handling via `async` and `future` helpers
* no rack
* optional tilt/erb support
* optional mustermann support
@@ -96,9 +97,108 @@
a `.each` block.
When a `POST /` with a 'foo' param is received, any value is messaged out to any '/' connected
websockets. When a `POST /bar` with a 'bar' param is received, any value is messaged out to all
websockets that connected to '/bar'.
+
+### SSE - Server-Sent Events
+
+The `eventsource` route builder also accepts a path and a block, and passes the socket to the block,
+just like the `websocket` builder. This socket is actually the raw `Celluloid::IO::TCPSocket` and is
+"detached" from the regular handling. There are no "on-*" methods; the `write` method should suffice.
+To make it easier to deal with creation of the properly formatted Strings to send, Angelo provides
+a couple helpers.
+
+##### `sse_event` helper
+
+To create an "event" that a javascript EventListener on the client can respond to:
+
+```ruby
+eventsource '/sse' do |s|
+ event = sse_event :foo, some_key: 'blah', other_key: 'boo'
+ s.write event
+ s.close
+end
+```
+
+In this case, the EventListener would have to be configured to listen for the `foo` event:
+
+```javascript
+var sse = new EventSource('/sse');
+sse.addEventListener('foo', function(e){ console.log("got foo event!\n" + JSON.parse(e.data)); });
+```
+
+The `sse_event` helper accepts a normal `String` for the data, but will automatically convert a `Hash`
+argument to a JSON object.
+
+##### `sse_message` helper
+
+The `sse_message` helper behaves exactly the same as `sse_event`, but does not take an event name:
+
+```ruby
+eventsource '/sse' do |s|
+ msg = sse_msg some_key: 'blah', other_key: 'boo'
+ s.write msg
+ s.close
+end
+```
+
+The client javascript would need to be altered to use the `EventSource.onmessage` property as well:
+
+```javascript
+var sse = new EventSource('/sse');
+sse.onmessage = function(e){ console.log("got message!\n" + JSON.parse(e.data)); };
+```
+
+##### `sses` helper
+
+Angelo also includes a "stash" helper for SSE connections. One can `<<` a socket into `sses` from
+inside an `eventsource` handler block. These can also be "later" be iterated over so one can do things
+like emit a message on every SSE connection when the service receives a POST request.
+
+The `sses` helper includes the same a context ability as the `websockets` helper. In addition, the
+`sses` stash includes methods for easily sending events or messages to all stashed connections. **Note
+that the `Stash::SSE#event` method only works on non-default contexts and uses the context name as
+the event name.**
+
+```ruby
+eventsource '/sse' do |s|
+ sses[:foo] << s
+end
+
+post '/sse_message' do
+ sses[:foo].message params[:data]
+end
+
+post '/sse_event' do
+ sses[:foo].event params[:data]
+end
+```
+
+##### `eventsource` instance helper
+
+Additionally, you can also start SSE handling *conditionally* from inside a GET block:
+
+```ruby
+get '/sse_maybe' do
+ if params[:sse]
+ eventsource do |c|
+ sses << c
+ c.write sse_message 'wooo fancy SSE for you!'
+ end
+ else
+ 'boring regular old get response'
+ end
+end
+
+post '/sse_event' do
+ sses.each {|sse| sse.write sse_event(:foo, 'fancy sse event!')}
+end
+```
+
+Handling this on the client may require conditionals for [browsers](http://caniuse.com/eventsource) that
+do not support EventSource yet, since this will respond with a non-"text/event-stream" Content-Type if
+'sse' is not present in the params.
### Tasks + Async / Future
Angelo is built on Reel and Celluloid::IO, giving your web application class the ability to define
"tasks" and call them from route handler blocks in an `async` or `future` style.