UNPKG

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