websocket-server.js 10 KB

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