websocket-server.js 10 KB

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