websocket-server.js 11 KB

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