WebSocketServer.js 15 KB

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