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