index.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. /**
  2. * Module dependencies.
  3. */
  4. var http = require('http');
  5. var read = require('fs').readFileSync;
  6. var engine = require('engine.io');
  7. var client = require('socket.io-client');
  8. var clientVersion = require('socket.io-client/package').version;
  9. var Client = require('./client');
  10. var Namespace = require('./namespace');
  11. var Adapter = require('socket.io-adapter');
  12. var debug = require('debug')('socket.io:server');
  13. var url = require('url');
  14. /**
  15. * Module exports.
  16. */
  17. module.exports = Server;
  18. /**
  19. * Socket.IO client source.
  20. */
  21. var clientSource = read(require.resolve('socket.io-client/socket.io.js'), 'utf-8');
  22. /**
  23. * Server constructor.
  24. *
  25. * @param {http.Server|Number|Object} srv http server, port or options
  26. * @param {Object} opts
  27. * @api public
  28. */
  29. function Server(srv, opts){
  30. if (!(this instanceof Server)) return new Server(srv, opts);
  31. if ('object' == typeof srv && !srv.listen) {
  32. opts = srv;
  33. srv = null;
  34. }
  35. opts = opts || {};
  36. this.nsps = {};
  37. this.path(opts.path || '/socket.io');
  38. this.serveClient(false !== opts.serveClient);
  39. this.adapter(opts.adapter || Adapter);
  40. this.origins(opts.origins || '*:*');
  41. this.sockets = this.of('/');
  42. if (srv) this.attach(srv, opts);
  43. }
  44. /**
  45. * Server request verification function, that checks for allowed origins
  46. *
  47. * @param {http.IncomingMessage} req request
  48. * @param {Function} fn callback to be called with the result: `fn(err, success)`
  49. */
  50. Server.prototype.checkRequest = function(req, fn) {
  51. var origin = req.headers.origin || req.headers.referer;
  52. // file:// URLs produce a null Origin which can't be authorized via echo-back
  53. if ('null' == origin || null == origin) origin = '*';
  54. if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn);
  55. if (this._origins.indexOf('*:*') !== -1) return fn(null, true);
  56. if (origin) {
  57. try {
  58. var parts = url.parse(origin);
  59. var defaultPort = 'https:' == parts.protocol ? 443 : 80;
  60. parts.port = parts.port != null
  61. ? parts.port
  62. : defaultPort;
  63. var ok =
  64. ~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
  65. ~this._origins.indexOf(parts.hostname + ':*') ||
  66. ~this._origins.indexOf('*:' + parts.port);
  67. return fn(null, !!ok);
  68. } catch (ex) {
  69. }
  70. }
  71. fn(null, false);
  72. };
  73. /**
  74. * Sets/gets whether client code is being served.
  75. *
  76. * @param {Boolean} v whether to serve client code
  77. * @return {Server|Boolean} self when setting or value when getting
  78. * @api public
  79. */
  80. Server.prototype.serveClient = function(v){
  81. if (!arguments.length) return this._serveClient;
  82. this._serveClient = v;
  83. return this;
  84. };
  85. /**
  86. * Old settings for backwards compatibility
  87. */
  88. var oldSettings = {
  89. "transports": "transports",
  90. "heartbeat timeout": "pingTimeout",
  91. "heartbeat interval": "pingInterval",
  92. "destroy buffer size": "maxHttpBufferSize"
  93. };
  94. /**
  95. * Backwards compatiblity.
  96. *
  97. * @api public
  98. */
  99. Server.prototype.set = function(key, val){
  100. if ('authorization' == key && val) {
  101. this.use(function(socket, next) {
  102. val(socket.request, function(err, authorized) {
  103. if (err) return next(new Error(err));
  104. if (!authorized) return next(new Error('Not authorized'));
  105. next();
  106. });
  107. });
  108. } else if ('origins' == key && val) {
  109. this.origins(val);
  110. } else if ('resource' == key) {
  111. this.path(val);
  112. } else if (oldSettings[key] && this.eio[oldSettings[key]]) {
  113. this.eio[oldSettings[key]] = val;
  114. } else {
  115. console.error('Option %s is not valid. Please refer to the README.', key);
  116. }
  117. return this;
  118. };
  119. /**
  120. * Sets the client serving path.
  121. *
  122. * @param {String} v pathname
  123. * @return {Server|String} self when setting or value when getting
  124. * @api public
  125. */
  126. Server.prototype.path = function(v){
  127. if (!arguments.length) return this._path;
  128. this._path = v.replace(/\/$/, '');
  129. return this;
  130. };
  131. /**
  132. * Sets the adapter for rooms.
  133. *
  134. * @param {Adapter} v pathname
  135. * @return {Server|Adapter} self when setting or value when getting
  136. * @api public
  137. */
  138. Server.prototype.adapter = function(v){
  139. if (!arguments.length) return this._adapter;
  140. this._adapter = v;
  141. for (var i in this.nsps) {
  142. if (this.nsps.hasOwnProperty(i)) {
  143. this.nsps[i].initAdapter();
  144. }
  145. }
  146. return this;
  147. };
  148. /**
  149. * Sets the allowed origins for requests.
  150. *
  151. * @param {String} v origins
  152. * @return {Server|Adapter} self when setting or value when getting
  153. * @api public
  154. */
  155. Server.prototype.origins = function(v){
  156. if (!arguments.length) return this._origins;
  157. this._origins = v;
  158. return this;
  159. };
  160. /**
  161. * Attaches socket.io to a server or port.
  162. *
  163. * @param {http.Server|Number} server or port
  164. * @param {Object} options passed to engine.io
  165. * @return {Server} self
  166. * @api public
  167. */
  168. Server.prototype.listen =
  169. Server.prototype.attach = function(srv, opts){
  170. if ('function' == typeof srv) {
  171. var msg = 'You are trying to attach socket.io to an express ' +
  172. 'request handler function. Please pass a http.Server instance.';
  173. throw new Error(msg);
  174. }
  175. // handle a port as a string
  176. if (Number(srv) == srv) {
  177. srv = Number(srv);
  178. }
  179. if ('number' == typeof srv) {
  180. debug('creating http server and binding to %d', srv);
  181. var port = srv;
  182. srv = http.Server(function(req, res){
  183. res.writeHead(404);
  184. res.end();
  185. });
  186. srv.listen(port);
  187. }
  188. // set engine.io path to `/socket.io`
  189. opts = opts || {};
  190. opts.path = opts.path || this.path();
  191. // set origins verification
  192. opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this);
  193. // initialize engine
  194. debug('creating engine.io instance with opts %j', opts);
  195. this.eio = engine.attach(srv, opts);
  196. // attach static file serving
  197. if (this._serveClient) this.attachServe(srv);
  198. // Export http server
  199. this.httpServer = srv;
  200. // bind to engine events
  201. this.bind(this.eio);
  202. return this;
  203. };
  204. /**
  205. * Attaches the static file serving.
  206. *
  207. * @param {Function|http.Server} srv http server
  208. * @api private
  209. */
  210. Server.prototype.attachServe = function(srv){
  211. debug('attaching client serving req handler');
  212. var url = this._path + '/socket.io.js';
  213. var evs = srv.listeners('request').slice(0);
  214. var self = this;
  215. srv.removeAllListeners('request');
  216. srv.on('request', function(req, res) {
  217. if (0 === req.url.indexOf(url)) {
  218. self.serve(req, res);
  219. } else {
  220. for (var i = 0; i < evs.length; i++) {
  221. evs[i].call(srv, req, res);
  222. }
  223. }
  224. });
  225. };
  226. /**
  227. * Handles a request serving `/socket.io.js`
  228. *
  229. * @param {http.Request} req
  230. * @param {http.Response} res
  231. * @api private
  232. */
  233. Server.prototype.serve = function(req, res){
  234. var etag = req.headers['if-none-match'];
  235. if (etag) {
  236. if (clientVersion == etag) {
  237. debug('serve client 304');
  238. res.writeHead(304);
  239. res.end();
  240. return;
  241. }
  242. }
  243. debug('serve client source');
  244. res.setHeader('Content-Type', 'application/javascript');
  245. res.setHeader('ETag', clientVersion);
  246. res.writeHead(200);
  247. res.end(clientSource);
  248. };
  249. /**
  250. * Binds socket.io to an engine.io instance.
  251. *
  252. * @param {engine.Server} engine engine.io (or compatible) server
  253. * @return {Server} self
  254. * @api public
  255. */
  256. Server.prototype.bind = function(engine){
  257. this.engine = engine;
  258. this.engine.on('connection', this.onconnection.bind(this));
  259. return this;
  260. };
  261. /**
  262. * Called with each incoming transport connection.
  263. *
  264. * @param {engine.Socket} conn
  265. * @return {Server} self
  266. * @api public
  267. */
  268. Server.prototype.onconnection = function(conn){
  269. debug('incoming connection with id %s', conn.id);
  270. var client = new Client(this, conn);
  271. client.connect('/');
  272. return this;
  273. };
  274. /**
  275. * Looks up a namespace.
  276. *
  277. * @param {String} name nsp name
  278. * @param {Function} fn optional, nsp `connection` ev handler
  279. * @api public
  280. */
  281. Server.prototype.of = function(name, fn){
  282. if (String(name)[0] !== '/') name = '/' + name;
  283. var nsp = this.nsps[name];
  284. if (!nsp) {
  285. debug('initializing namespace %s', name);
  286. nsp = new Namespace(this, name);
  287. this.nsps[name] = nsp;
  288. }
  289. if (fn) nsp.on('connect', fn);
  290. return nsp;
  291. };
  292. /**
  293. * Closes server connection
  294. *
  295. * @api public
  296. */
  297. Server.prototype.close = function(){
  298. for (var id in this.nsps['/'].sockets) {
  299. if (this.nsps['/'].sockets.hasOwnProperty(id)) {
  300. this.nsps['/'].sockets[id].onclose();
  301. }
  302. }
  303. this.engine.close();
  304. if(this.httpServer){
  305. this.httpServer.close();
  306. }
  307. };
  308. /**
  309. * Expose main namespace (/).
  310. */
  311. ['on', 'to', 'in', 'use', 'emit', 'send', 'write', 'clients', 'compress'].forEach(function(fn){
  312. Server.prototype[fn] = function(){
  313. var nsp = this.sockets[fn];
  314. return nsp.apply(this.sockets, arguments);
  315. };
  316. });
  317. Namespace.flags.forEach(function(flag){
  318. Server.prototype.__defineGetter__(flag, function(){
  319. this.sockets.flags = this.sockets.flags || {};
  320. this.sockets.flags[flag] = true;
  321. return this;
  322. });
  323. });
  324. /**
  325. * BC with `io.listen`
  326. */
  327. Server.listen = Server;