/** * Class representing a WebSocket server. * * @extends EventEmitter
*/ class WebSocketServer extends EventEmitter { /** * Create a `WebSocketServer` instance. * * @param {Object} options Configuration options * @param {Number} [options.backlog=511] The maximum length of the queue of * pending connections * @param {Boolean} [options.clientTracking=true] Specifies whether or not to * track clients * @param {Function} [options.handleProtocols] A hook to handle protocols * @param {String} [options.host] The hostname where to bind the server * @param {Number} [options.maxPayload=104857600] The maximum allowed message * size * @param {Boolean} [options.noServer=false] Enable no server mode * @param {String} [options.path] Accept only connections matching this path * @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable * permessage-deflate * @param {Number} [options.port] The port where to bind the server * @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S * server to use * @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or * not to skip UTF-8 validation for text and close messages * @param {Function} [options.verifyClient] A hook to reject connections * @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket` * class to use. It must be the `WebSocket` class or class that extends it * @param {Function} [callback] A listener for the `listening` event
*/
constructor(options, callback) { super();
if (options.perMessageDeflate === true) options.perMessageDeflate = {}; if (options.clientTracking) { this.clients = new Set(); this._shouldEmitClose = false;
}
this.options = options; this._state = RUNNING;
}
/** * Returns the bound address, the address family name, and port of the server * as reported by the operating system if listening on an IP socket. * If the server is listening on a pipe or UNIX domain socket, the name is * returned as a string. * * @return {(Object|String|null)} The address of the server * @public
*/
address() { if (this.options.noServer) { thrownew Error('The server is operating in "noServer" mode');
}
if (!this._server) returnnull; returnthis._server.address();
}
/** * Stop the server from accepting new connections and emit the `'close'` event * when all existing connections are closed. * * @param {Function} [cb] A one-time listener for the `'close'` event * @public
*/
close(cb) { if (this._state === CLOSED) { if (cb) { this.once('close', () => {
cb(new Error('The server is not running'));
});
}
process.nextTick(emitClose, this); return;
}
if (cb) this.once('close', cb);
if (this._state === CLOSING) return; this._state = CLOSING;
if (this.options.noServer || this.options.server) { if (this._server) { this._removeListeners(); this._removeListeners = this._server = null;
}
if (this.clients) { if (!this.clients.size) {
process.nextTick(emitClose, this);
} else { this._shouldEmitClose = true;
}
} else {
process.nextTick(emitClose, this);
}
} else { const server = this._server;
// // The HTTP/S server was created internally. Close it, and rely on its // `'close'` event. //
server.close(() => {
emitClose(this);
});
}
}
/** * See if a given request should be handled by this server instance. * * @param {http.IncomingMessage} req Request object to inspect * @return {Boolean} `true` if the request is valid, else `false` * @public
*/
shouldHandle(req) { if (this.options.path) { const index = req.url.indexOf('?'); const pathname = index !== -1 ? req.url.slice(0, index) : req.url;
if (pathname !== this.options.path) returnfalse;
}
returntrue;
}
/** * Handle a HTTP Upgrade request. * * @param {http.IncomingMessage} req The request object * @param {(net.Socket|tls.Socket)} socket The network socket between the * server and client * @param {Buffer} head The first packet of the upgraded stream * @param {Function} cb Callback * @public
*/
handleUpgrade(req, socket, head, cb) {
socket.on('error', socketOnError);
const key = req.headers['sec-websocket-key']; const version = +req.headers['sec-websocket-version'];
/** * Upgrade the connection to WebSocket. * * @param {Object} extensions The accepted extensions * @param {String} key The value of the `Sec-WebSocket-Key` header * @param {Set} protocols The subprotocols * @param {http.IncomingMessage} req The request object * @param {(net.Socket|tls.Socket)} socket The network socket between the * server and client * @param {Buffer} head The first packet of the upgraded stream * @param {Function} cb Callback * @throws {Error} If called more than once with the same socket * @private
*/
completeUpgrade(extensions, key, protocols, req, socket, head, cb) { // // Destroy the socket if the client has already sent a FIN packet. // if (!socket.readable || !socket.writable) return socket.destroy();
if (socket[kWebSocket]) { thrownew Error( 'server.handleUpgrade() was called more than once with the same ' + 'socket, possibly due to a misconfiguration'
);
}
if (this._state > RUNNING) return abortHandshake(socket, 503);
if (this.clients) { this.clients.add(ws);
ws.on('close', () => { this.clients.delete(ws);
if (this._shouldEmitClose && !this.clients.size) {
process.nextTick(emitClose, this);
}
});
}
cb(ws, req);
}
}
module.exports = WebSocketServer;
/** * Add event listeners on an `EventEmitter` using a map of <event, listener> * pairs. * * @param {EventEmitter} server The event emitter * @param {Object.<String, Function>} map The listeners to add * @return {Function} A function that will remove the added listeners when * called * @private
*/ function addListeners(server, map) { for (const event of Object.keys(map)) server.on(event, map[event]);
returnfunction removeListeners() { for (const event of Object.keys(map)) {
server.removeListener(event, map[event]);
}
};
}
/** * Emit a `'close'` event on an `EventEmitter`. * * @param {EventEmitter} server The event emitter * @private
*/ function emitClose(server) {
server._state = CLOSED;
server.emit('close');
}
/** * Close the connection when preconditions are not fulfilled. * * @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request * @param {Number} code The HTTP response status code * @param {String} [message] The HTTP response body * @param {Object} [headers] Additional HTTP response headers * @private
*/ function abortHandshake(socket, code, message, headers) { // // The socket is writable unless the user destroyed or ended it before calling // `server.handleUpgrade()` or in the `verifyClient` function, which is a user // error. Handling this does not make much sense as the worst that can happen // is that some of the data written by the user might be discarded due to the // call to `socket.end()` below, which triggers an `'error'` event that in // turn causes the socket to be destroyed. //
message = message || http.STATUS_CODES[code];
headers = {
Connection: 'close', 'Content-Type': 'text/html', 'Content-Length': Buffer.byteLength(message),
...headers
};
/** * Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least * one listener for it, otherwise call `abortHandshake()`. * * @param {WebSocketServer} server The WebSocket server * @param {http.IncomingMessage} req The request object * @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request * @param {Number} code The HTTP response status code * @param {String} message The HTTP response body * @private
*/ function abortHandshakeOrEmitwsClientError(server, req, socket, code, message) { if (server.listenerCount('wsClientError')) { const err = new Error(message);
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.