Subversion Repositories qbpwcf-lib(archive)

Rev

Rev 915 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 liveuser 1
/************************************************************************
2
 *  Copyright 2010-2015 Brian McKelvey.
3
 *
4
 *  Licensed under the Apache License, Version 2.0 (the "License");
5
 *  you may not use this file except in compliance with the License.
6
 *  You may obtain a copy of the License at
7
 *
8
 *      http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 *  Unless required by applicable law or agreed to in writing, software
11
 *  distributed under the License is distributed on an "AS IS" BASIS,
12
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 *  See the License for the specific language governing permissions and
14
 *  limitations under the License.
15
 ***********************************************************************/
16
 
17
var extend = require('./utils').extend;
18
var utils = require('./utils');
19
var util = require('util');
20
var debug = require('debug')('websocket:server');
21
var EventEmitter = require('events').EventEmitter;
22
var WebSocketRequest = require('./WebSocketRequest');
23
 
24
var WebSocketServer = function WebSocketServer(config) {
25
    // Superclass Constructor
26
    EventEmitter.call(this);
27
 
28
    this._handlers = {
29
        upgrade: this.handleUpgrade.bind(this),
30
        requestAccepted: this.handleRequestAccepted.bind(this),
31
        requestResolved: this.handleRequestResolved.bind(this)
32
    };
33
    this.connections = [];
34
    this.pendingRequests = [];
35
    if (config) {
36
        this.mount(config);
37
    }
38
};
39
 
40
util.inherits(WebSocketServer, EventEmitter);
41
 
42
WebSocketServer.prototype.mount = function(config) {
43
    this.config = {
44
        // The http server instance to attach to.  Required.
45
        httpServer: null,
46
 
47
        // 64KiB max frame size.
48
        maxReceivedFrameSize: 0x10000,
49
 
50
        // 1MiB max message size, only applicable if
51
        // assembleFragments is true
52
        maxReceivedMessageSize: 0x100000,
53
 
54
        // Outgoing messages larger than fragmentationThreshold will be
55
        // split into multiple fragments.
56
        fragmentOutgoingMessages: true,
57
 
58
        // Outgoing frames are fragmented if they exceed this threshold.
59
        // Default is 16KiB
60
        fragmentationThreshold: 0x4000,
61
 
62
        // If true, the server will automatically send a ping to all
63
        // clients every 'keepaliveInterval' milliseconds.  The timer is
64
        // reset on any received data from the client.
65
        keepalive: true,
66
 
67
        // The interval to send keepalive pings to connected clients if the
68
        // connection is idle.  Any received data will reset the counter.
69
        keepaliveInterval: 20000,
70
 
71
        // If true, the server will consider any connection that has not
72
        // received any data within the amount of time specified by
73
        // 'keepaliveGracePeriod' after a keepalive ping has been sent to
74
        // be dead, and will drop the connection.
75
        // Ignored if keepalive is false.
76
        dropConnectionOnKeepaliveTimeout: true,
77
 
78
        // The amount of time to wait after sending a keepalive ping before
79
        // closing the connection if the connected peer does not respond.
80
        // Ignored if keepalive is false.
81
        keepaliveGracePeriod: 10000,
82
 
83
        // Whether to use native TCP keep-alive instead of WebSockets ping
84
        // and pong packets.  Native TCP keep-alive sends smaller packets
85
        // on the wire and so uses bandwidth more efficiently.  This may
86
        // be more important when talking to mobile devices.
87
        // If this value is set to true, then these values will be ignored:
88
        //   keepaliveGracePeriod
89
        //   dropConnectionOnKeepaliveTimeout
90
        useNativeKeepalive: false,
91
 
92
        // If true, fragmented messages will be automatically assembled
93
        // and the full message will be emitted via a 'message' event.
94
        // If false, each frame will be emitted via a 'frame' event and
95
        // the application will be responsible for aggregating multiple
96
        // fragmented frames.  Single-frame messages will emit a 'message'
97
        // event in addition to the 'frame' event.
98
        // Most users will want to leave this set to 'true'
99
        assembleFragments: true,
100
 
101
        // If this is true, websocket connections will be accepted
102
        // regardless of the path and protocol specified by the client.
103
        // The protocol accepted will be the first that was requested
104
        // by the client.  Clients from any origin will be accepted.
105
        // This should only be used in the simplest of cases.  You should
106
        // probably leave this set to 'false' and inspect the request
107
        // object to make sure it's acceptable before accepting it.
108
        autoAcceptConnections: false,
109
 
110
        // Whether or not the X-Forwarded-For header should be respected.
111
        // It's important to set this to 'true' when accepting connections
112
        // from untrusted clients, as a malicious client could spoof its
113
        // IP address by simply setting this header.  It's meant to be added
114
        // by a trusted proxy or other intermediary within your own
115
        // infrastructure.
116
        // See:  http://en.wikipedia.org/wiki/X-Forwarded-For
117
        ignoreXForwardedFor: false,
118
 
119
        // The Nagle Algorithm makes more efficient use of network resources
120
        // by introducing a small delay before sending small packets so that
121
        // multiple messages can be batched together before going onto the
122
        // wire.  This however comes at the cost of latency, so the default
123
        // is to disable it.  If you don't need low latency and are streaming
124
        // lots of small messages, you can change this to 'false'
125
        disableNagleAlgorithm: true,
126
 
127
        // The number of milliseconds to wait after sending a close frame
128
        // for an acknowledgement to come back before giving up and just
129
        // closing the socket.
130
        closeTimeout: 5000
131
    };
132
    extend(this.config, config);
133
 
134
    if (this.config.httpServer) {
135
        if (!Array.isArray(this.config.httpServer)) {
136
            this.config.httpServer = [this.config.httpServer];
137
        }
138
        var upgradeHandler = this._handlers.upgrade;
139
        this.config.httpServer.forEach(function(httpServer) {
140
            httpServer.on('upgrade', upgradeHandler);
141
        });
142
    }
143
    else {
144
        throw new Error('You must specify an httpServer on which to mount the WebSocket server.');
145
    }
146
};
147
 
148
WebSocketServer.prototype.unmount = function() {
149
    var upgradeHandler = this._handlers.upgrade;
150
    this.config.httpServer.forEach(function(httpServer) {
151
        httpServer.removeListener('upgrade', upgradeHandler);
152
    });
153
};
154
 
155
WebSocketServer.prototype.closeAllConnections = function() {
156
    this.connections.forEach(function(connection) {
157
        connection.close();
158
    });
159
    this.pendingRequests.forEach(function(request) {
160
        process.nextTick(function() {
161
          request.reject(503); // HTTP 503 Service Unavailable
162
        });
163
    });
164
};
165
 
166
WebSocketServer.prototype.broadcast = function(data) {
167
    if (Buffer.isBuffer(data)) {
168
        this.broadcastBytes(data);
169
    }
170
    else if (typeof(data.toString) === 'function') {
171
        this.broadcastUTF(data);
172
    }
173
};
174
 
175
WebSocketServer.prototype.broadcastUTF = function(utfData) {
176
    this.connections.forEach(function(connection) {
177
        connection.sendUTF(utfData);
178
    });
179
};
180
 
181
WebSocketServer.prototype.broadcastBytes = function(binaryData) {
182
    this.connections.forEach(function(connection) {
183
        connection.sendBytes(binaryData);
184
    });
185
};
186
 
187
WebSocketServer.prototype.shutDown = function() {
188
    this.unmount();
189
    this.closeAllConnections();
190
};
191
 
192
WebSocketServer.prototype.handleUpgrade = function(request, socket) {
193
    var wsRequest = new WebSocketRequest(socket, request, this.config);
194
    try {
195
        wsRequest.readHandshake();
196
    }
197
    catch(e) {
198
        wsRequest.reject(
199
            e.httpCode ? e.httpCode : 400,
200
            e.message,
201
            e.headers
202
        );
203
        debug('Invalid handshake: %s', e.message);
204
        return;
205
    }
206
 
207
    this.pendingRequests.push(wsRequest);
208
 
209
    wsRequest.once('requestAccepted', this._handlers.requestAccepted);
210
    wsRequest.once('requestResolved', this._handlers.requestResolved);
211
 
212
    if (!this.config.autoAcceptConnections && utils.eventEmitterListenerCount(this, 'request') > 0) {
213
        this.emit('request', wsRequest);
214
    }
215
    else if (this.config.autoAcceptConnections) {
216
        wsRequest.accept(wsRequest.requestedProtocols[0], wsRequest.origin);
217
    }
218
    else {
219
        wsRequest.reject(404, 'No handler is configured to accept the connection.');
220
    }
221
};
222
 
223
WebSocketServer.prototype.handleRequestAccepted = function(connection) {
224
    var self = this;
225
    connection.once('close', function(closeReason, description) {
226
        self.handleConnectionClose(connection, closeReason, description);
227
    });
228
    this.connections.push(connection);
229
    this.emit('connect', connection);
230
};
231
 
232
WebSocketServer.prototype.handleConnectionClose = function(connection, closeReason, description) {
233
    var index = this.connections.indexOf(connection);
234
    if (index !== -1) {
235
        this.connections.splice(index, 1);
236
    }
237
    this.emit('close', connection, closeReason, description);
238
};
239
 
240
WebSocketServer.prototype.handleRequestResolved = function(request) {
241
    var index = this.pendingRequests.indexOf(request);
242
    if (index !== -1) { this.pendingRequests.splice(index, 1); }
243
};
244
 
245
module.exports = WebSocketServer;