You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

379 lines
8.4 KiB

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