Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

websocket-server.js 12KB

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