Permalink
Browse files

Working scenario for separate Express and Socket.IO servers.

  • Loading branch information...
1 parent bd8b53d commit 9976b3243312164843b43968de0f4cc619ead0e1 @shimaore shimaore committed Aug 15, 2012
Showing with 163 additions and 24 deletions.
  1. +74 −0 examples/share_express.coffee
  2. +36 −0 examples/share_socket.coffee
  3. +3 −1 package.json
  4. +6 −2 src/client.coffee
  5. +44 −21 src/zappa.coffee
@@ -0,0 +1,74 @@
+#!/usr/bin/coffee
+#
+# This examples show how data can be shared between Express and Socket.IO.
+# You must run share_socketio.coffee in parallel to this script.
+#
+
+require('./zappajs') 3000, ->
+
+ @use 'logger',
+ 'cookieParser',
+ 'partials',
+ @enable 'default layout'
+
+ express_store = do =>
+ ExpressRedisStore = require('connect-redis') @express
+ new ExpressRedisStore()
+
+ @use session:
+ store: express_store
+ secret: 'rock zappa rock'
+
+ @use session_store: express_store
+
+ socketio_store = do ->
+ SocketIORedisStore = require 'socket.io/lib/stores/redis'
+ redis = require 'redis'
+ pub = redis.createClient()
+ sub = redis.createClient()
+ client = redis.createClient()
+ new SocketIORedisStore
+ redisPub: pub
+ redisSub: sub
+ redisClient: client
+
+ @io.set 'store', socketio_store
+
+ @get '/': ->
+ @render 'default',
+ scripts: '/socket.io/socket.io /zappa/jquery /zappa/zappa /index'.split ' '
+
+ @get '/touch': ->
+ @session.foo = 'bar'
+ @send ok:true
+
+ @get '/verify': ->
+ @send foo:@session.foo
+
+ @view default: ->
+ div id:'log'
+
+ @client '/index.js': ->
+
+ $ =>
+ log = -> $('#log').append arguments...
+ socket = null
+
+ @connect()
+
+ channel_name = Math.random()
+
+ # connect to the separate Socket.IO process.
+ socket = io.connect 'http://127.0.0.1:3001'
+ socket.on 'connect', =>
+ @share channel_name, socket, (data) ->
+ log "<p>Received key #{data.key} for channel #{data.channel_name}</p>"
+ $.getJSON "/touch", (data) ->
+ $.getJSON "/verify", (data) ->
+ log "<p>Express says that foo = #{data.foo}</p>"
+ socket.emit 'express done, your turn'
+
+ socket.on 'all set', (data) ->
+ log "<p>Socket says that foo = #{data.foo}.</p>"
+
+ log '<p>Client started.</p>'
@@ -0,0 +1,36 @@
+#!/usr/bin/coffee
+#
+# This examples show how data can be shared between Express and Socket.IO.
+# You must run share_express.coffee in parallel to this script.
+#
+# This is the "Socket.IO only" side of the experiment.
+
+require('./zappajs') 3001, ->
+
+ express_store = do =>
+ ExpressRedisStore = require('connect-redis') @express
+ new ExpressRedisStore()
+
+ @use session:
+ store: express_store
+ secret: 'rock zappa rock'
+
+ @use session_store: express_store
+
+ socketio_store = do ->
+ SocketIORedisStore = require 'socket.io/lib/stores/redis'
+ redis = require 'redis'
+ pub = redis.createClient()
+ sub = redis.createClient()
+ client = redis.createClient()
+ new SocketIORedisStore
+ redisPub: pub
+ redisSub: sub
+ redisClient: client
+
+ @io.set 'store', socketio_store
+
+ @on 'express done, your turn': ->
+ @session (err,session) =>
+ # Let the client confirm that we received the session data OK.
+ @emit 'all set', foo: session.foo
View
@@ -24,7 +24,9 @@
"stylus": "0.28.2",
"less": "1.3.0",
"zappajs-partials": "0.0.6",
- "connect-assets": "2.3.1"
+ "connect-assets": "2.3.1",
+ "redis": "*",
+ "connect-redis": "*"
},
"keywords": ["framework", "websockets", "coffeescript", "express", "socket.io", "sammy", "sinatra", "dsl"],
"main": "./lib/zappa",
View
@@ -7,7 +7,7 @@ skeleton = ->
zappa.run = (func) ->
context = {}
-
+
# Storage for the functions provided by the user.
ws_handlers = {}
helpers = {}
@@ -50,6 +50,9 @@ skeleton = ->
for k, v of arguments[0]
context.socket.emit.apply context.socket, [k, v]
+ context.share = (channel,socket,cb) ->
+ $.getJSON "/zappa/socket/#{channel}/#{socket.socket.sessionid}", cb
+
route = (r) ->
ctx = {app}
@@ -71,7 +74,7 @@ skeleton = ->
# Implements the websockets client with socket.io.
if context.socket?
context.socket.on 'connect', ->
- $.getJSON "/zappa/socket/#{socket.id}", (data) ->
+ context.share '__local', socket, (data) ->
context.key = data.key
for name, h of ws_handlers
@@ -83,6 +86,7 @@ skeleton = ->
id: context.socket.id
data: data
emit: context.emit
+ share: context.share
apply_helpers ctx
View
@@ -14,6 +14,8 @@ jquery = fs.readFileSync(__dirname + '/../vendor/jquery-1.8.0.min.js').toString(
sammy = fs.readFileSync(__dirname + '/../vendor/sammy-0.7.1.min.js').toString()
uglify = require 'uglify-js'
+socketio_key = '__session'
+
# Soft dependencies:
jsdom = null
express_partials = null
@@ -240,6 +242,8 @@ zappa.app = (func,options={}) ->
for k,v of maps
partials.register k, v
partials
+ session_store: (store) ->
+ context.session_store = store
use = (name, arg = null) ->
if zappa_middleware[name]
@@ -286,15 +290,21 @@ zappa.app = (func,options={}) ->
ctx[name] = helper
ctx
+ # Local socket
request_socket = (req) ->
- socket_id = req.session?.__socket_id
+ socket_id = req.session?.__socket?['__local']?.id
socket_id and io?.sockets.socket socket_id, true
# The callback will receive (err,session).
socket_session = (socket,cb) ->
- socket.get '__session', (err,data) ->
- if data?.id?
- context.session_store?.get data.id, cb
+ socket.get socketio_key, (err,data) ->
+ if err
+ return cb err
+ data = JSON.parse data
+ if data.id?
+ context.session_store.get data.id, cb
+ else
+ cb err
# Register a route with express.
route = (r) ->
@@ -333,7 +343,6 @@ zappa.app = (func,options={}) ->
res.send r.handler
else
app[r.verb] r.path, r.middleware..., (req, res, next) ->
- socket = request_socket req
ctx =
app: app
settings: app.settings
@@ -355,8 +364,14 @@ zappa.app = (func,options={}) ->
else
for k, v of arguments[0]
render.apply @, [k, v]
- emit: -> socket.emit arguments...
- on: -> socket.on arguments...
+ emit: ->
+ socket = request_socket req
+ if socket?
+ if typeof arguments[0] isnt 'object'
+ socket.emit.apply socket, arguments
+ else
+ for k, v of arguments[0]
+ socket.emit.apply socket, [k, v]
render = (name,opts = {},fn) ->
@@ -492,28 +507,36 @@ zappa.app = (func,options={}) ->
style @style if @style
body @body
- # TODO -- add channel_name to request
if io?
- context.get '/zappa/socket/:socket_id', ->
+ context.get '/zappa/socket/:channel_name/:socket_id', ->
if @session?
- if @session.__socket_key?
+ channel_name = @params.channel_name
+ socket_id = @params.socket_id
+
+ @session.__socket ?= {}
+
+ if @session.__socket[channel_name]?
# Client (or hijacker) trying to re-key.
- @send key: null
+ @send error:'Channel already assigned', channel_name: channel_name
else
- key = uuid() # used for socket 'authorization' (TODO)
- @session.__socket_id = @params.socket_id
- @session.__socket_key = key
- # Socket.IO client
- client = io.sockets.store.client(socket_id)
- client.set "__session",
+ key = uuid() # used for socket 'authorization'
+
+ # Update the Express session store
+ @session.__socket[channel_name] =
+ id: socket_id
+ key: key
+
+ # Update the Socket.IO store
+ io_client = io.sockets.store.client(socket_id)
+ io_data = JSON.stringify
id: @req.sessionID
key: key
- # Hack -- the Express session module does not give us access to the store.
- context.session_store ?= @req.sessionStore
+ io_client.set socketio_key, io_data
+
# Let the client know which key to use.
- @send key: key
+ @send channel_name: channel_name, key: key
else
- @send key: null
+ @send error:'No session'
context

0 comments on commit 9976b32

Please sign in to comment.