123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- /************************************************************************
- * Copyright 2010-2015 Brian McKelvey.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- ***********************************************************************/
- var extend = require('./utils').extend;
- var utils = require('./utils');
- var util = require('util');
- var debug = require('debug')('websocket:server');
- var EventEmitter = require('events').EventEmitter;
- var WebSocketRequest = require('./WebSocketRequest');
- var WebSocketServer = function WebSocketServer(config) {
- // Superclass Constructor
- EventEmitter.call(this);
- this._handlers = {
- upgrade: this.handleUpgrade.bind(this),
- requestAccepted: this.handleRequestAccepted.bind(this),
- requestResolved: this.handleRequestResolved.bind(this)
- };
- this.connections = [];
- this.pendingRequests = [];
- if (config) {
- this.mount(config);
- }
- };
- util.inherits(WebSocketServer, EventEmitter);
- WebSocketServer.prototype.mount = function(config) {
- this.config = {
- // The http server instance to attach to. Required.
- httpServer: null,
- // 64KiB max frame size.
- maxReceivedFrameSize: 0x10000,
- // 1MiB max message size, only applicable if
- // assembleFragments is true
- maxReceivedMessageSize: 0x100000,
- // Outgoing messages larger than fragmentationThreshold will be
- // split into multiple fragments.
- fragmentOutgoingMessages: true,
- // Outgoing frames are fragmented if they exceed this threshold.
- // Default is 16KiB
- fragmentationThreshold: 0x4000,
- // If true, the server will automatically send a ping to all
- // clients every 'keepaliveInterval' milliseconds. The timer is
- // reset on any received data from the client.
- keepalive: true,
- // The interval to send keepalive pings to connected clients if the
- // connection is idle. Any received data will reset the counter.
- keepaliveInterval: 20000,
- // If true, the server will consider any connection that has not
- // received any data within the amount of time specified by
- // 'keepaliveGracePeriod' after a keepalive ping has been sent to
- // be dead, and will drop the connection.
- // Ignored if keepalive is false.
- dropConnectionOnKeepaliveTimeout: true,
- // The amount of time to wait after sending a keepalive ping before
- // closing the connection if the connected peer does not respond.
- // Ignored if keepalive is false.
- keepaliveGracePeriod: 10000,
- // Whether to use native TCP keep-alive instead of WebSockets ping
- // and pong packets. Native TCP keep-alive sends smaller packets
- // on the wire and so uses bandwidth more efficiently. This may
- // be more important when talking to mobile devices.
- // If this value is set to true, then these values will be ignored:
- // keepaliveGracePeriod
- // dropConnectionOnKeepaliveTimeout
- useNativeKeepalive: false,
- // If true, fragmented messages will be automatically assembled
- // and the full message will be emitted via a 'message' event.
- // If false, each frame will be emitted via a 'frame' event and
- // the application will be responsible for aggregating multiple
- // fragmented frames. Single-frame messages will emit a 'message'
- // event in addition to the 'frame' event.
- // Most users will want to leave this set to 'true'
- assembleFragments: true,
- // If this is true, websocket connections will be accepted
- // regardless of the path and protocol specified by the client.
- // The protocol accepted will be the first that was requested
- // by the client. Clients from any origin will be accepted.
- // This should only be used in the simplest of cases. You should
- // probably leave this set to 'false' and inspect the request
- // object to make sure it's acceptable before accepting it.
- autoAcceptConnections: false,
- // Whether or not the X-Forwarded-For header should be respected.
- // It's important to set this to 'true' when accepting connections
- // from untrusted clients, as a malicious client could spoof its
- // IP address by simply setting this header. It's meant to be added
- // by a trusted proxy or other intermediary within your own
- // infrastructure.
- // See: http://en.wikipedia.org/wiki/X-Forwarded-For
- ignoreXForwardedFor: false,
- // The Nagle Algorithm makes more efficient use of network resources
- // by introducing a small delay before sending small packets so that
- // multiple messages can be batched together before going onto the
- // wire. This however comes at the cost of latency, so the default
- // is to disable it. If you don't need low latency and are streaming
- // lots of small messages, you can change this to 'false'
- disableNagleAlgorithm: true,
- // The number of milliseconds to wait after sending a close frame
- // for an acknowledgement to come back before giving up and just
- // closing the socket.
- closeTimeout: 5000
- };
- extend(this.config, config);
- if (this.config.httpServer) {
- if (!Array.isArray(this.config.httpServer)) {
- this.config.httpServer = [this.config.httpServer];
- }
- var upgradeHandler = this._handlers.upgrade;
- this.config.httpServer.forEach(function(httpServer) {
- httpServer.on('upgrade', upgradeHandler);
- });
- }
- else {
- throw new Error('You must specify an httpServer on which to mount the WebSocket server.');
- }
- };
- WebSocketServer.prototype.unmount = function() {
- var upgradeHandler = this._handlers.upgrade;
- this.config.httpServer.forEach(function(httpServer) {
- httpServer.removeListener('upgrade', upgradeHandler);
- });
- };
- WebSocketServer.prototype.closeAllConnections = function() {
- this.connections.forEach(function(connection) {
- connection.close();
- });
- this.pendingRequests.forEach(function(request) {
- process.nextTick(function() {
- request.reject(503); // HTTP 503 Service Unavailable
- });
- });
- };
- WebSocketServer.prototype.broadcast = function(data) {
- if (Buffer.isBuffer(data)) {
- this.broadcastBytes(data);
- }
- else if (typeof(data.toString) === 'function') {
- this.broadcastUTF(data);
- }
- };
- WebSocketServer.prototype.broadcastUTF = function(utfData) {
- this.connections.forEach(function(connection) {
- connection.sendUTF(utfData);
- });
- };
- WebSocketServer.prototype.broadcastBytes = function(binaryData) {
- this.connections.forEach(function(connection) {
- connection.sendBytes(binaryData);
- });
- };
- WebSocketServer.prototype.shutDown = function() {
- this.unmount();
- this.closeAllConnections();
- };
- WebSocketServer.prototype.handleUpgrade = function(request, socket) {
- var wsRequest = new WebSocketRequest(socket, request, this.config);
- try {
- wsRequest.readHandshake();
- }
- catch(e) {
- wsRequest.reject(
- e.httpCode ? e.httpCode : 400,
- e.message,
- e.headers
- );
- debug('Invalid handshake: %s', e.message);
- return;
- }
-
- this.pendingRequests.push(wsRequest);
- wsRequest.once('requestAccepted', this._handlers.requestAccepted);
- wsRequest.once('requestResolved', this._handlers.requestResolved);
- if (!this.config.autoAcceptConnections && utils.eventEmitterListenerCount(this, 'request') > 0) {
- this.emit('request', wsRequest);
- }
- else if (this.config.autoAcceptConnections) {
- wsRequest.accept(wsRequest.requestedProtocols[0], wsRequest.origin);
- }
- else {
- wsRequest.reject(404, 'No handler is configured to accept the connection.');
- }
- };
- WebSocketServer.prototype.handleRequestAccepted = function(connection) {
- var self = this;
- connection.once('close', function(closeReason, description) {
- self.handleConnectionClose(connection, closeReason, description);
- });
- this.connections.push(connection);
- this.emit('connect', connection);
- };
- WebSocketServer.prototype.handleConnectionClose = function(connection, closeReason, description) {
- var index = this.connections.indexOf(connection);
- if (index !== -1) {
- this.connections.splice(index, 1);
- }
- this.emit('close', connection, closeReason, description);
- };
- WebSocketServer.prototype.handleRequestResolved = function(request) {
- var index = this.pendingRequests.indexOf(request);
- if (index !== -1) { this.pendingRequests.splice(index, 1); }
- };
- module.exports = WebSocketServer;
|