Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

websocket-server.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. 'use strict';
  2. const EventEmitter = require('events');
  3. const { createHash } = require('crypto');
  4. const { createServer, STATUS_CODES } = require('http');
  5. const PerMessageDeflate = require('./permessage-deflate');
  6. const WebSocket = require('./websocket');
  7. const { format, parse } = require('./extension');
  8. const { GUID } = require('./constants');
  9. const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
  10. const kUsedByWebSocketServer = Symbol('kUsedByWebSocketServer');
  11. /**
  12. * Class representing a WebSocket server.
  13. *
  14. * @extends EventEmitter
  15. */
  16. class WebSocketServer extends EventEmitter {
  17. /**
  18. * Create a `WebSocketServer` instance.
  19. *
  20. * @param {Object} options Configuration options
  21. * @param {Number} options.backlog The maximum length of the queue of pending
  22. * connections
  23. * @param {Boolean} options.clientTracking Specifies whether or not to track
  24. * clients
  25. * @param {Function} options.handleProtocols A hook to handle protocols
  26. * @param {String} options.host The hostname where to bind the server
  27. * @param {Number} options.maxPayload The maximum allowed message size
  28. * @param {Boolean} options.noServer Enable no server mode
  29. * @param {String} options.path Accept only connections matching this path
  30. * @param {(Boolean|Object)} options.perMessageDeflate Enable/disable
  31. * permessage-deflate
  32. * @param {Number} options.port The port where to bind the server
  33. * @param {http.Server} options.server A pre-created HTTP/S server to use
  34. * @param {Function} options.verifyClient A hook to reject connections
  35. * @param {Function} callback A listener for the `listening` event
  36. */
  37. constructor(options, callback) {
  38. super();
  39. options = {
  40. maxPayload: 100 * 1024 * 1024,
  41. perMessageDeflate: false,
  42. handleProtocols: null,
  43. clientTracking: true,
  44. verifyClient: null,
  45. noServer: false,
  46. backlog: null, // use default (511 as implemented in net.js)
  47. server: null,
  48. host: null,
  49. path: null,
  50. port: null,
  51. ...options
  52. };
  53. if (options.port == null && !options.server && !options.noServer) {
  54. throw new TypeError(
  55. 'One of the "port", "server", or "noServer" options must be specified'
  56. );
  57. }
  58. if (options.port != null) {
  59. this._server = createServer((req, res) => {
  60. const body = STATUS_CODES[426];
  61. res.writeHead(426, {
  62. 'Content-Length': body.length,
  63. 'Content-Type': 'text/plain'
  64. });
  65. res.end(body);
  66. });
  67. this._server.listen(
  68. options.port,
  69. options.host,
  70. options.backlog,
  71. callback
  72. );
  73. } else if (options.server) {
  74. if (options.server[kUsedByWebSocketServer]) {
  75. throw new Error(
  76. 'The HTTP/S server is already being used by another WebSocket server'
  77. );
  78. }
  79. options.server[kUsedByWebSocketServer] = true;
  80. this._server = options.server;
  81. }
  82. if (this._server) {
  83. this._removeListeners = addListeners(this._server, {
  84. listening: this.emit.bind(this, 'listening'),
  85. error: this.emit.bind(this, 'error'),
  86. upgrade: (req, socket, head) => {
  87. this.handleUpgrade(req, socket, head, (ws) => {
  88. this.emit('connection', ws, req);
  89. });
  90. }
  91. });
  92. }
  93. if (options.perMessageDeflate === true) options.perMessageDeflate = {};
  94. if (options.clientTracking) this.clients = new Set();
  95. this.options = options;
  96. }
  97. /**
  98. * Returns the bound address, the address family name, and port of the server
  99. * as reported by the operating system if listening on an IP socket.
  100. * If the server is listening on a pipe or UNIX domain socket, the name is
  101. * returned as a string.
  102. *
  103. * @return {(Object|String|null)} The address of the server
  104. * @public
  105. */
  106. address() {
  107. if (this.options.noServer) {
  108. throw new Error('The server is operating in "noServer" mode');
  109. }
  110. if (!this._server) return null;
  111. return this._server.address();
  112. }
  113. /**
  114. * Close the server.
  115. *
  116. * @param {Function} cb Callback
  117. * @public
  118. */
  119. close(cb) {
  120. if (cb) this.once('close', cb);
  121. //
  122. // Terminate all associated clients.
  123. //
  124. if (this.clients) {
  125. for (const client of this.clients) client.terminate();
  126. }
  127. const server = this._server;
  128. if (server) {
  129. this._removeListeners();
  130. this._removeListeners = this._server = null;
  131. //
  132. // Close the http server if it was internally created.
  133. //
  134. if (this.options.port != null) {
  135. server.close(() => this.emit('close'));
  136. return;
  137. }
  138. delete server[kUsedByWebSocketServer];
  139. }
  140. process.nextTick(emitClose, this);
  141. }
  142. /**
  143. * See if a given request should be handled by this server instance.
  144. *
  145. * @param {http.IncomingMessage} req Request object to inspect
  146. * @return {Boolean} `true` if the request is valid, else `false`
  147. * @public
  148. */
  149. shouldHandle(req) {
  150. if (this.options.path) {
  151. const index = req.url.indexOf('?');
  152. const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
  153. if (pathname !== this.options.path) return false;
  154. }
  155. return true;
  156. }
  157. /**
  158. * Handle a HTTP Upgrade request.
  159. *
  160. * @param {http.IncomingMessage} req The request object
  161. * @param {net.Socket} socket The network socket between the server and client
  162. * @param {Buffer} head The first packet of the upgraded stream
  163. * @param {Function} cb Callback
  164. * @public
  165. */
  166. handleUpgrade(req, socket, head, cb) {
  167. socket.on('error', socketOnError);
  168. const key =
  169. req.headers['sec-websocket-key'] !== undefined
  170. ? req.headers['sec-websocket-key'].trim()
  171. : false;
  172. const version = +req.headers['sec-websocket-version'];
  173. const extensions = {};
  174. if (
  175. req.method !== 'GET' ||
  176. req.headers.upgrade.toLowerCase() !== 'websocket' ||
  177. !key ||
  178. !keyRegex.test(key) ||
  179. (version !== 8 && version !== 13) ||
  180. !this.shouldHandle(req)
  181. ) {
  182. return abortHandshake(socket, 400);
  183. }
  184. if (this.options.perMessageDeflate) {
  185. const perMessageDeflate = new PerMessageDeflate(
  186. this.options.perMessageDeflate,
  187. true,
  188. this.options.maxPayload
  189. );
  190. try {
  191. const offers = parse(req.headers['sec-websocket-extensions']);
  192. if (offers[PerMessageDeflate.extensionName]) {
  193. perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
  194. extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
  195. }
  196. } catch (err) {
  197. return abortHandshake(socket, 400);
  198. }
  199. }
  200. //
  201. // Optionally call external client verification handler.
  202. //
  203. if (this.options.verifyClient) {
  204. const info = {
  205. origin:
  206. req.headers[`${version === 8 ? 'sec-websocket-origin' : 'origin'}`],
  207. secure: !!(req.connection.authorized || req.connection.encrypted),
  208. req
  209. };
  210. if (this.options.verifyClient.length === 2) {
  211. this.options.verifyClient(info, (verified, code, message, headers) => {
  212. if (!verified) {
  213. return abortHandshake(socket, code || 401, message, headers);
  214. }
  215. this.completeUpgrade(key, extensions, req, socket, head, cb);
  216. });
  217. return;
  218. }
  219. if (!this.options.verifyClient(info)) return abortHandshake(socket, 401);
  220. }
  221. this.completeUpgrade(key, extensions, req, socket, head, cb);
  222. }
  223. /**
  224. * Upgrade the connection to WebSocket.
  225. *
  226. * @param {String} key The value of the `Sec-WebSocket-Key` header
  227. * @param {Object} extensions The accepted extensions
  228. * @param {http.IncomingMessage} req The request object
  229. * @param {net.Socket} socket The network socket between the server and client
  230. * @param {Buffer} head The first packet of the upgraded stream
  231. * @param {Function} cb Callback
  232. * @private
  233. */
  234. completeUpgrade(key, extensions, req, socket, head, cb) {
  235. //
  236. // Destroy the socket if the client has already sent a FIN packet.
  237. //
  238. if (!socket.readable || !socket.writable) return socket.destroy();
  239. const digest = createHash('sha1')
  240. .update(key + GUID)
  241. .digest('base64');
  242. const headers = [
  243. 'HTTP/1.1 101 Switching Protocols',
  244. 'Upgrade: websocket',
  245. 'Connection: Upgrade',
  246. `Sec-WebSocket-Accept: ${digest}`
  247. ];
  248. const ws = new WebSocket(null);
  249. let protocol = req.headers['sec-websocket-protocol'];
  250. if (protocol) {
  251. protocol = protocol.trim().split(/ *, */);
  252. //
  253. // Optionally call external protocol selection handler.
  254. //
  255. if (this.options.handleProtocols) {
  256. protocol = this.options.handleProtocols(protocol, req);
  257. } else {
  258. protocol = protocol[0];
  259. }
  260. if (protocol) {
  261. headers.push(`Sec-WebSocket-Protocol: ${protocol}`);
  262. ws.protocol = protocol;
  263. }
  264. }
  265. if (extensions[PerMessageDeflate.extensionName]) {
  266. const params = extensions[PerMessageDeflate.extensionName].params;
  267. const value = format({
  268. [PerMessageDeflate.extensionName]: [params]
  269. });
  270. headers.push(`Sec-WebSocket-Extensions: ${value}`);
  271. ws._extensions = extensions;
  272. }
  273. //
  274. // Allow external modification/inspection of handshake headers.
  275. //
  276. this.emit('headers', headers, req);
  277. socket.write(headers.concat('\r\n').join('\r\n'));
  278. socket.removeListener('error', socketOnError);
  279. ws.setSocket(socket, head, this.options.maxPayload);
  280. if (this.clients) {
  281. this.clients.add(ws);
  282. ws.on('close', () => this.clients.delete(ws));
  283. }
  284. cb(ws);
  285. }
  286. }
  287. module.exports = WebSocketServer;
  288. /**
  289. * Add event listeners on an `EventEmitter` using a map of <event, listener>
  290. * pairs.
  291. *
  292. * @param {EventEmitter} server The event emitter
  293. * @param {Object.<String, Function>} map The listeners to add
  294. * @return {Function} A function that will remove the added listeners when called
  295. * @private
  296. */
  297. function addListeners(server, map) {
  298. for (const event of Object.keys(map)) server.on(event, map[event]);
  299. return function removeListeners() {
  300. for (const event of Object.keys(map)) {
  301. server.removeListener(event, map[event]);
  302. }
  303. };
  304. }
  305. /**
  306. * Emit a `'close'` event on an `EventEmitter`.
  307. *
  308. * @param {EventEmitter} server The event emitter
  309. * @private
  310. */
  311. function emitClose(server) {
  312. server.emit('close');
  313. }
  314. /**
  315. * Handle premature socket errors.
  316. *
  317. * @private
  318. */
  319. function socketOnError() {
  320. this.destroy();
  321. }
  322. /**
  323. * Close the connection when preconditions are not fulfilled.
  324. *
  325. * @param {net.Socket} socket The socket of the upgrade request
  326. * @param {Number} code The HTTP response status code
  327. * @param {String} [message] The HTTP response body
  328. * @param {Object} [headers] Additional HTTP response headers
  329. * @private
  330. */
  331. function abortHandshake(socket, code, message, headers) {
  332. if (socket.writable) {
  333. message = message || STATUS_CODES[code];
  334. headers = {
  335. Connection: 'close',
  336. 'Content-type': 'text/html',
  337. 'Content-Length': Buffer.byteLength(message),
  338. ...headers
  339. };
  340. socket.write(
  341. `HTTP/1.1 ${code} ${STATUS_CODES[code]}\r\n` +
  342. Object.keys(headers)
  343. .map((h) => `${h}: ${headers[h]}`)
  344. .join('\r\n') +
  345. '\r\n\r\n' +
  346. message
  347. );
  348. }
  349. socket.removeListener('error', socketOnError);
  350. socket.destroy();
  351. }