Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit 69d1e7ffc892512574a9151c894fe5a410d0daea @majek majek committed Jul 22, 2011
Showing with 780 additions and 0 deletions.
  1. 0 lib/.placeholder
  2. +8 −0 package.json
  3. +24 −0 run.sh
  4. +60 −0 src/sockjs.coffee
  5. +61 −0 src/trans-jsonp.coffee
  6. +186 −0 src/trans-websocket.coffee
  7. +171 −0 src/transport.coffee
  8. +54 −0 src/utils.coffee
  9. +179 −0 src/webjs.coffee
  10. +37 −0 test-server.js
No changes.
@@ -0,0 +1,8 @@
+{
+ "name": "sockjs-node",
+ "version": "0.0.1",
+ "dependencies": {
+ "coffee-script": "1.1.1",
+ "jquery": "1.5.1"
+ }
+}
24 run.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+if [ -e .pidfile.pid ]; then
+ kill `cat .pidfile.pid`
+ rm .pidfile.pid
+fi
+
+while [ 1 ]; do
+ echo " [*] Compiling coffee"
+ coffee -o lib/ -c src/*.coffee && while [ 1 ]; do
+ echo " [*] Running node"
+ node test-server.js &
+ NODEPID=$!
+ echo $NODEPID > .pidfile.pid
+
+ echo " [*] node pid: $NODEPID"
+ break
+ done
+
+ inotifywait -r -q -e modify .
+ kill $NODEPID
+ rm -f .pidfile.pid
+ # Sync takes some time, wait to avoid races.
+ sleep 0.1
+done
@@ -0,0 +1,60 @@
+events = require('events')
+webjs = require('./webjs')
+$ = require('jquery');
+
+trans_websocket = require('./trans-websocket');
+trans_jsonp = require('./trans-jsonp');
+
+
+app =
+ welcome_screen: (req, res) ->
+ res.writeHead(200, {})
+ res.end("Welcome to SockJS!")
+ return true
+
+$.extend(app, webjs.generic_app)
+$.extend(app, trans_websocket.app)
+$.extend(app, trans_jsonp.app)
+
+
+class Server extends events.EventEmitter
+ constructor: (user_options) ->
+ @options =
+ prefix: ''
+ origins: ['*:*']
+ if user_options
+ $.extend(@options, user_options)
+
+ installHandlers: (http_server, user_options) ->
+ options = {}
+ $.extend(options, @options)
+ if user_options
+ $.extend(options, user_options)
+
+ p = (s) => new RegExp('^' + options.prefix + s + '[/]?$')
+ t = (s) => [p('/([^/.]+)/([^/.]+)' + s), 'server', 'session']
+ dispatcher = [
+ ['GET', p(''), ['welcome_screen']],
+ ['GET', t('/websocket'), ['websocket']],
+ ['GET', t('/jsonp'), ['h_no_cache','jsonp']],
+ ['POST', t('/send'), ['expect', 'jsonp_send', 'expose']],
+ ]
+ webjs_handler = new webjs.WebJS(app, dispatcher)
+
+ install_handler = (ee, event, handler) ->
+ old_listeners = ee.listeners(event)
+ ee.removeAllListeners(event)
+ new_handler = (a,b,c) ->
+ if handler(a,b,c) isnt true
+ for listener in old_listeners
+ listener.call(this, a, b, c)
+ return false
+ ee.addListener(event, new_handler)
+ handler = (req,res,extra) =>
+ req.sockjs_server = @
+ return webjs_handler.handler(req, res, extra)
+ install_handler(http_server, 'request', handler)
+ install_handler(http_server, 'upgrade', handler)
+ return true
+
+exports.Server = Server
@@ -0,0 +1,61 @@
+transport = require('./transport')
+
+class JsonP extends transport.PollingTransport
+ protocol: "jsonp"
+
+ constructor: (req) ->
+ super(req.session, req.sockjs_server)
+
+ _register: (req) ->
+ @callback = if 'c' of req.query then req.query['c'] else req.query['callback']
+ super
+
+ writeOpen: ->
+ @rawWrite(@callback + "(undefined, 'open');\r\n")
+
+ writeHeartbeat: ->
+ @rawWrite(@callback + "(undefined, 'heartbeat');\r\n")
+
+ writeClose: ->
+ @rawWrite(@callback + "(undefined, 'close');\r\n")
+
+ writeMessages: (messages) ->
+ @rawWrite(@callback + "(" + JSON.stringify(messages) + ");\r\n")
+
+
+exports.app =
+ jsonp: (req, res, _, next_filter) ->
+ if not('c' of req.query or 'callback' of req.query)
+ throw {
+ status: 500
+ message: '"callback" parameter required'
+ }
+
+ res.setHeader('Content-type', 'application/javascript; charset=UTF-8')
+ res.statusCode = 200
+
+ jsonp = transport.Transport.bySession(req.session)
+ if jsonp is null
+ jsonp = new JsonP(req)
+ jsonp._register(req, res)
+ return true
+
+ jsonp_send: (req, res, query) ->
+ if not query
+ throw {
+ status: 500
+ message: 'payload expected'
+ }
+ if not('d' of query)
+ throw {
+ status: 500
+ message: '"d" expected'
+ }
+ jsonp = transport.Transport.bySession(req.session)
+ if jsonp is null
+ throw {status: 404}
+ for message in JSON.parse(query.d)
+ jsonp.didMessage(message)
+
+ res.setHeader('Content-type', 'text/plain')
+ return 'ok'
@@ -0,0 +1,186 @@
+crypto = require('crypto')
+
+utils = require('./utils')
+transport = require('./transport')
+
+validateCrypto = (req_headers, nonce) ->
+ k1 = req_headers['sec-websocket-key1']
+ k2 = req_headers['sec-websocket-key2']
+
+ if not k1 or not k2
+ return false
+
+ md5 = crypto.createHash('md5')
+ for k in [k1, k2]
+ n = parseInt(k.replace(/[^\d]/g, ''))
+ spaces = k.replace(/[^ ]/g, '').length
+
+ if spaces is 0 or n % spaces isnt 0
+ return false
+ n /= spaces
+ s = String.fromCharCode(
+ n >> 24 & 0xFF,
+ n >> 16 & 0xFF,
+ n >> 8 & 0xFF,
+ n & 0xFF)
+ md5.update(s)
+ md5.update(nonce.toString('binary'))
+ return md5.digest('binary')
+
+
+class WebHandshake
+ constructor: (@req, @connection, head, origin, location) ->
+ @sec = ('sec-websocket-key1' of @req.headers)
+ wsp = (@sec and ('sec-websocket-protocol' of @req.headers))
+ prefix = if @sec then 'Sec-' else ''
+ blob = [
+ 'HTTP/1.1 101 WebSocket Protocol Handshake',
+ 'Upgrade: WebSocket',
+ 'Connection: Upgrade'
+ prefix + 'WebSocket-Origin: ' + origin,
+ prefix + 'WebSocket-Location: ' + location,
+ ]
+ if wsp
+ blob.push('Sec-WebSocket-Protocol: ' +
+ @req.headers['sec-websocket-protocol'])
+
+ @_setup()
+ try
+ @connection.write(blob.concat('', '').join('\r\n'), 'utf8')
+ @connection.setTimeout(0)
+ @connection.setNoDelay(true)
+ @connection.setEncoding('binary')
+ catch e
+ @didClose()
+ return
+
+ @buffer = new Buffer(0)
+ @didMessage(head)
+ return
+
+ _setup: ->
+ @close_cb = () => @didClose()
+ @connection.addListener('close', @close_cb)
+ @data_cb = (data) => @didMessage(data)
+ @connection.addListener('data', @data_cb)
+
+ _cleanup: ->
+ @connection.removeListener('close', @close_cb)
+ @connection.removeListener('data', @data_cb)
+ @close_cb = @data_cb = undefined
+
+ didClose: ->
+ if @connection
+ @_cleanup()
+ try
+ @connection.end()
+ catch x
+ @connection = undefined
+
+ didMessage: (bin_data) ->
+ @buffer = utils.buffer_concat(@buffer, bin_data)
+ if @sec is false or @buffer.length >= 8
+ @gotEnough()
+
+ gotEnough: ->
+ @_cleanup()
+ if @sec
+ nonce = @buffer.slice(0, 8)
+ @buffer = @buffer.slice(8)
+ reply = validateCrypto(@req.headers, nonce)
+ if reply is false
+ @didClose()
+ return false
+ try
+ @connection.write(reply, 'binary')
+ catch x
+ @didClose()
+ return false
+ ws = new WebSocket(@req, @connection)
+
+
+
+class WebSocket extends transport.Transport
+ protocol: "websocket"
+
+ constructor: (@req, @connection) ->
+ super(@req.session, @req.sockjs_server)
+ @_setup()
+ @_recv_buffer = new Buffer(0)
+ @didOpen()
+
+ _setup: ->
+ @close_cb = () => @didClose(1001, "Socket closed")
+ @connection.addListener('close', @close_cb)
+ @data_cb = (data) => @didMessage(data)
+ @connection.addListener('data', @data_cb)
+
+ _cleanup: ->
+ @connection.removeListener('close', @close_cb)
+ @connection.removeListener('data', @data_cb)
+ @close_cb = @data_cb = undefined
+
+ doSend: (payload) ->
+ if typeof @connection is 'undefined'
+ return false
+ try
+ @connection.write('\u0000', 'binary')
+ @connection.write(''+payload, 'utf-8')
+ @connection.write('\uffff', 'binary')
+ return true
+ catch e
+ console.log(e.stack)
+ process.nextTick(() => @didClose(1001, "Socket closed (write)"))
+ return false
+
+ doClose: ->
+ @connection.end()
+
+ didClose: (a,b)->
+ if typeof @connection isnt 'undefined'
+ @_cleanup()
+ try
+ @connection.end()
+ catch x
+ @connection = undefined
+ super
+
+ didMessage: (bin_data) ->
+ buf = @_recv_buffer = utils.buffer_concat(@_recv_buffer, new Buffer(bin_data, 'binary'))
+ # TODO: support length in framing
+ if buf.length is 0
+ return
+ if buf[0] is 0x00
+ for i in [1...buf.length]
+ if buf[i] is 0xff
+ data = buf.slice(1, i).toString('utf8')
+ @_recv_buffer = buf.slice(i+1)
+ super(data)
+ return @didMessage(new Buffer(0))
+ # not enough chars
+ return
+ else if buf[0] is 0xff and buf[1] is 0x00
+ @didClose(1001, "Socket closed by the client")
+ else
+ @didClose(1002, "Broken framing")
+ return
+
+exports.app =
+ websocket: (req, connection, head) ->
+ if req.headers.upgrade isnt 'WebSocket'
+ throw {
+ status: 406
+ message: "Can upgrade only to websockets."
+ }
+ origin = req.headers.origin
+ if not utils.verify_origin(origin, req.sockjs_server.options.origins)
+ throw {
+ status: 403
+ message: "Unverified origin."
+ }
+ location = (if origin and origin[0...5] is 'https' then 'wss' else 'ws')
+ location += '://' + req.headers.host + req.url
+
+ head or= new Buffer(0)
+ ws = new WebHandshake(req, connection, head, origin, location)
+ return true
Oops, something went wrong.

0 comments on commit 69d1e7f

Please sign in to comment.