7 var http = require('http');
8 var read = require('fs').readFileSync;
9 var path = require('path');
10 var exists = require('fs').existsSync;
11 var engine = require('engine.io');
12 var clientVersion = require('socket.io-client/package.json').version;
13 var Client = require('./client');
14 var Emitter = require('events').EventEmitter;
15 var Namespace = require('./namespace');
16 var ParentNamespace = require('./parent-namespace');
17 var Adapter = require('socket.io-adapter');
18 var parser = require('socket.io-parser');
19 var debug = require('debug')('socket.io:server');
20 var url = require('url');
26 module.exports = Server;
29 * Socket.IO client source.
32 var clientSource = undefined;
33 var clientSourceMap = undefined;
38 * @param {http.Server|Number|Object} srv http server, port or options
39 * @param {Object} [opts]
43 function Server(srv, opts){
44 if (!(this instanceof Server)) return new Server(srv, opts);
45 if ('object' == typeof srv && srv instanceof Object && !srv.listen) {
51 this.parentNsps = new Map();
52 this.path(opts.path || '/socket.io');
53 this.serveClient(false !== opts.serveClient);
54 this.parser = opts.parser || parser;
55 this.encoder = new this.parser.Encoder();
56 this.adapter(opts.adapter || Adapter);
57 this.origins(opts.origins || '*:*');
58 this.sockets = this.of('/');
59 if (srv) this.attach(srv, opts);
63 * Server request verification function, that checks for allowed origins
65 * @param {http.IncomingMessage} req request
66 * @param {Function} fn callback to be called with the result: `fn(err, success)`
69 Server.prototype.checkRequest = function(req, fn) {
70 var origin = req.headers.origin || req.headers.referer;
72 // file:// URLs produce a null Origin which can't be authorized via echo-back
73 if ('null' == origin || null == origin) origin = '*';
75 if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn);
76 if (this._origins.indexOf('*:*') !== -1) return fn(null, true);
79 var parts = url.parse(origin);
80 var defaultPort = 'https:' == parts.protocol ? 443 : 80;
81 parts.port = parts.port != null
85 ~this._origins.indexOf(parts.protocol + '//' + parts.hostname + ':' + parts.port) ||
86 ~this._origins.indexOf(parts.hostname + ':' + parts.port) ||
87 ~this._origins.indexOf(parts.hostname + ':*') ||
88 ~this._origins.indexOf('*:' + parts.port);
89 debug('origin %s is %svalid', origin, !!ok ? '' : 'not ');
90 return fn(null, !!ok);
98 * Sets/gets whether client code is being served.
100 * @param {Boolean} v whether to serve client code
101 * @return {Server|Boolean} self when setting or value when getting
105 Server.prototype.serveClient = function(v){
106 if (!arguments.length) return this._serveClient;
107 this._serveClient = v;
108 var resolvePath = function(file){
109 var filepath = path.resolve(__dirname, './../../', file);
110 if (exists(filepath)) {
113 return require.resolve(file);
115 if (v && !clientSource) {
116 clientSource = read(resolvePath( 'socket.io-client/dist/socket.io.js'), 'utf-8');
118 clientSourceMap = read(resolvePath( 'socket.io-client/dist/socket.io.js.map'), 'utf-8');
120 debug('could not load sourcemap file');
127 * Old settings for backwards compatibility
131 "transports": "transports",
132 "heartbeat timeout": "pingTimeout",
133 "heartbeat interval": "pingInterval",
134 "destroy buffer size": "maxHttpBufferSize"
138 * Backwards compatibility.
143 Server.prototype.set = function(key, val){
144 if ('authorization' == key && val) {
145 this.use(function(socket, next) {
146 val(socket.request, function(err, authorized) {
147 if (err) return next(new Error(err));
148 if (!authorized) return next(new Error('Not authorized'));
152 } else if ('origins' == key && val) {
154 } else if ('resource' == key) {
156 } else if (oldSettings[key] && this.eio[oldSettings[key]]) {
157 this.eio[oldSettings[key]] = val;
159 console.error('Option %s is not valid. Please refer to the README.', key);
166 * Executes the middleware for an incoming namespace not already created on the server.
168 * @param {String} name name of incoming namespace
169 * @param {Object} query the query parameters
170 * @param {Function} fn callback
174 Server.prototype.checkNamespace = function(name, query, fn){
175 if (this.parentNsps.size === 0) return fn(false);
177 const keysIterator = this.parentNsps.keys();
180 let nextFn = keysIterator.next();
184 nextFn.value(name, query, (err, allow) => {
188 fn(this.parentNsps.get(nextFn.value).createChild(name));
197 * Sets the client serving path.
199 * @param {String} v pathname
200 * @return {Server|String} self when setting or value when getting
204 Server.prototype.path = function(v){
205 if (!arguments.length) return this._path;
206 this._path = v.replace(/\/$/, '');
211 * Sets the adapter for rooms.
213 * @param {Adapter} v pathname
214 * @return {Server|Adapter} self when setting or value when getting
218 Server.prototype.adapter = function(v){
219 if (!arguments.length) return this._adapter;
221 for (var i in this.nsps) {
222 if (this.nsps.hasOwnProperty(i)) {
223 this.nsps[i].initAdapter();
230 * Sets the allowed origins for requests.
232 * @param {String|String[]} v origins
233 * @return {Server|Adapter} self when setting or value when getting
237 Server.prototype.origins = function(v){
238 if (!arguments.length) return this._origins;
245 * Attaches socket.io to a server or port.
247 * @param {http.Server|Number} server or port
248 * @param {Object} options passed to engine.io
249 * @return {Server} self
253 Server.prototype.listen =
254 Server.prototype.attach = function(srv, opts){
255 if ('function' == typeof srv) {
256 var msg = 'You are trying to attach socket.io to an express ' +
257 'request handler function. Please pass a http.Server instance.';
258 throw new Error(msg);
261 // handle a port as a string
262 if (Number(srv) == srv) {
266 if ('number' == typeof srv) {
267 debug('creating http server and binding to %d', srv);
269 srv = http.Server(function(req, res){
277 // set engine.io path to `/socket.io`
279 opts.path = opts.path || this.path();
280 // set origins verification
281 opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this);
283 if (this.sockets.fns.length > 0) {
284 this.initEngine(srv, opts);
289 var connectPacket = { type: parser.CONNECT, nsp: '/' };
290 this.encoder.encode(connectPacket, function (encodedPacket){
291 // the CONNECT packet will be merged with Engine.IO handshake,
292 // to reduce the number of round trips
293 opts.initialPacket = encodedPacket;
295 self.initEngine(srv, opts);
303 * @param {Object} options passed to engine.io
307 Server.prototype.initEngine = function(srv, opts){
309 debug('creating engine.io instance with opts %j', opts);
310 this.eio = engine.attach(srv, opts);
312 // attach static file serving
313 if (this._serveClient) this.attachServe(srv);
315 // Export http server
316 this.httpServer = srv;
318 // bind to engine events
323 * Attaches the static file serving.
325 * @param {Function|http.Server} srv http server
329 Server.prototype.attachServe = function(srv){
330 debug('attaching client serving req handler');
331 var url = this._path + '/socket.io.js';
332 var urlMap = this._path + '/socket.io.js.map';
333 var evs = srv.listeners('request').slice(0);
335 srv.removeAllListeners('request');
336 srv.on('request', function(req, res) {
337 if (0 === req.url.indexOf(urlMap)) {
338 self.serveMap(req, res);
339 } else if (0 === req.url.indexOf(url)) {
340 self.serve(req, res);
342 for (var i = 0; i < evs.length; i++) {
343 evs[i].call(srv, req, res);
350 * Handles a request serving `/socket.io.js`
352 * @param {http.Request} req
353 * @param {http.Response} res
357 Server.prototype.serve = function(req, res){
358 // Per the standard, ETags must be quoted:
359 // https://tools.ietf.org/html/rfc7232#section-2.3
360 var expectedEtag = '"' + clientVersion + '"';
362 var etag = req.headers['if-none-match'];
364 if (expectedEtag == etag) {
365 debug('serve client 304');
372 debug('serve client source');
373 res.setHeader("Cache-Control", "public, max-age=0");
374 res.setHeader('Content-Type', 'application/javascript');
375 res.setHeader('ETag', expectedEtag);
377 res.end(clientSource);
381 * Handles a request serving `/socket.io.js.map`
383 * @param {http.Request} req
384 * @param {http.Response} res
388 Server.prototype.serveMap = function(req, res){
389 // Per the standard, ETags must be quoted:
390 // https://tools.ietf.org/html/rfc7232#section-2.3
391 var expectedEtag = '"' + clientVersion + '"';
393 var etag = req.headers['if-none-match'];
395 if (expectedEtag == etag) {
396 debug('serve client 304');
403 debug('serve client sourcemap');
404 res.setHeader('Content-Type', 'application/json');
405 res.setHeader('ETag', expectedEtag);
407 res.end(clientSourceMap);
411 * Binds socket.io to an engine.io instance.
413 * @param {engine.Server} engine engine.io (or compatible) server
414 * @return {Server} self
418 Server.prototype.bind = function(engine){
419 this.engine = engine;
420 this.engine.on('connection', this.onconnection.bind(this));
425 * Called with each incoming transport connection.
427 * @param {engine.Socket} conn
428 * @return {Server} self
432 Server.prototype.onconnection = function(conn){
433 debug('incoming connection with id %s', conn.id);
434 var client = new Client(this, conn);
440 * Looks up a namespace.
442 * @param {String|RegExp|Function} name nsp name
443 * @param {Function} [fn] optional, nsp `connection` ev handler
447 Server.prototype.of = function(name, fn){
448 if (typeof name === 'function' || name instanceof RegExp) {
449 const parentNsp = new ParentNamespace(this);
450 debug('initializing parent namespace %s', parentNsp.name);
451 if (typeof name === 'function') {
452 this.parentNsps.set(name, parentNsp);
454 this.parentNsps.set((nsp, conn, next) => next(null, name.test(nsp)), parentNsp);
456 if (fn) parentNsp.on('connect', fn);
460 if (String(name)[0] !== '/') name = '/' + name;
462 var nsp = this.nsps[name];
464 debug('initializing namespace %s', name);
465 nsp = new Namespace(this, name);
466 this.nsps[name] = nsp;
468 if (fn) nsp.on('connect', fn);
473 * Closes server connection
475 * @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed
479 Server.prototype.close = function(fn){
480 for (var id in this.nsps['/'].sockets) {
481 if (this.nsps['/'].sockets.hasOwnProperty(id)) {
482 this.nsps['/'].sockets[id].onclose();
488 if (this.httpServer) {
489 this.httpServer.close(fn);
496 * Expose main namespace (/).
499 var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){
500 return typeof Emitter.prototype[key] === 'function';
503 emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress', 'binary']).forEach(function(fn){
504 Server.prototype[fn] = function(){
505 return this.sockets[fn].apply(this.sockets, arguments);
509 Namespace.flags.forEach(function(flag){
510 Object.defineProperty(Server.prototype, flag, {
512 this.sockets.flags = this.sockets.flags || {};
513 this.sockets.flags[flag] = true;
520 * BC with `io.listen`
523 Server.listen = Server;