comunicationLeap/node_modules/ws/lib/WebSocketServer.js
node_modules/ws/lib/WebSocketServer.jsRaw
1/*!
2 * ws: a node.js websocket client
3 * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com>
4 * MIT Licensed
5 */
6
7var util = require('util')
8 , events = require('events')
9 , http = require('http')
10 , crypto = require('crypto')
11 , url = require('url')
12 , Options = require('options')
13 , WebSocket = require('./WebSocket')
14 , tls = require('tls')
15 , url = require('url');
16
17/**
18 * WebSocket Server implementation
19 */
20
21function WebSocketServer(options, callback) {
22 options = new Options({
23 host: '0.0.0.0',
24 port: null,
25 server: null,
26 verifyClient: null,
27 handleProtocols: null,
28 path: null,
29 noServer: false,
30 disableHixie: false,
31 clientTracking: true
32 }).merge(options);
33
34 if (!options.isDefinedAndNonNull('port') && !options.isDefinedAndNonNull('server') && !options.value.noServer) {
35 throw new TypeError('`port` or a `server` must be provided');
36 }
37
38 var self = this;
39
40 if (options.isDefinedAndNonNull('port')) {
41 this._server = http.createServer(function (req, res) {
42 res.writeHead(200, {'Content-Type': 'text/plain'});
43 res.end('Not implemented');
44 });
45 this._server.listen(options.value.port, options.value.host, callback);
46 this._closeServer = function() { self._server.close(); };
47 }
48 else if (options.value.server) {
49 this._server = options.value.server;
50 if (options.value.path) {
51 // take note of the path, to avoid collisions when multiple websocket servers are
52 // listening on the same http server
53 if (this._server._webSocketPaths && options.value.server._webSocketPaths[options.value.path]) {
54 throw new Error('two instances of WebSocketServer cannot listen on the same http server path');
55 }
56 if (typeof this._server._webSocketPaths !== 'object') {
57 this._server._webSocketPaths = {};
58 }
59 this._server._webSocketPaths[options.value.path] = 1;
60 }
61 }
62 if (this._server) this._server.once('listening', function() { self.emit('listening'); });
63
64 if (typeof this._server != 'undefined') {
65 this._server.on('error', function(error) {
66 self.emit('error', error)
67 });
68 this._server.on('upgrade', function(req, socket, upgradeHead) {
69 //copy upgradeHead to avoid retention of large slab buffers used in node core
70 var head = new Buffer(upgradeHead.length);
71 upgradeHead.copy(head);
72
73 self.handleUpgrade(req, socket, head, function(client) {
74 self.emit('connection'+req.url, client);
75 self.emit('connection', client);
76 });
77 });
78 }
79
80 this.options = options.value;
81 this.path = options.value.path;
82 this.clients = [];
83}
84
85/**
86 * Inherits from EventEmitter.
87 */
88
89util.inherits(WebSocketServer, events.EventEmitter);
90
91/**
92 * Immediately shuts down the connection.
93 *
94 * @api public
95 */
96
97WebSocketServer.prototype.close = function() {
98 // terminate all associated clients
99 var error = null;
100 try {
101 for (var i = 0, l = this.clients.length; i < l; ++i) {
102 this.clients[i].terminate();
103 }
104 }
105 catch (e) {
106 error = e;
107 }
108
109 // remove path descriptor, if any
110 if (this.path && this._server._webSocketPaths) {
111 delete this._server._webSocketPaths[this.path];
112 if (Object.keys(this._server._webSocketPaths).length == 0) {
113 delete this._server._webSocketPaths;
114 }
115 }
116
117 // close the http server if it was internally created
118 try {
119 if (typeof this._closeServer !== 'undefined') {
120 this._closeServer();
121 }
122 }
123 finally {
124 delete this._server;
125 }
126 if (error) throw error;
127}
128
129/**
130 * Handle a HTTP Upgrade request.
131 *
132 * @api public
133 */
134
135WebSocketServer.prototype.handleUpgrade = function(req, socket, upgradeHead, cb) {
136 // check for wrong path
137 if (this.options.path) {
138 var u = url.parse(req.url);
139 if (u && u.pathname !== this.options.path) return;
140 }
141
142 if (typeof req.headers.upgrade === 'undefined' || req.headers.upgrade.toLowerCase() !== 'websocket') {
143 abortConnection(socket, 400, 'Bad Request');
144 return;
145 }
146
147 if (req.headers['sec-websocket-key1']) handleHixieUpgrade.apply(this, arguments);
148 else handleHybiUpgrade.apply(this, arguments);
149}
150
151module.exports = WebSocketServer;
152
153/**
154 * Entirely private apis,
155 * which may or may not be bound to a sepcific WebSocket instance.
156 */
157
158function handleHybiUpgrade(req, socket, upgradeHead, cb) {
159 // handle premature socket errors
160 var errorHandler = function() {
161 try { socket.destroy(); } catch (e) {}
162 }
163 socket.on('error', errorHandler);
164
165 // verify key presence
166 if (!req.headers['sec-websocket-key']) {
167 abortConnection(socket, 400, 'Bad Request');
168 return;
169 }
170
171 // verify version
172 var version = parseInt(req.headers['sec-websocket-version']);
173 if ([8, 13].indexOf(version) === -1) {
174 abortConnection(socket, 400, 'Bad Request');
175 return;
176 }
177
178 // verify protocol
179 var protocols = req.headers['sec-websocket-protocol'];
180
181 // verify client
182 var origin = version < 13 ?
183 req.headers['sec-websocket-origin'] :
184 req.headers['origin'];
185
186 // handler to call when the connection sequence completes
187 var self = this;
188 var completeHybiUpgrade2 = function(protocol) {
189
190 // calc key
191 var key = req.headers['sec-websocket-key'];
192 var shasum = crypto.createHash('sha1');
193 shasum.update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
194 key = shasum.digest('base64');
195
196 var headers = [
197 'HTTP/1.1 101 Switching Protocols'
198 , 'Upgrade: websocket'
199 , 'Connection: Upgrade'
200 , 'Sec-WebSocket-Accept: ' + key
201 ];
202
203 if (typeof protocol != 'undefined') {
204 headers.push('Sec-WebSocket-Protocol: ' + protocol);
205 }
206
207 // allows external modification/inspection of handshake headers
208 self.emit('headers', headers);
209
210 socket.setTimeout(0);
211 socket.setNoDelay(true);
212 try {
213 socket.write(headers.concat('', '').join('\r\n'));
214 }
215 catch (e) {
216 // if the upgrade write fails, shut the connection down hard
217 try { socket.destroy(); } catch (e) {}
218 return;
219 }
220
221 var client = new WebSocket([req, socket, upgradeHead], {
222 protocolVersion: version,
223 protocol: protocol
224 });
225
226 if (self.options.clientTracking) {
227 self.clients.push(client);
228 client.on('close', function() {
229 var index = self.clients.indexOf(client);
230 if (index != -1) {
231 self.clients.splice(index, 1);
232 }
233 });
234 }
235
236 // signal upgrade complete
237 socket.removeListener('error', errorHandler);
238 cb(client);
239 }
240
241 // optionally call external protocol selection handler before
242 // calling completeHybiUpgrade2
243 var completeHybiUpgrade1 = function() {
244 // choose from the sub-protocols
245 if (typeof self.options.handleProtocols == 'function') {
246 var protList = (protocols || "").split(/, */);
247 var callbackCalled = false;
248 var res = self.options.handleProtocols(protList, function(result, protocol) {
249 callbackCalled = true;
250 if (!result) abortConnection(socket, 404, 'Unauthorized')
251 else completeHybiUpgrade2(protocol);
252 });
253 if (!callbackCalled) {
254 // the handleProtocols handler never called our callback
255 abortConnection(socket, 501, 'Could not process protocols');
256 }
257 return;
258 } else {
259 if (typeof protocols !== 'undefined') {
260 completeHybiUpgrade2(protocols.split(/, */)[0]);
261 }
262 else {
263 completeHybiUpgrade2();
264 }
265 }
266 }
267
268 // optionally call external client verification handler
269 if (typeof this.options.verifyClient == 'function') {
270 var info = {
271 origin: origin,
272 secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined',
273 req: req
274 };
275 if (this.options.verifyClient.length == 2) {
276 this.options.verifyClient(info, function(result) {
277 if (!result) abortConnection(socket, 401, 'Unauthorized')
278 else completeHybiUpgrade1();
279 });
280 return;
281 }
282 else if (!this.options.verifyClient(info)) {
283 abortConnection(socket, 401, 'Unauthorized');
284 return;
285 }
286 }
287
288 completeHybiUpgrade1();
289}
290
291function handleHixieUpgrade(req, socket, upgradeHead, cb) {
292 // handle premature socket errors
293 var errorHandler = function() {
294 try { socket.destroy(); } catch (e) {}
295 }
296 socket.on('error', errorHandler);
297
298 // bail if options prevent hixie
299 if (this.options.disableHixie) {
300 abortConnection(socket, 401, 'Hixie support disabled');
301 return;
302 }
303
304 // verify key presence
305 if (!req.headers['sec-websocket-key2']) {
306 abortConnection(socket, 400, 'Bad Request');
307 return;
308 }
309
310 var origin = req.headers['origin']
311 , self = this;
312
313 // setup handshake completion to run after client has been verified
314 var onClientVerified = function() {
315 var wshost;
316 if (!req.headers['x-forwarded-host'])
317 wshost = req.headers.host;
318 else
319 wshost = req.headers['x-forwarded-host'];
320 var location = ((req.headers['x-forwarded-proto'] === 'https' || socket.encrypted) ? 'wss' : 'ws') + '://' + wshost + req.url
321 , protocol = req.headers['sec-websocket-protocol'];
322
323 // handshake completion code to run once nonce has been successfully retrieved
324 var completeHandshake = function(nonce, rest) {
325 // calculate key
326 var k1 = req.headers['sec-websocket-key1']
327 , k2 = req.headers['sec-websocket-key2']
328 , md5 = crypto.createHash('md5');
329
330 [k1, k2].forEach(function (k) {
331 var n = parseInt(k.replace(/[^\d]/g, ''))
332 , spaces = k.replace(/[^ ]/g, '').length;
333 if (spaces === 0 || n % spaces !== 0){
334 abortConnection(socket, 400, 'Bad Request');
335 return;
336 }
337 n /= spaces;
338 md5.update(String.fromCharCode(
339 n >> 24 & 0xFF,
340 n >> 16 & 0xFF,
341 n >> 8 & 0xFF,
342 n & 0xFF));
343 });
344 md5.update(nonce.toString('binary'));
345
346 var headers = [
347 'HTTP/1.1 101 Switching Protocols'
348 , 'Upgrade: WebSocket'
349 , 'Connection: Upgrade'
350 , 'Sec-WebSocket-Location: ' + location
351 ];
352 if (typeof protocol != 'undefined') headers.push('Sec-WebSocket-Protocol: ' + protocol);
353 if (typeof origin != 'undefined') headers.push('Sec-WebSocket-Origin: ' + origin);
354
355 socket.setTimeout(0);
356 socket.setNoDelay(true);
357 try {
358 // merge header and hash buffer
359 var headerBuffer = new Buffer(headers.concat('', '').join('\r\n'));
360 var hashBuffer = new Buffer(md5.digest('binary'), 'binary');
361 var handshakeBuffer = new Buffer(headerBuffer.length + hashBuffer.length);
362 headerBuffer.copy(handshakeBuffer, 0);
363 hashBuffer.copy(handshakeBuffer, headerBuffer.length);
364
365 // do a single write, which - upon success - causes a new client websocket to be setup
366 socket.write(handshakeBuffer, 'binary', function(err) {
367 if (err) return; // do not create client if an error happens
368 var client = new WebSocket([req, socket, rest], {
369 protocolVersion: 'hixie-76',
370 protocol: protocol
371 });
372 if (self.options.clientTracking) {
373 self.clients.push(client);
374 client.on('close', function() {
375 var index = self.clients.indexOf(client);
376 if (index != -1) {
377 self.clients.splice(index, 1);
378 }
379 });
380 }
381
382 // signal upgrade complete
383 socket.removeListener('error', errorHandler);
384 cb(client);
385 });
386 }
387 catch (e) {
388 try { socket.destroy(); } catch (e) {}
389 return;
390 }
391 }
392
393 // retrieve nonce
394 var nonceLength = 8;
395 if (upgradeHead && upgradeHead.length >= nonceLength) {
396 var nonce = upgradeHead.slice(0, nonceLength);
397 var rest = upgradeHead.length > nonceLength ? upgradeHead.slice(nonceLength) : null;
398 completeHandshake.call(self, nonce, rest);
399 }
400 else {
401 // nonce not present in upgradeHead, so we must wait for enough data
402 // data to arrive before continuing
403 var nonce = new Buffer(nonceLength);
404 upgradeHead.copy(nonce, 0);
405 var received = upgradeHead.length;
406 var rest = null;
407 var handler = function (data) {
408 var toRead = Math.min(data.length, nonceLength - received);
409 if (toRead === 0) return;
410 data.copy(nonce, received, 0, toRead);
411 received += toRead;
412 if (received == nonceLength) {
413 socket.removeListener('data', handler);
414 if (toRead < data.length) rest = data.slice(toRead);
415 completeHandshake.call(self, nonce, rest);
416 }
417 }
418 socket.on('data', handler);
419 }
420 }
421
422 // verify client
423 if (typeof this.options.verifyClient == 'function') {
424 var info = {
425 origin: origin,
426 secure: typeof req.connection.authorized !== 'undefined' || typeof req.connection.encrypted !== 'undefined',
427 req: req
428 };
429 if (this.options.verifyClient.length == 2) {
430 var self = this;
431 this.options.verifyClient(info, function(result) {
432 if (!result) abortConnection(socket, 401, 'Unauthorized')
433 else onClientVerified.apply(self);
434 });
435 return;
436 }
437 else if (!this.options.verifyClient(info)) {
438 abortConnection(socket, 401, 'Unauthorized');
439 return;
440 }
441 }
442
443 // no client verification required
444 onClientVerified();
445}
446
447function abortConnection(socket, code, name) {
448 try {
449 var response = [
450 'HTTP/1.1 ' + code + ' ' + name,
451 'Content-type: text/html'
452 ];
453 socket.write(response.concat('', '').join('\r\n'));
454 }
455 catch (e) { /* ignore errors - we've aborted this connection */ }
456 finally {
457 // ensure that an early aborted connection is shut down completely
458 try { socket.destroy(); } catch (e) {}
459 }
460}