Skip to content

Instantly share code, notes, and snippets.

@linus
Created January 4, 2012 15:54
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save linus/1560654 to your computer and use it in GitHub Desktop.
Save linus/1560654 to your computer and use it in GitHub Desktop.
Server-side Events with Node.js and Redis
redis = require 'redis'
# Make sure multi-line messages are properly formatted
formatData = (data) ->
data.split(/\n/).join "\ndata: "
# Format data as an event
formatEvent = (type, data) ->
"""
id: #{+new Date}
event: #{type}\n
data: #{formatData data}
"""
# Stream events on redis channel to response
stream = (config, req, res) ->
channel = req.url[1..]
unless channel in req.user.auctions.concat req.user.username
res.writeHead 403
return res.end()
res.writeHead 200,
'content-type': 'text/event-stream'
'cache-control': 'no-cache'
'connection': 'keep-alive'
res.write ''
sub = redis.createClient config
sub.on 'pmessage', (pattern, chan, message) ->
type = chan.slice channel.length
res.write formatEvent type, message
sub.psubscribe channel + '*'
finish = (err) ->
console.error err if err
sub.quit()
res.end()
req.on 'close', finish
req.on 'error', finish
# SSE middleware
module.exports = (config) ->
(req, res, next) ->
if req.accepts 'event-stream'
stream config, req, res
else
next()
linus@Newton:~$ redis-cli
redis 127.0.0.1:6379> publish /updates.foo "hello there!"
(integer) 1
redis 127.0.0.1:6379>
http = require 'http'
redis = require 'redis'
# Send data as an event of optional type
sendEvent = (res, data, type) ->
# Make sure multi-line messages are properly formatted
formatData = (data) ->
data.split(/\n/).join "\ndata: "
res.write "id: #{+new Date}\n"
res.write "event: #{type}\n" if type
res.write "data: #{formatData data}\n\n"
# Handle event-stream requests
sendEvents = (req, res) ->
# Write header
res.writeHead 200,
'content-type': 'text/event-stream'
'cache-control': 'no-cache'
'connection': 'keep-alive'
res.write '' # Flush headers
# Subscribe to redis channels matching the request url
sub = redis.createClient()
channels = "#{req.url}.*"
sub.psubscribe channels
# Message handler
sub.on 'pmessage', (pattern, channel, message) ->
# Type is the last part of the channel name
type = channel.split(".").pop()
sendEvent res, message, type
finish = (err) ->
console.error err if err
sub.quit()
res.end()
# Clean up when request is closed
req.on 'close', finish
req.on 'error', finish
# Send the page
sendPage = (req, res) ->
res.writeHead 200, 'content-type': 'text/html'
res.end """<!doctype html>
<html>
<head>
<script>
var logger = function(channel) {
return function(message) {
var data = message.data || message;
console.log(channel + ": ", data);
}
};
var messages = new EventSource("updates");
messages.onopen = logger("open");
messages.onerror = logger("error");
messages.onmessage = logger("message");
messages.addEventListener("foo", logger("foo"), false);
messages.addEventListener("bar", logger("bar"), false);
</script>
</head>
<body>hai</body>
</html>
"""
# Create our app
app = http.createServer (req, res) ->
if req.headers.accept is 'text/event-stream'
sendEvents req, res
else
sendPage req, res
app.listen 8000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment