Skip to content

Instantly share code, notes, and snippets.

@dodo
Created May 5, 2012 18:26
Show Gist options
  • Save dodo/2604577 to your computer and use it in GitHub Desktop.
Save dodo/2604577 to your computer and use it in GitHub Desktop.
stateful http server
fs = require 'fs'
path = require 'path'
http = require 'http'
{ EventEmitter } = require 'events'
socketio = require 'socket.io'
Cookies = require 'cookies'
hat = require 'hat'
{ Template } = require 'dynamictemplate'
render = require 'dynamictemplate/render'
# store code
class Sessions extends EventEmitter
constructor: ->
@sessions = {}
@i = 0
manage: (req, res, sid, callback) ->
console.log "?", sid
sid ?= hat()
# session
console.log "?", sid
ses = @sessions[sid] ? @create sid
# emits
ses.emit("url:#{req.url}", req, res)
console.log "save", ses.id
callback(null, ses)
ses.emit('request', req, res)
create: (sid) ->
console.log "create"
# FIXME dispose old session?
@emit 'session', @sessions[sid] = ses = new EventEmitter
ses.setId = (id) =>
console.log "change id"
delete @sessions[ses.id]
@sessions[id] = ses
ses.id = id
ses.id = sid
return ses
get: (sid) ->
@sessions[sid]
# sessions store
sessions = new Sessions
# http server
server = http.createServer (req, res) ->
if req.url is "/favicon.ico"
res.writeHead 404
return do res.end
if req.url is "/domevents.js"
res.writeHead 200, 'Content-Type':"text/javascript"
return fs.createReadStream(
require.resolve 'domevents/events.browser').pipe(res)
console.log req.method, req.url, req.headers.cookie
cookies = new Cookies req, res
sessions.manage req, res, cookies.get('sid'), (err, ses) ->
cookies.set('sid', ses.id)
res.writeHead 200, 'Content-Type':"text/html"
# websocket management
io = socketio.listen server
io.configure ->
io.set 'transports', ['websocket']#, 'xhr-polling']
io.disable 'log'
io.sockets.on 'connection', (socket) ->
console.log "ws connection"
socket.on 'sid', (sid) ->
ses = sessions.get(sid)
console.log "delegate ws to", ses?.id
ses?.emit 'websocket', socket
sessions.on 'session', (ses) ->
eventnames = {}
ses.websocket = ws = new EventEmitter
old_emit = ws.emit
ws.emit = (event) ->
return old_emit.call(ws, arguments...) if event is 'newListener'
console.warn "No websocket to receive Event '#{event}'"
ws.on 'newListener', (event) ->
console.log "new event listener", event
eventnames[event] = true
ses.on 'websocket', (socket) ->
console.log "#{socket? and 'reset' or 'init'} websocket", @id
for event in Object.keys(eventnames)
listeners = ws.listeners(event)
if listeners.length is 0
delete eventnames[event]
else
socket.on(event, old_emit.bind(ws, event))
ws.emit = socket.emit.bind(socket)
# views
indexView = (ses, state) ->
new Template doctype:on, schema:5, pretty:on, ->
@$html ->
@$head ->
@$title "stateful test - #{ses.id}"
@$body ->
@$p "sid:#{ses.id}"
@$p "sessions:#{Object.keys(sessions.sessions).length}"
@$p "reload:#{state.reload - 1}" # this is after state.reload++
@$p -> @$a href:'/', "home"
@$p ->
@$a href:'/session', "new session"
@text "(#{state.newsid}x clicked)"
@$p ->
@$button id:'red', "button"
@text "(#{state.clicked}x clicked)"
@$p ->
@text "history:"
@$ul ->
@$li "#{x}" for x in state.history
# scripts
@$script src:"/domevents.js"
@$script src:"/socket.io/socket.io.js"
# client code
@$script "var sid='#{ses.id}';(" + ( ->
console.log "loaded"
socket = io.connect()
socket.on 'connect', ->
socket.emit "sid", sid
button = new DomEventEmitter document.getElementById 'red'
button.on 'click', (ev) ->
console.log "button clicked!"
socket.emit 'button:click'
socket.on 'state:clicked', (c) ->
console.log "click count:", c
) + ").call(this)"
# main code
sessions.on 'session', (ses) ->
state =
reload:0
newsid:0
clicked:0
history: []
ses.on 'request', (req, res) ->
render(indexView ses, state).pipe(res)
state.reload++
ses.on 'url:/session', (req, res) ->
state.history.unshift @id
@setId hat()
state.newsid++
ses.websocket.on 'button:click', ->
console.log "button clicked!", ses.id
ses.websocket.emit 'state:clicked', ++state.clicked
# start the server
console.log "listen 3000"
server.listen 3000
@dodo
Copy link
Author

dodo commented May 5, 2012

session based instead of route based.

the idea is to have the same interface for http like for stream/connection/session based protocols (eg tcp, udp, websocket, xmpp, etc)

dunno if this is a good idea, but it's at least worth a try.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment