rob@77: rob@77: /** rob@77: * Module dependencies. rob@77: */ rob@77: rob@77: var http = require('http'); rob@77: var read = require('fs').readFileSync; rob@77: var parse = require('url').parse; rob@77: var engine = require('engine.io'); rob@77: var client = require('socket.io-client'); rob@77: var clientVersion = require('socket.io-client/package').version; rob@77: var Client = require('./client'); rob@77: var Namespace = require('./namespace'); rob@77: var Adapter = require('socket.io-adapter'); rob@77: var debug = require('debug')('socket.io:server'); rob@77: var url = require('url'); rob@77: rob@77: /** rob@77: * Module exports. rob@77: */ rob@77: rob@77: module.exports = Server; rob@77: rob@77: /** rob@77: * Socket.IO client source. rob@77: */ rob@77: rob@77: var clientSource = read(require.resolve('socket.io-client/socket.io.js'), 'utf-8'); rob@77: rob@77: /** rob@77: * Server constructor. rob@77: * rob@77: * @param {http.Server|Number|Object} http server, port or options rob@77: * @param {Object} options rob@77: * @api public rob@77: */ rob@77: rob@77: function Server(srv, opts){ rob@77: if (!(this instanceof Server)) return new Server(srv, opts); rob@77: if ('object' == typeof srv && !srv.listen) { rob@77: opts = srv; rob@77: srv = null; rob@77: } rob@77: opts = opts || {}; rob@77: this.nsps = {}; rob@77: this.path(opts.path || '/socket.io'); rob@77: this.serveClient(false !== opts.serveClient); rob@77: this.adapter(opts.adapter || Adapter); rob@77: this.origins(opts.origins || '*:*'); rob@77: this.sockets = this.of('/'); rob@77: if (srv) this.attach(srv, opts); rob@77: } rob@77: rob@77: /** rob@77: * Server request verification function, that checks for allowed origins rob@77: * rob@77: * @param {http.IncomingMessage} request rob@77: * @param {Function} callback to be called with the result: `fn(err, success)` rob@77: */ rob@77: rob@77: Server.prototype.checkRequest = function(req, fn) { rob@77: var origin = req.headers.origin || req.headers.referer; rob@77: rob@77: // file:// URLs produce a null Origin which can't be authorized via echo-back rob@77: if ('null' == origin) origin = '*'; rob@77: rob@77: if (this._origins.indexOf('*:*') !== -1) return fn(null, true); rob@77: if (origin) { rob@77: try { rob@77: var parts = url.parse(origin); rob@77: parts.port = parts.port || 80; rob@77: var ok = rob@77: ~this._origins.indexOf(parts.hostname + ':' + parts.port) || rob@77: ~this._origins.indexOf(parts.hostname + ':*') || rob@77: ~this._origins.indexOf('*:' + parts.port); rob@77: return fn(null, !!ok); rob@77: } catch (ex) { rob@77: } rob@77: } rob@77: fn(null, false); rob@77: }; rob@77: rob@77: /** rob@77: * Sets/gets whether client code is being served. rob@77: * rob@77: * @param {Boolean} whether to serve client code rob@77: * @return {Server|Boolean} self when setting or value when getting rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.serveClient = function(v){ rob@77: if (!arguments.length) return this._serveClient; rob@77: this._serveClient = v; rob@77: return this; rob@77: }; rob@77: rob@77: /** rob@77: * Old settings for backwards compatibility rob@77: */ rob@77: rob@77: var oldSettings = { rob@77: "transports": "transports", rob@77: "heartbeat timeout": "pingTimeout", rob@77: "heartbeat interval": "pingInterval", rob@77: "destroy buffer size": "maxHttpBufferSize" rob@77: }; rob@77: rob@77: /** rob@77: * Backwards compatiblity. rob@77: * rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.set = function(key, val){ rob@77: if ('authorization' == key && val) { rob@77: this.use(function(socket, next) { rob@77: val(socket.request, function(err, authorized) { rob@77: if (err) return next(new Error(err)); rob@77: if (!authorized) return next(new Error('Not authorized')); rob@77: next(); rob@77: }); rob@77: }); rob@77: } else if ('origins' == key && val) { rob@77: this.origins(val); rob@77: } else if ('resource' == key) { rob@77: this.path(val); rob@77: } else if (oldSettings[key] && this.eio[oldSettings[key]]) { rob@77: this.eio[oldSettings[key]] = val; rob@77: } else { rob@77: console.error('Option %s is not valid. Please refer to the README.', key); rob@77: } rob@77: rob@77: return this; rob@77: }; rob@77: rob@77: /** rob@77: * Sets the client serving path. rob@77: * rob@77: * @param {String} pathname rob@77: * @return {Server|String} self when setting or value when getting rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.path = function(v){ rob@77: if (!arguments.length) return this._path; rob@77: this._path = v.replace(/\/$/, ''); rob@77: return this; rob@77: }; rob@77: rob@77: /** rob@77: * Sets the adapter for rooms. rob@77: * rob@77: * @param {Adapter} pathname rob@77: * @return {Server|Adapter} self when setting or value when getting rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.adapter = function(v){ rob@77: if (!arguments.length) return this._adapter; rob@77: this._adapter = v; rob@77: for (var i in this.nsps) { rob@77: if (this.nsps.hasOwnProperty(i)) { rob@77: this.nsps[i].initAdapter(); rob@77: } rob@77: } rob@77: return this; rob@77: }; rob@77: rob@77: /** rob@77: * Sets the allowed origins for requests. rob@77: * rob@77: * @param {String} origins rob@77: * @return {Server|Adapter} self when setting or value when getting rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.origins = function(v){ rob@77: if (!arguments.length) return this._origins; rob@77: rob@77: this._origins = v; rob@77: return this; rob@77: }; rob@77: rob@77: /** rob@77: * Attaches socket.io to a server or port. rob@77: * rob@77: * @param {http.Server|Number} server or port rob@77: * @param {Object} options passed to engine.io rob@77: * @return {Server} self rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.listen = rob@77: Server.prototype.attach = function(srv, opts){ rob@77: if ('function' == typeof srv) { rob@77: var msg = 'You are trying to attach socket.io to an express' + rob@77: 'request handler function. Please pass a http.Server instance.'; rob@77: throw new Error(msg); rob@77: } rob@77: rob@77: // handle a port as a string rob@77: if (Number(srv) == srv) { rob@77: srv = Number(srv); rob@77: } rob@77: rob@77: if ('number' == typeof srv) { rob@77: debug('creating http server and binding to %d', srv); rob@77: var port = srv; rob@77: srv = http.Server(function(req, res){ rob@77: res.writeHead(404); rob@77: res.end(); rob@77: }); rob@77: srv.listen(port); rob@77: } rob@77: rob@77: // set engine.io path to `/socket.io` rob@77: opts = opts || {}; rob@77: opts.path = opts.path || '/socket.io'; rob@77: // set origins verification rob@77: opts.allowRequest = this.checkRequest.bind(this); rob@77: rob@77: // initialize engine rob@77: debug('creating engine.io instance with opts %j', opts); rob@77: this.eio = engine.attach(srv, opts); rob@77: rob@77: // attach static file serving rob@77: if (this._serveClient) this.attachServe(srv); rob@77: rob@77: // bind to engine events rob@77: this.bind(this.eio); rob@77: rob@77: return this; rob@77: }; rob@77: rob@77: /** rob@77: * Attaches the static file serving. rob@77: * rob@77: * @param {Function|http.Server} http server rob@77: * @api private rob@77: */ rob@77: rob@77: Server.prototype.attachServe = function(srv){ rob@77: debug('attaching client serving req handler'); rob@77: var url = this._path + '/socket.io.js'; rob@77: var evs = srv.listeners('request').slice(0); rob@77: var self = this; rob@77: srv.removeAllListeners('request'); rob@77: srv.on('request', function(req, res) { rob@77: if (0 == req.url.indexOf(url)) { rob@77: self.serve(req, res); rob@77: } else { rob@77: for (var i = 0; i < evs.length; i++) { rob@77: evs[i].call(srv, req, res); rob@77: } rob@77: } rob@77: }); rob@77: }; rob@77: rob@77: /** rob@77: * Handles a request serving `/socket.io.js` rob@77: * rob@77: * @param {http.Request} req rob@77: * @param {http.Response} res rob@77: * @api private rob@77: */ rob@77: rob@77: Server.prototype.serve = function(req, res){ rob@77: if (req.headers.etag) { rob@77: if (clientVersion == req.headers.etag) { rob@77: debug('serve client 304'); rob@77: res.writeHead(304); rob@77: res.end(); rob@77: return; rob@77: } rob@77: } rob@77: rob@77: debug('serve client source'); rob@77: res.setHeader('Content-Type', 'application/javascript'); rob@77: res.setHeader('ETag', clientVersion); rob@77: res.writeHead(200); rob@77: res.end(clientSource); rob@77: }; rob@77: rob@77: /** rob@77: * Binds socket.io to an engine.io instance. rob@77: * rob@77: * @param {engine.Server} engine.io (or compatible) server rob@77: * @return {Server} self rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.bind = function(engine){ rob@77: this.engine = engine; rob@77: this.engine.on('connection', this.onconnection.bind(this)); rob@77: return this; rob@77: }; rob@77: rob@77: /** rob@77: * Called with each incoming transport connection. rob@77: * rob@77: * @param {engine.Socket} socket rob@77: * @return {Server} self rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.onconnection = function(conn){ rob@77: debug('incoming connection with id %s', conn.id); rob@77: var client = new Client(this, conn); rob@77: client.connect('/'); rob@77: return this; rob@77: }; rob@77: rob@77: /** rob@77: * Looks up a namespace. rob@77: * rob@77: * @param {String} nsp name rob@77: * @param {Function} optional, nsp `connection` ev handler rob@77: * @api public rob@77: */ rob@77: rob@77: Server.prototype.of = function(name, fn){ rob@77: if (!this.nsps[name]) { rob@77: debug('initializing namespace %s', name); rob@77: var nsp = new Namespace(this, name); rob@77: this.nsps[name] = nsp; rob@77: } rob@77: if (fn) this.nsps[name].on('connect', fn); rob@77: return this.nsps[name]; rob@77: }; rob@77: rob@77: /** rob@77: * Expose main namespace (/). rob@77: */ rob@77: rob@77: ['on', 'to', 'in', 'use', 'emit', 'send', 'write'].forEach(function(fn){ rob@77: Server.prototype[fn] = function(){ rob@77: var nsp = this.sockets[fn]; rob@77: return nsp.apply(this.sockets, arguments); rob@77: }; rob@77: }); rob@77: rob@77: Namespace.flags.forEach(function(flag){ rob@77: Server.prototype.__defineGetter__(flag, function(name){ rob@77: this.flags.push(name); rob@77: return this; rob@77: }); rob@77: }); rob@77: rob@77: /** rob@77: * BC with `io.listen` rob@77: */ rob@77: rob@77: Server.listen = Server;