/js/lib/Socket.IO-node/lib/socket.io/listener.js
JavaScript | 224 lines | 159 code | 35 blank | 30 comment | 32 complexity | 3a9fcc06ff57591c9046453296b0ed2f MD5 | raw file
1 2/*! 3 * Socket.IO - Listener 4 * Copyright (c) 2010-2011 Guillermo Rauch <guillermo@learnboost.com> 5 * MIT Licensed 6 */ 7 8var EventEmitter = require('events').EventEmitter 9 , util = require(process.binding('natives').util ? 'util' : 'sys') 10 , clientVersion = require('./../../support/socket.io-client/lib/io').io.version 11 , Client = require('./client') 12 , merge = require('./utils').merge 13 , url = require('url') 14 , fs = require('fs') 15 , transports = {}; 16 17/** 18 * Supported transports. 19 */ 20 21var transports = [ 22 'websocket' 23 , 'flashsocket' 24 , 'htmlfile' 25 , 'xhr-multipart' 26 , 'xhr-polling' 27 , 'jsonp-polling' 28]; 29 30/** 31 * Expose `Listener`. 32 */ 33 34module.exports = Listener; 35 36/** 37 * Initialize a `Listener` with the given `server` and `options`. 38 * 39 * @param {http.Server} server 40 * @param {Object} options 41 * @api private 42 */ 43 44function Listener(server, options){ 45 var self = this; 46 this.server = server; 47 this.clients = this.clientsIndex = {}; 48 this._clientCount = 0; 49 this._clientFiles = {}; 50 51 var defaults = { 52 origins: '*:*' 53 , resource: 'socket.io' 54 , flashPolicyServer: true 55 , transports: transports 56 , transportOptions: {} 57 , log: util.log 58 }; 59 60 merge(this, merge(defaults, options || {})); 61 this.log = this.log || function(){}; 62 63 64 var listeners = this.server.listeners('request'); 65 this.server.removeAllListeners('request'); 66 67 this.server.on('request', function(req, res){ 68 if (self.check(req, res)) return; 69 for (var i = 0, len = listeners.length; i < len; i++){ 70 listeners[i].call(this, req, res); 71 } 72 }); 73 74 this.server.on('upgrade', function(req, socket, head){ 75 if (!self.check(req, socket, true, head)){ 76 socket.end(); 77 socket.destroy(); 78 } 79 }); 80 81 this.transports.forEach(function(name) { 82 if (!(name in transports)) 83 transports[name] = require('./transports/' + name); 84 if ('init' in transports[name]) transports[name].init(self); 85 }); 86 87 this.log('socket.io ready - accepting connections'); 88}; 89 90/** 91 * Inherit from `EventEmitter.prototype`. 92 */ 93 94Listener.prototype.__proto__ = EventEmitter.prototype; 95 96/** 97 * Broadcast `message` to all clients, omitting those specified 98 * by the `except` argument. 99 * 100 * @param {String} message 101 * @param {String|Number|Array} except 102 * @return {Listener} 103 * @api private 104 */ 105 106Listener.prototype.broadcast = function(message, except){ 107 var keys = Object.keys(this.clients) 108 , len = keys.length 109 , key; 110 111 for (var i = 0; i < len; ++i){ 112 key = keys[i]; 113 if (except) { 114 if (Array.isArray(except) && ~except.indexOf(key)) continue; 115 else if (key == except) continue; 116 } 117 this.clients[key].send(message); 118 } 119 120 return this; 121}; 122 123Listener.prototype.check = function(req, res, httpUpgrade, head){ 124 var path = url.parse(req.url).pathname 125 , parts 126 , cn; 127 128 if (path && 0 === path.indexOf('/' + this.resource)){ 129 parts = path.substr(2 + this.resource.length).split('/'); 130 if (this._serveClient(parts.join('/'), req, res)) return true; 131 if (!(parts[0] in transports)) return false; 132 if (parts[1]){ 133 cn = this.clients[parts[1]]; 134 if (cn){ 135 cn._onConnect(req, res); 136 } else { 137 req.connection.end(); 138 req.connection.destroy(); 139 this.log('Couldnt find client with session id "' + parts[1] + '"'); 140 } 141 } else { 142 this._onConnection(parts[0], req, res, httpUpgrade, head); 143 } 144 return true; 145 } 146 return false; 147}; 148 149Listener.prototype._serveClient = function(file, req, res){ 150 var self = this 151 , clientPaths = { 152 'socket.io.js': 'socket.io.js', 153 'lib/vendor/web-socket-js/WebSocketMain.swf': 'lib/vendor/web-socket-js/WebSocketMain.swf', // for compat with old clients 154 'WebSocketMain.swf': 'lib/vendor/web-socket-js/WebSocketMain.swf' 155 } 156 , types = { 157 swf: 'application/x-shockwave-flash', 158 js: 'text/javascript' 159 }; 160 161 function write(path){ 162 if (req.headers['if-none-match'] == clientVersion){ 163 res.writeHead(304); 164 res.end(); 165 } else { 166 res.writeHead(200, self._clientFiles[path].headers); 167 res.end(self._clientFiles[path].content, self._clientFiles[path].encoding); 168 } 169 }; 170 171 var path = clientPaths[file]; 172 173 if (req.method == 'GET' && path !== undefined){ 174 if (path in this._clientFiles){ 175 write(path); 176 return true; 177 } 178 179 fs.readFile(__dirname + '/../../support/socket.io-client/' + path, function(err, data){ 180 if (err){ 181 res.writeHead(404); 182 res.end('404'); 183 } else { 184 var ext = path.split('.').pop(); 185 self._clientFiles[path] = { 186 headers: { 187 'Content-Length': data.length, 188 'Content-Type': types[ext], 189 'ETag': clientVersion 190 }, 191 content: data, 192 encoding: ext == 'swf' ? 'binary' : 'utf8' 193 }; 194 write(path); 195 } 196 }); 197 198 return true; 199 } 200 201 return false; 202}; 203 204Listener.prototype._onClientConnect = function(client){ 205 this.clients[client.sessionId] = client; 206 this.log('Client '+ client.sessionId +' connected'); 207 this.emit('clientConnect', client); 208 this.emit('connection', client); 209}; 210 211Listener.prototype._onClientMessage = function(data, client){ 212 this.emit('clientMessage', data, client); 213}; 214 215Listener.prototype._onClientDisconnect = function(client){ 216 delete this.clients[client.sessionId]; 217 this.log('Client '+ client.sessionId +' disconnected'); 218 this.emit('clientDisconnect', client); 219}; 220 221Listener.prototype._onConnection = function(transport, req, res, httpUpgrade, head){ 222 this.log('Initializing client with transport "'+ transport +'"'); 223 new transports[transport](this, req, res, this.transportOptions[transport], head); 224};