Ein Projekt das es ermöglicht Beerpong über das Internet von zwei unabhängigen positionen aus zu spielen. Entstehung im Rahmen einer Praktikumsaufgabe im Fach Interaktion.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

WebSocketServer.js 15KB

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