| 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 util = require('util');
|
|
|
18 |
var utils = require('./utils');
|
|
|
19 |
var EventEmitter = require('events').EventEmitter;
|
|
|
20 |
var WebSocketFrame = require('./WebSocketFrame');
|
|
|
21 |
var BufferList = require('../vendor/FastBufferList');
|
|
|
22 |
var Validation = require('./Validation').Validation;
|
|
|
23 |
|
|
|
24 |
// Connected, fully-open, ready to send and receive frames
|
|
|
25 |
const STATE_OPEN = 'open';
|
|
|
26 |
// Received a close frame from the remote peer
|
|
|
27 |
const STATE_PEER_REQUESTED_CLOSE = 'peer_requested_close';
|
|
|
28 |
// Sent close frame to remote peer. No further data can be sent.
|
|
|
29 |
const STATE_ENDING = 'ending';
|
|
|
30 |
// Connection is fully closed. No further data can be sent or received.
|
|
|
31 |
const STATE_CLOSED = 'closed';
|
|
|
32 |
|
|
|
33 |
var setImmediateImpl = ('setImmediate' in global) ?
|
|
|
34 |
global.setImmediate.bind(global) :
|
|
|
35 |
process.nextTick.bind(process);
|
|
|
36 |
|
|
|
37 |
var idCounter = 0;
|
|
|
38 |
|
|
|
39 |
function WebSocketConnection(socket, extensions, protocol, maskOutgoingPackets, config) {
|
|
|
40 |
this._debug = utils.BufferingLogger('websocket:connection', ++idCounter);
|
|
|
41 |
this._debug('constructor');
|
|
|
42 |
|
|
|
43 |
if (this._debug.enabled) {
|
|
|
44 |
instrumentSocketForDebugging(this, socket);
|
|
|
45 |
}
|
|
|
46 |
|
|
|
47 |
// Superclass Constructor
|
|
|
48 |
EventEmitter.call(this);
|
|
|
49 |
|
|
|
50 |
this._pingListenerCount = 0;
|
|
|
51 |
this.on('newListener', function(ev) {
|
|
|
52 |
if (ev === 'ping'){
|
|
|
53 |
this._pingListenerCount++;
|
|
|
54 |
}
|
|
|
55 |
}).on('removeListener', function(ev) {
|
|
|
56 |
if (ev === 'ping') {
|
|
|
57 |
this._pingListenerCount--;
|
|
|
58 |
}
|
|
|
59 |
});
|
|
|
60 |
|
|
|
61 |
this.config = config;
|
|
|
62 |
this.socket = socket;
|
|
|
63 |
this.protocol = protocol;
|
|
|
64 |
this.extensions = extensions;
|
|
|
65 |
this.remoteAddress = socket.remoteAddress;
|
|
|
66 |
this.closeReasonCode = -1;
|
|
|
67 |
this.closeDescription = null;
|
|
|
68 |
this.closeEventEmitted = false;
|
|
|
69 |
|
|
|
70 |
// We have to mask outgoing packets if we're acting as a WebSocket client.
|
|
|
71 |
this.maskOutgoingPackets = maskOutgoingPackets;
|
|
|
72 |
|
|
|
73 |
// We re-use the same buffers for the mask and frame header for all frames
|
|
|
74 |
// received on each connection to avoid a small memory allocation for each
|
|
|
75 |
// frame.
|
|
|
76 |
this.maskBytes = new Buffer(4);
|
|
|
77 |
this.frameHeader = new Buffer(10);
|
|
|
78 |
|
|
|
79 |
// the BufferList will handle the data streaming in
|
|
|
80 |
this.bufferList = new BufferList();
|
|
|
81 |
|
|
|
82 |
// Prepare for receiving first frame
|
|
|
83 |
this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
|
|
|
84 |
this.fragmentationSize = 0; // data received so far...
|
|
|
85 |
this.frameQueue = [];
|
|
|
86 |
|
|
|
87 |
// Various bits of connection state
|
|
|
88 |
this.connected = true;
|
|
|
89 |
this.state = STATE_OPEN;
|
|
|
90 |
this.waitingForCloseResponse = false;
|
|
|
91 |
// Received TCP FIN, socket's readable stream is finished.
|
|
|
92 |
this.receivedEnd = false;
|
|
|
93 |
|
|
|
94 |
this.closeTimeout = this.config.closeTimeout;
|
|
|
95 |
this.assembleFragments = this.config.assembleFragments;
|
|
|
96 |
this.maxReceivedMessageSize = this.config.maxReceivedMessageSize;
|
|
|
97 |
|
|
|
98 |
this.outputBufferFull = false;
|
|
|
99 |
this.inputPaused = false;
|
|
|
100 |
this.receivedDataHandler = this.processReceivedData.bind(this);
|
|
|
101 |
this._closeTimerHandler = this.handleCloseTimer.bind(this);
|
|
|
102 |
|
|
|
103 |
// Disable nagle algorithm?
|
|
|
104 |
this.socket.setNoDelay(this.config.disableNagleAlgorithm);
|
|
|
105 |
|
|
|
106 |
// Make sure there is no socket inactivity timeout
|
|
|
107 |
this.socket.setTimeout(0);
|
|
|
108 |
|
|
|
109 |
if (this.config.keepalive && !this.config.useNativeKeepalive) {
|
|
|
110 |
if (typeof(this.config.keepaliveInterval) !== 'number') {
|
|
|
111 |
throw new Error('keepaliveInterval must be specified and numeric ' +
|
|
|
112 |
'if keepalive is true.');
|
|
|
113 |
}
|
|
|
114 |
this._keepaliveTimerHandler = this.handleKeepaliveTimer.bind(this);
|
|
|
115 |
this.setKeepaliveTimer();
|
|
|
116 |
|
|
|
117 |
if (this.config.dropConnectionOnKeepaliveTimeout) {
|
|
|
118 |
if (typeof(this.config.keepaliveGracePeriod) !== 'number') {
|
|
|
119 |
throw new Error('keepaliveGracePeriod must be specified and ' +
|
|
|
120 |
'numeric if dropConnectionOnKeepaliveTimeout ' +
|
|
|
121 |
'is true.');
|
|
|
122 |
}
|
|
|
123 |
this._gracePeriodTimerHandler = this.handleGracePeriodTimer.bind(this);
|
|
|
124 |
}
|
|
|
125 |
}
|
|
|
126 |
else if (this.config.keepalive && this.config.useNativeKeepalive) {
|
|
|
127 |
if (!('setKeepAlive' in this.socket)) {
|
|
|
128 |
throw new Error('Unable to use native keepalive: unsupported by ' +
|
|
|
129 |
'this version of Node.');
|
|
|
130 |
}
|
|
|
131 |
this.socket.setKeepAlive(true, this.config.keepaliveInterval);
|
|
|
132 |
}
|
|
|
133 |
|
|
|
134 |
// The HTTP Client seems to subscribe to socket error events
|
|
|
135 |
// and re-dispatch them in such a way that doesn't make sense
|
|
|
136 |
// for users of our client, so we want to make sure nobody
|
|
|
137 |
// else is listening for error events on the socket besides us.
|
|
|
138 |
this.socket.removeAllListeners('error');
|
|
|
139 |
}
|
|
|
140 |
|
|
|
141 |
WebSocketConnection.CLOSE_REASON_NORMAL = 1000;
|
|
|
142 |
WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001;
|
|
|
143 |
WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002;
|
|
|
144 |
WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003;
|
|
|
145 |
WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value. Undefined meaning.
|
|
|
146 |
WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire
|
|
|
147 |
WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire
|
|
|
148 |
WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007;
|
|
|
149 |
WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008;
|
|
|
150 |
WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009;
|
|
|
151 |
WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010;
|
|
|
152 |
WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011;
|
|
|
153 |
WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015; // Not to be used on the wire
|
|
|
154 |
|
|
|
155 |
WebSocketConnection.CLOSE_DESCRIPTIONS = {
|
|
|
156 |
1000: 'Normal connection closure',
|
|
|
157 |
1001: 'Remote peer is going away',
|
|
|
158 |
1002: 'Protocol error',
|
|
|
159 |
1003: 'Unprocessable input',
|
|
|
160 |
1004: 'Reserved',
|
|
|
161 |
1005: 'Reason not provided',
|
|
|
162 |
1006: 'Abnormal closure, no further detail available',
|
|
|
163 |
1007: 'Invalid data received',
|
|
|
164 |
1008: 'Policy violation',
|
|
|
165 |
1009: 'Message too big',
|
|
|
166 |
1010: 'Extension requested by client is required',
|
|
|
167 |
1011: 'Internal Server Error',
|
|
|
168 |
1015: 'TLS Handshake Failed'
|
|
|
169 |
};
|
|
|
170 |
|
|
|
171 |
function validateCloseReason(code) {
|
|
|
172 |
if (code < 1000) {
|
|
|
173 |
// Status codes in the range 0-999 are not used
|
|
|
174 |
return false;
|
|
|
175 |
}
|
|
|
176 |
if (code >= 1000 && code <= 2999) {
|
|
|
177 |
// Codes from 1000 - 2999 are reserved for use by the protocol. Only
|
|
|
178 |
// a few codes are defined, all others are currently illegal.
|
|
|
179 |
return [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011].indexOf(code) !== -1;
|
|
|
180 |
}
|
|
|
181 |
if (code >= 3000 && code <= 3999) {
|
|
|
182 |
// Reserved for use by libraries, frameworks, and applications.
|
|
|
183 |
// Should be registered with IANA. Interpretation of these codes is
|
|
|
184 |
// undefined by the WebSocket protocol.
|
|
|
185 |
return true;
|
|
|
186 |
}
|
|
|
187 |
if (code >= 4000 && code <= 4999) {
|
|
|
188 |
// Reserved for private use. Interpretation of these codes is
|
|
|
189 |
// undefined by the WebSocket protocol.
|
|
|
190 |
return true;
|
|
|
191 |
}
|
|
|
192 |
if (code >= 5000) {
|
|
|
193 |
return false;
|
|
|
194 |
}
|
|
|
195 |
}
|
|
|
196 |
|
|
|
197 |
util.inherits(WebSocketConnection, EventEmitter);
|
|
|
198 |
|
|
|
199 |
WebSocketConnection.prototype._addSocketEventListeners = function() {
|
|
|
200 |
this.socket.on('error', this.handleSocketError.bind(this));
|
|
|
201 |
this.socket.on('end', this.handleSocketEnd.bind(this));
|
|
|
202 |
this.socket.on('close', this.handleSocketClose.bind(this));
|
|
|
203 |
this.socket.on('drain', this.handleSocketDrain.bind(this));
|
|
|
204 |
this.socket.on('pause', this.handleSocketPause.bind(this));
|
|
|
205 |
this.socket.on('resume', this.handleSocketResume.bind(this));
|
|
|
206 |
this.socket.on('data', this.handleSocketData.bind(this));
|
|
|
207 |
};
|
|
|
208 |
|
|
|
209 |
// set or reset the keepalive timer when data is received.
|
|
|
210 |
WebSocketConnection.prototype.setKeepaliveTimer = function() {
|
|
|
211 |
this._debug('setKeepaliveTimer');
|
|
|
212 |
if (!this.config.keepalive) { return; }
|
|
|
213 |
this.clearKeepaliveTimer();
|
|
|
214 |
this.clearGracePeriodTimer();
|
|
|
215 |
this._keepaliveTimeoutID = setTimeout(this._keepaliveTimerHandler, this.config.keepaliveInterval);
|
|
|
216 |
};
|
|
|
217 |
|
|
|
218 |
WebSocketConnection.prototype.clearKeepaliveTimer = function() {
|
|
|
219 |
if (this._keepaliveTimeoutID) {
|
|
|
220 |
clearTimeout(this._keepaliveTimeoutID);
|
|
|
221 |
}
|
|
|
222 |
};
|
|
|
223 |
|
|
|
224 |
// No data has been received within config.keepaliveTimeout ms.
|
|
|
225 |
WebSocketConnection.prototype.handleKeepaliveTimer = function() {
|
|
|
226 |
this._debug('handleKeepaliveTimer');
|
|
|
227 |
this._keepaliveTimeoutID = null;
|
|
|
228 |
this.ping();
|
|
|
229 |
|
|
|
230 |
// If we are configured to drop connections if the client doesn't respond
|
|
|
231 |
// then set the grace period timer.
|
|
|
232 |
if (this.config.dropConnectionOnKeepaliveTimeout) {
|
|
|
233 |
this.setGracePeriodTimer();
|
|
|
234 |
}
|
|
|
235 |
else {
|
|
|
236 |
// Otherwise reset the keepalive timer to send the next ping.
|
|
|
237 |
this.setKeepaliveTimer();
|
|
|
238 |
}
|
|
|
239 |
};
|
|
|
240 |
|
|
|
241 |
WebSocketConnection.prototype.setGracePeriodTimer = function() {
|
|
|
242 |
this._debug('setGracePeriodTimer');
|
|
|
243 |
this.clearGracePeriodTimer();
|
|
|
244 |
this._gracePeriodTimeoutID = setTimeout(this._gracePeriodTimerHandler, this.config.keepaliveGracePeriod);
|
|
|
245 |
};
|
|
|
246 |
|
|
|
247 |
WebSocketConnection.prototype.clearGracePeriodTimer = function() {
|
|
|
248 |
if (this._gracePeriodTimeoutID) {
|
|
|
249 |
clearTimeout(this._gracePeriodTimeoutID);
|
|
|
250 |
}
|
|
|
251 |
};
|
|
|
252 |
|
|
|
253 |
WebSocketConnection.prototype.handleGracePeriodTimer = function() {
|
|
|
254 |
this._debug('handleGracePeriodTimer');
|
|
|
255 |
// If this is called, the client has not responded and is assumed dead.
|
|
|
256 |
this._gracePeriodTimeoutID = null;
|
|
|
257 |
this.drop(WebSocketConnection.CLOSE_REASON_ABNORMAL, 'Peer not responding.', true);
|
|
|
258 |
};
|
|
|
259 |
|
|
|
260 |
WebSocketConnection.prototype.handleSocketData = function(data) {
|
|
|
261 |
this._debug('handleSocketData');
|
|
|
262 |
// Reset the keepalive timer when receiving data of any kind.
|
|
|
263 |
this.setKeepaliveTimer();
|
|
|
264 |
|
|
|
265 |
// Add received data to our bufferList, which efficiently holds received
|
|
|
266 |
// data chunks in a linked list of Buffer objects.
|
|
|
267 |
this.bufferList.write(data);
|
|
|
268 |
|
|
|
269 |
this.processReceivedData();
|
|
|
270 |
};
|
|
|
271 |
|
|
|
272 |
WebSocketConnection.prototype.processReceivedData = function() {
|
|
|
273 |
this._debug('processReceivedData');
|
|
|
274 |
// If we're not connected, we should ignore any data remaining on the buffer.
|
|
|
275 |
if (!this.connected) { return; }
|
|
|
276 |
|
|
|
277 |
// Receiving/parsing is expected to be halted when paused.
|
|
|
278 |
if (this.inputPaused) { return; }
|
|
|
279 |
|
|
|
280 |
var frame = this.currentFrame;
|
|
|
281 |
|
|
|
282 |
// WebSocketFrame.prototype.addData returns true if all data necessary to
|
|
|
283 |
// parse the frame was available. It returns false if we are waiting for
|
|
|
284 |
// more data to come in on the wire.
|
|
|
285 |
if (!frame.addData(this.bufferList)) { this._debug('-- insufficient data for frame'); return; }
|
|
|
286 |
|
|
|
287 |
var self = this;
|
|
|
288 |
|
|
|
289 |
// Handle possible parsing errors
|
|
|
290 |
if (frame.protocolError) {
|
|
|
291 |
// Something bad happened.. get rid of this client.
|
|
|
292 |
this._debug('-- protocol error');
|
|
|
293 |
process.nextTick(function() {
|
|
|
294 |
self.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, frame.dropReason);
|
|
|
295 |
});
|
|
|
296 |
return;
|
|
|
297 |
}
|
|
|
298 |
else if (frame.frameTooLarge) {
|
|
|
299 |
this._debug('-- frame too large');
|
|
|
300 |
process.nextTick(function() {
|
|
|
301 |
self.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, frame.dropReason);
|
|
|
302 |
});
|
|
|
303 |
return;
|
|
|
304 |
}
|
|
|
305 |
|
|
|
306 |
// For now since we don't support extensions, all RSV bits are illegal
|
|
|
307 |
if (frame.rsv1 || frame.rsv2 || frame.rsv3) {
|
|
|
308 |
this._debug('-- illegal rsv flag');
|
|
|
309 |
process.nextTick(function() {
|
|
|
310 |
self.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
|
|
|
311 |
'Unsupported usage of rsv bits without negotiated extension.');
|
|
|
312 |
});
|
|
|
313 |
return;
|
|
|
314 |
}
|
|
|
315 |
|
|
|
316 |
if (!this.assembleFragments) {
|
|
|
317 |
this._debug('-- emitting frame');
|
|
|
318 |
process.nextTick(function() { self.emit('frame', frame); });
|
|
|
319 |
}
|
|
|
320 |
|
|
|
321 |
process.nextTick(function() { self.processFrame(frame); });
|
|
|
322 |
|
|
|
323 |
this.currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
|
|
|
324 |
|
|
|
325 |
// If there's data remaining, schedule additional processing, but yield
|
|
|
326 |
// for now so that other connections have a chance to have their data
|
|
|
327 |
// processed. We use setImmediate here instead of process.nextTick to
|
|
|
328 |
// explicitly indicate that we wish for other I/O to be handled first.
|
|
|
329 |
if (this.bufferList.length > 0) {
|
|
|
330 |
setImmediateImpl(this.receivedDataHandler);
|
|
|
331 |
}
|
|
|
332 |
};
|
|
|
333 |
|
|
|
334 |
WebSocketConnection.prototype.handleSocketError = function(error) {
|
|
|
335 |
this._debug('handleSocketError: %j', error);
|
|
|
336 |
this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
|
|
|
337 |
this.closeDescription = 'Socket Error: ' + error.syscall + ' ' + error.code;
|
|
|
338 |
this.connected = false;
|
|
|
339 |
this.state = STATE_CLOSED;
|
|
|
340 |
this.fragmentationSize = 0;
|
|
|
341 |
if (utils.eventEmitterListenerCount(this, 'error') > 0) {
|
|
|
342 |
this.emit('error', error);
|
|
|
343 |
}
|
|
|
344 |
this.socket.destroy(error);
|
|
|
345 |
this._debug.printOutput();
|
|
|
346 |
};
|
|
|
347 |
|
|
|
348 |
WebSocketConnection.prototype.handleSocketEnd = function() {
|
|
|
349 |
this._debug('handleSocketEnd: received socket end. state = %s', this.state);
|
|
|
350 |
this.receivedEnd = true;
|
|
|
351 |
if (this.state === STATE_CLOSED) {
|
|
|
352 |
// When using the TLS module, sometimes the socket will emit 'end'
|
|
|
353 |
// after it emits 'close'. I don't think that's correct behavior,
|
|
|
354 |
// but we should deal with it gracefully by ignoring it.
|
|
|
355 |
this._debug(' --- Socket \'end\' after \'close\'');
|
|
|
356 |
return;
|
|
|
357 |
}
|
|
|
358 |
if (this.state !== STATE_PEER_REQUESTED_CLOSE &&
|
|
|
359 |
this.state !== STATE_ENDING) {
|
|
|
360 |
this._debug(' --- UNEXPECTED socket end.');
|
|
|
361 |
this.socket.end();
|
|
|
362 |
}
|
|
|
363 |
};
|
|
|
364 |
|
|
|
365 |
WebSocketConnection.prototype.handleSocketClose = function(hadError) {
|
|
|
366 |
this._debug('handleSocketClose: received socket close');
|
|
|
367 |
this.socketHadError = hadError;
|
|
|
368 |
this.connected = false;
|
|
|
369 |
this.state = STATE_CLOSED;
|
|
|
370 |
// If closeReasonCode is still set to -1 at this point then we must
|
|
|
371 |
// not have received a close frame!!
|
|
|
372 |
if (this.closeReasonCode === -1) {
|
|
|
373 |
this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL;
|
|
|
374 |
this.closeDescription = 'Connection dropped by remote peer.';
|
|
|
375 |
}
|
|
|
376 |
this.clearCloseTimer();
|
|
|
377 |
this.clearKeepaliveTimer();
|
|
|
378 |
this.clearGracePeriodTimer();
|
|
|
379 |
if (!this.closeEventEmitted) {
|
|
|
380 |
this.closeEventEmitted = true;
|
|
|
381 |
this._debug('-- Emitting WebSocketConnection close event');
|
|
|
382 |
this.emit('close', this.closeReasonCode, this.closeDescription);
|
|
|
383 |
}
|
|
|
384 |
};
|
|
|
385 |
|
|
|
386 |
WebSocketConnection.prototype.handleSocketDrain = function() {
|
|
|
387 |
this._debug('handleSocketDrain: socket drain event');
|
|
|
388 |
this.outputBufferFull = false;
|
|
|
389 |
this.emit('drain');
|
|
|
390 |
};
|
|
|
391 |
|
|
|
392 |
WebSocketConnection.prototype.handleSocketPause = function() {
|
|
|
393 |
this._debug('handleSocketPause: socket pause event');
|
|
|
394 |
this.inputPaused = true;
|
|
|
395 |
this.emit('pause');
|
|
|
396 |
};
|
|
|
397 |
|
|
|
398 |
WebSocketConnection.prototype.handleSocketResume = function() {
|
|
|
399 |
this._debug('handleSocketResume: socket resume event');
|
|
|
400 |
this.inputPaused = false;
|
|
|
401 |
this.emit('resume');
|
|
|
402 |
this.processReceivedData();
|
|
|
403 |
};
|
|
|
404 |
|
|
|
405 |
WebSocketConnection.prototype.pause = function() {
|
|
|
406 |
this._debug('pause: pause requested');
|
|
|
407 |
this.socket.pause();
|
|
|
408 |
};
|
|
|
409 |
|
|
|
410 |
WebSocketConnection.prototype.resume = function() {
|
|
|
411 |
this._debug('resume: resume requested');
|
|
|
412 |
this.socket.resume();
|
|
|
413 |
};
|
|
|
414 |
|
|
|
415 |
WebSocketConnection.prototype.close = function(reasonCode, description) {
|
|
|
416 |
if (this.connected) {
|
|
|
417 |
this._debug('close: Initating clean WebSocket close sequence.');
|
|
|
418 |
if ('number' !== typeof reasonCode) {
|
|
|
419 |
reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
|
|
|
420 |
}
|
|
|
421 |
if (!validateCloseReason(reasonCode)) {
|
|
|
422 |
throw new Error('Close code ' + reasonCode + ' is not valid.');
|
|
|
423 |
}
|
|
|
424 |
if ('string' !== typeof description) {
|
|
|
425 |
description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
|
|
|
426 |
}
|
|
|
427 |
this.closeReasonCode = reasonCode;
|
|
|
428 |
this.closeDescription = description;
|
|
|
429 |
this.setCloseTimer();
|
|
|
430 |
this.sendCloseFrame(this.closeReasonCode, this.closeDescription);
|
|
|
431 |
this.state = STATE_ENDING;
|
|
|
432 |
this.connected = false;
|
|
|
433 |
}
|
|
|
434 |
};
|
|
|
435 |
|
|
|
436 |
WebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) {
|
|
|
437 |
this._debug('drop');
|
|
|
438 |
if (typeof(reasonCode) !== 'number') {
|
|
|
439 |
reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
|
|
|
440 |
}
|
|
|
441 |
|
|
|
442 |
if (typeof(description) !== 'string') {
|
|
|
443 |
// If no description is provided, try to look one up based on the
|
|
|
444 |
// specified reasonCode.
|
|
|
445 |
description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode];
|
|
|
446 |
}
|
|
|
447 |
|
|
|
448 |
this._debug('Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s',
|
|
|
449 |
skipCloseFrame, reasonCode, description
|
|
|
450 |
);
|
|
|
451 |
|
|
|
452 |
this.closeReasonCode = reasonCode;
|
|
|
453 |
this.closeDescription = description;
|
|
|
454 |
this.frameQueue = [];
|
|
|
455 |
this.fragmentationSize = 0;
|
|
|
456 |
if (!skipCloseFrame) {
|
|
|
457 |
this.sendCloseFrame(reasonCode, description);
|
|
|
458 |
}
|
|
|
459 |
this.connected = false;
|
|
|
460 |
this.state = STATE_CLOSED;
|
|
|
461 |
this.clearCloseTimer();
|
|
|
462 |
this.clearKeepaliveTimer();
|
|
|
463 |
this.clearGracePeriodTimer();
|
|
|
464 |
|
|
|
465 |
if (!this.closeEventEmitted) {
|
|
|
466 |
this.closeEventEmitted = true;
|
|
|
467 |
this._debug('Emitting WebSocketConnection close event');
|
|
|
468 |
this.emit('close', this.closeReasonCode, this.closeDescription);
|
|
|
469 |
}
|
|
|
470 |
|
|
|
471 |
this._debug('Drop: destroying socket');
|
|
|
472 |
this.socket.destroy();
|
|
|
473 |
};
|
|
|
474 |
|
|
|
475 |
WebSocketConnection.prototype.setCloseTimer = function() {
|
|
|
476 |
this._debug('setCloseTimer');
|
|
|
477 |
this.clearCloseTimer();
|
|
|
478 |
this._debug('Setting close timer');
|
|
|
479 |
this.waitingForCloseResponse = true;
|
|
|
480 |
this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout);
|
|
|
481 |
};
|
|
|
482 |
|
|
|
483 |
WebSocketConnection.prototype.clearCloseTimer = function() {
|
|
|
484 |
this._debug('clearCloseTimer');
|
|
|
485 |
if (this.closeTimer) {
|
|
|
486 |
this._debug('Clearing close timer');
|
|
|
487 |
clearTimeout(this.closeTimer);
|
|
|
488 |
this.waitingForCloseResponse = false;
|
|
|
489 |
this.closeTimer = null;
|
|
|
490 |
}
|
|
|
491 |
};
|
|
|
492 |
|
|
|
493 |
WebSocketConnection.prototype.handleCloseTimer = function() {
|
|
|
494 |
this._debug('handleCloseTimer');
|
|
|
495 |
this.closeTimer = null;
|
|
|
496 |
if (this.waitingForCloseResponse) {
|
|
|
497 |
this._debug('Close response not received from client. Forcing socket end.');
|
|
|
498 |
this.waitingForCloseResponse = false;
|
|
|
499 |
this.state = STATE_CLOSED;
|
|
|
500 |
this.socket.end();
|
|
|
501 |
}
|
|
|
502 |
};
|
|
|
503 |
|
|
|
504 |
WebSocketConnection.prototype.processFrame = function(frame) {
|
|
|
505 |
this._debug('processFrame');
|
|
|
506 |
this._debug(' -- frame: %s', frame);
|
|
|
507 |
|
|
|
508 |
// Any non-control opcode besides 0x00 (continuation) received in the
|
|
|
509 |
// middle of a fragmented message is illegal.
|
|
|
510 |
if (this.frameQueue.length !== 0 && (frame.opcode > 0x00 && frame.opcode < 0x08)) {
|
|
|
511 |
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
|
|
|
512 |
'Illegal frame opcode 0x' + frame.opcode.toString(16) + ' ' +
|
|
|
513 |
'received in middle of fragmented message.');
|
|
|
514 |
return;
|
|
|
515 |
}
|
|
|
516 |
|
|
|
517 |
switch(frame.opcode) {
|
|
|
518 |
case 0x02: // WebSocketFrame.BINARY_FRAME
|
|
|
519 |
this._debug('-- Binary Frame');
|
|
|
520 |
if (this.assembleFragments) {
|
|
|
521 |
if (frame.fin) {
|
|
|
522 |
// Complete single-frame message received
|
|
|
523 |
this._debug('---- Emitting \'message\' event');
|
|
|
524 |
this.emit('message', {
|
|
|
525 |
type: 'binary',
|
|
|
526 |
binaryData: frame.binaryPayload
|
|
|
527 |
});
|
|
|
528 |
}
|
|
|
529 |
else {
|
|
|
530 |
// beginning of a fragmented message
|
|
|
531 |
this.frameQueue.push(frame);
|
|
|
532 |
this.fragmentationSize = frame.length;
|
|
|
533 |
}
|
|
|
534 |
}
|
|
|
535 |
break;
|
|
|
536 |
case 0x01: // WebSocketFrame.TEXT_FRAME
|
|
|
537 |
this._debug('-- Text Frame');
|
|
|
538 |
if (this.assembleFragments) {
|
|
|
539 |
if (frame.fin) {
|
|
|
540 |
if (!Validation.isValidUTF8(frame.binaryPayload)) {
|
|
|
541 |
this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
|
|
|
542 |
'Invalid UTF-8 Data Received');
|
|
|
543 |
return;
|
|
|
544 |
}
|
|
|
545 |
// Complete single-frame message received
|
|
|
546 |
this._debug('---- Emitting \'message\' event');
|
|
|
547 |
this.emit('message', {
|
|
|
548 |
type: 'utf8',
|
|
|
549 |
utf8Data: frame.binaryPayload.toString('utf8')
|
|
|
550 |
});
|
|
|
551 |
}
|
|
|
552 |
else {
|
|
|
553 |
// beginning of a fragmented message
|
|
|
554 |
this.frameQueue.push(frame);
|
|
|
555 |
this.fragmentationSize = frame.length;
|
|
|
556 |
}
|
|
|
557 |
}
|
|
|
558 |
break;
|
|
|
559 |
case 0x00: // WebSocketFrame.CONTINUATION
|
|
|
560 |
this._debug('-- Continuation Frame');
|
|
|
561 |
if (this.assembleFragments) {
|
|
|
562 |
if (this.frameQueue.length === 0) {
|
|
|
563 |
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
|
|
|
564 |
'Unexpected Continuation Frame');
|
|
|
565 |
return;
|
|
|
566 |
}
|
|
|
567 |
|
|
|
568 |
this.fragmentationSize += frame.length;
|
|
|
569 |
|
|
|
570 |
if (this.fragmentationSize > this.maxReceivedMessageSize) {
|
|
|
571 |
this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG,
|
|
|
572 |
'Maximum message size exceeded.');
|
|
|
573 |
return;
|
|
|
574 |
}
|
|
|
575 |
|
|
|
576 |
this.frameQueue.push(frame);
|
|
|
577 |
|
|
|
578 |
if (frame.fin) {
|
|
|
579 |
// end of fragmented message, so we process the whole
|
|
|
580 |
// message now. We also have to decode the utf-8 data
|
|
|
581 |
// for text frames after combining all the fragments.
|
|
|
582 |
var bytesCopied = 0;
|
|
|
583 |
var binaryPayload = new Buffer(this.fragmentationSize);
|
|
|
584 |
var opcode = this.frameQueue[0].opcode;
|
|
|
585 |
this.frameQueue.forEach(function (currentFrame) {
|
|
|
586 |
currentFrame.binaryPayload.copy(binaryPayload, bytesCopied);
|
|
|
587 |
bytesCopied += currentFrame.binaryPayload.length;
|
|
|
588 |
});
|
|
|
589 |
this.frameQueue = [];
|
|
|
590 |
this.fragmentationSize = 0;
|
|
|
591 |
|
|
|
592 |
switch (opcode) {
|
|
|
593 |
case 0x02: // WebSocketOpcode.BINARY_FRAME
|
|
|
594 |
this.emit('message', {
|
|
|
595 |
type: 'binary',
|
|
|
596 |
binaryData: binaryPayload
|
|
|
597 |
});
|
|
|
598 |
break;
|
|
|
599 |
case 0x01: // WebSocketOpcode.TEXT_FRAME
|
|
|
600 |
if (!Validation.isValidUTF8(binaryPayload)) {
|
|
|
601 |
this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
|
|
|
602 |
'Invalid UTF-8 Data Received');
|
|
|
603 |
return;
|
|
|
604 |
}
|
|
|
605 |
this.emit('message', {
|
|
|
606 |
type: 'utf8',
|
|
|
607 |
utf8Data: binaryPayload.toString('utf8')
|
|
|
608 |
});
|
|
|
609 |
break;
|
|
|
610 |
default:
|
|
|
611 |
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
|
|
|
612 |
'Unexpected first opcode in fragmentation sequence: 0x' + opcode.toString(16));
|
|
|
613 |
return;
|
|
|
614 |
}
|
|
|
615 |
}
|
|
|
616 |
}
|
|
|
617 |
break;
|
|
|
618 |
case 0x09: // WebSocketFrame.PING
|
|
|
619 |
this._debug('-- Ping Frame');
|
|
|
620 |
|
|
|
621 |
if (this._pingListenerCount > 0) {
|
|
|
622 |
// logic to emit the ping frame: this is only done when a listener is known to exist
|
|
|
623 |
// Expose a function allowing the user to override the default ping() behavior
|
|
|
624 |
var cancelled = false;
|
|
|
625 |
var cancel = function() {
|
|
|
626 |
cancelled = true;
|
|
|
627 |
};
|
|
|
628 |
this.emit('ping', cancel, frame.binaryPayload);
|
|
|
629 |
|
|
|
630 |
// Only send a pong if the client did not indicate that he would like to cancel
|
|
|
631 |
if (!cancelled) {
|
|
|
632 |
this.pong(frame.binaryPayload);
|
|
|
633 |
}
|
|
|
634 |
}
|
|
|
635 |
else {
|
|
|
636 |
this.pong(frame.binaryPayload);
|
|
|
637 |
}
|
|
|
638 |
|
|
|
639 |
break;
|
|
|
640 |
case 0x0A: // WebSocketFrame.PONG
|
|
|
641 |
this._debug('-- Pong Frame');
|
|
|
642 |
this.emit('pong', frame.binaryPayload);
|
|
|
643 |
break;
|
|
|
644 |
case 0x08: // WebSocketFrame.CONNECTION_CLOSE
|
|
|
645 |
this._debug('-- Close Frame');
|
|
|
646 |
if (this.waitingForCloseResponse) {
|
|
|
647 |
// Got response to our request to close the connection.
|
|
|
648 |
// Close is complete, so we just hang up.
|
|
|
649 |
this._debug('---- Got close response from peer. Completing closing handshake.');
|
|
|
650 |
this.clearCloseTimer();
|
|
|
651 |
this.waitingForCloseResponse = false;
|
|
|
652 |
this.state = STATE_CLOSED;
|
|
|
653 |
this.socket.end();
|
|
|
654 |
return;
|
|
|
655 |
}
|
|
|
656 |
|
|
|
657 |
this._debug('---- Closing handshake initiated by peer.');
|
|
|
658 |
// Got request from other party to close connection.
|
|
|
659 |
// Send back acknowledgement and then hang up.
|
|
|
660 |
this.state = STATE_PEER_REQUESTED_CLOSE;
|
|
|
661 |
var respondCloseReasonCode;
|
|
|
662 |
|
|
|
663 |
// Make sure the close reason provided is legal according to
|
|
|
664 |
// the protocol spec. Providing no close status is legal.
|
|
|
665 |
// WebSocketFrame sets closeStatus to -1 by default, so if it
|
|
|
666 |
// is still -1, then no status was provided.
|
|
|
667 |
if (frame.invalidCloseFrameLength) {
|
|
|
668 |
this.closeReasonCode = 1005; // 1005 = No reason provided.
|
|
|
669 |
respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
|
|
|
670 |
}
|
|
|
671 |
else if (frame.closeStatus === -1 || validateCloseReason(frame.closeStatus)) {
|
|
|
672 |
this.closeReasonCode = frame.closeStatus;
|
|
|
673 |
respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
|
|
|
674 |
}
|
|
|
675 |
else {
|
|
|
676 |
this.closeReasonCode = frame.closeStatus;
|
|
|
677 |
respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR;
|
|
|
678 |
}
|
|
|
679 |
|
|
|
680 |
// If there is a textual description in the close frame, extract it.
|
|
|
681 |
if (frame.binaryPayload.length > 1) {
|
|
|
682 |
if (!Validation.isValidUTF8(frame.binaryPayload)) {
|
|
|
683 |
this.drop(WebSocketConnection.CLOSE_REASON_INVALID_DATA,
|
|
|
684 |
'Invalid UTF-8 Data Received');
|
|
|
685 |
return;
|
|
|
686 |
}
|
|
|
687 |
this.closeDescription = frame.binaryPayload.toString('utf8');
|
|
|
688 |
}
|
|
|
689 |
else {
|
|
|
690 |
this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode];
|
|
|
691 |
}
|
|
|
692 |
this._debug(
|
|
|
693 |
'------ Remote peer %s - code: %d - %s - close frame payload length: %d',
|
|
|
694 |
this.remoteAddress, this.closeReasonCode,
|
|
|
695 |
this.closeDescription, frame.length
|
|
|
696 |
);
|
|
|
697 |
this._debug('------ responding to remote peer\'s close request.');
|
|
|
698 |
this.sendCloseFrame(respondCloseReasonCode, null);
|
|
|
699 |
this.connected = false;
|
|
|
700 |
break;
|
|
|
701 |
default:
|
|
|
702 |
this._debug('-- Unrecognized Opcode %d', frame.opcode);
|
|
|
703 |
this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR,
|
|
|
704 |
'Unrecognized Opcode: 0x' + frame.opcode.toString(16));
|
|
|
705 |
break;
|
|
|
706 |
}
|
|
|
707 |
};
|
|
|
708 |
|
|
|
709 |
WebSocketConnection.prototype.send = function(data, cb) {
|
|
|
710 |
this._debug('send');
|
|
|
711 |
if (Buffer.isBuffer(data)) {
|
|
|
712 |
this.sendBytes(data, cb);
|
|
|
713 |
}
|
|
|
714 |
else if (typeof(data['toString']) === 'function') {
|
|
|
715 |
this.sendUTF(data, cb);
|
|
|
716 |
}
|
|
|
717 |
else {
|
|
|
718 |
throw new Error('Data provided must either be a Node Buffer or implement toString()');
|
|
|
719 |
}
|
|
|
720 |
};
|
|
|
721 |
|
|
|
722 |
WebSocketConnection.prototype.sendUTF = function(data, cb) {
|
|
|
723 |
data = new Buffer(data.toString(), 'utf8');
|
|
|
724 |
this._debug('sendUTF: %d bytes', data.length);
|
|
|
725 |
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
|
|
|
726 |
frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME
|
|
|
727 |
frame.binaryPayload = data;
|
|
|
728 |
this.fragmentAndSend(frame, cb);
|
|
|
729 |
};
|
|
|
730 |
|
|
|
731 |
WebSocketConnection.prototype.sendBytes = function(data, cb) {
|
|
|
732 |
this._debug('sendBytes');
|
|
|
733 |
if (!Buffer.isBuffer(data)) {
|
|
|
734 |
throw new Error('You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()');
|
|
|
735 |
}
|
|
|
736 |
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
|
|
|
737 |
frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME
|
|
|
738 |
frame.binaryPayload = data;
|
|
|
739 |
this.fragmentAndSend(frame, cb);
|
|
|
740 |
};
|
|
|
741 |
|
|
|
742 |
WebSocketConnection.prototype.ping = function(data) {
|
|
|
743 |
this._debug('ping');
|
|
|
744 |
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
|
|
|
745 |
frame.opcode = 0x09; // WebSocketOpcode.PING
|
|
|
746 |
frame.fin = true;
|
|
|
747 |
if (data) {
|
|
|
748 |
if (!Buffer.isBuffer(data)) {
|
|
|
749 |
data = new Buffer(data.toString(), 'utf8');
|
|
|
750 |
}
|
|
|
751 |
if (data.length > 125) {
|
|
|
752 |
this._debug('WebSocket: Data for ping is longer than 125 bytes. Truncating.');
|
|
|
753 |
data = data.slice(0,124);
|
|
|
754 |
}
|
|
|
755 |
frame.binaryPayload = data;
|
|
|
756 |
}
|
|
|
757 |
this.sendFrame(frame);
|
|
|
758 |
};
|
|
|
759 |
|
|
|
760 |
// Pong frames have to echo back the contents of the data portion of the
|
|
|
761 |
// ping frame exactly, byte for byte.
|
|
|
762 |
WebSocketConnection.prototype.pong = function(binaryPayload) {
|
|
|
763 |
this._debug('pong');
|
|
|
764 |
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
|
|
|
765 |
frame.opcode = 0x0A; // WebSocketOpcode.PONG
|
|
|
766 |
if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) {
|
|
|
767 |
this._debug('WebSocket: Data for pong is longer than 125 bytes. Truncating.');
|
|
|
768 |
binaryPayload = binaryPayload.slice(0,124);
|
|
|
769 |
}
|
|
|
770 |
frame.binaryPayload = binaryPayload;
|
|
|
771 |
frame.fin = true;
|
|
|
772 |
this.sendFrame(frame);
|
|
|
773 |
};
|
|
|
774 |
|
|
|
775 |
WebSocketConnection.prototype.fragmentAndSend = function(frame, cb) {
|
|
|
776 |
this._debug('fragmentAndSend');
|
|
|
777 |
if (frame.opcode > 0x07) {
|
|
|
778 |
throw new Error('You cannot fragment control frames.');
|
|
|
779 |
}
|
|
|
780 |
|
|
|
781 |
var threshold = this.config.fragmentationThreshold;
|
|
|
782 |
var length = frame.binaryPayload.length;
|
|
|
783 |
|
|
|
784 |
// Send immediately if fragmentation is disabled or the message is not
|
|
|
785 |
// larger than the fragmentation threshold.
|
|
|
786 |
if (!this.config.fragmentOutgoingMessages || (frame.binaryPayload && length <= threshold)) {
|
|
|
787 |
frame.fin = true;
|
|
|
788 |
this.sendFrame(frame, cb);
|
|
|
789 |
return;
|
|
|
790 |
}
|
|
|
791 |
|
|
|
792 |
var numFragments = Math.ceil(length / threshold);
|
|
|
793 |
var sentFragments = 0;
|
|
|
794 |
var sentCallback = function fragmentSentCallback(err) {
|
|
|
795 |
if (err) {
|
|
|
796 |
if (typeof cb === 'function') {
|
|
|
797 |
// pass only the first error
|
|
|
798 |
cb(err);
|
|
|
799 |
cb = null;
|
|
|
800 |
}
|
|
|
801 |
return;
|
|
|
802 |
}
|
|
|
803 |
++sentFragments;
|
|
|
804 |
if ((sentFragments === numFragments) && (typeof cb === 'function')) {
|
|
|
805 |
cb();
|
|
|
806 |
}
|
|
|
807 |
};
|
|
|
808 |
for (var i=1; i <= numFragments; i++) {
|
|
|
809 |
var currentFrame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
|
|
|
810 |
|
|
|
811 |
// continuation opcode except for first frame.
|
|
|
812 |
currentFrame.opcode = (i === 1) ? frame.opcode : 0x00;
|
|
|
813 |
|
|
|
814 |
// fin set on last frame only
|
|
|
815 |
currentFrame.fin = (i === numFragments);
|
|
|
816 |
|
|
|
817 |
// length is likely to be shorter on the last fragment
|
|
|
818 |
var currentLength = (i === numFragments) ? length - (threshold * (i-1)) : threshold;
|
|
|
819 |
var sliceStart = threshold * (i-1);
|
|
|
820 |
|
|
|
821 |
// Slice the right portion of the original payload
|
|
|
822 |
currentFrame.binaryPayload = frame.binaryPayload.slice(sliceStart, sliceStart + currentLength);
|
|
|
823 |
|
|
|
824 |
this.sendFrame(currentFrame, sentCallback);
|
|
|
825 |
}
|
|
|
826 |
};
|
|
|
827 |
|
|
|
828 |
WebSocketConnection.prototype.sendCloseFrame = function(reasonCode, description, cb) {
|
|
|
829 |
if (typeof(reasonCode) !== 'number') {
|
|
|
830 |
reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL;
|
|
|
831 |
}
|
|
|
832 |
|
|
|
833 |
this._debug('sendCloseFrame state: %s, reasonCode: %d, description: %s', this.state, reasonCode, description);
|
|
|
834 |
|
|
|
835 |
if (this.state !== STATE_OPEN && this.state !== STATE_PEER_REQUESTED_CLOSE) { return; }
|
|
|
836 |
|
|
|
837 |
var frame = new WebSocketFrame(this.maskBytes, this.frameHeader, this.config);
|
|
|
838 |
frame.fin = true;
|
|
|
839 |
frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE
|
|
|
840 |
frame.closeStatus = reasonCode;
|
|
|
841 |
if (typeof(description) === 'string') {
|
|
|
842 |
frame.binaryPayload = new Buffer(description, 'utf8');
|
|
|
843 |
}
|
|
|
844 |
|
|
|
845 |
this.sendFrame(frame, cb);
|
|
|
846 |
this.socket.end();
|
|
|
847 |
};
|
|
|
848 |
|
|
|
849 |
WebSocketConnection.prototype.sendFrame = function(frame, cb) {
|
|
|
850 |
this._debug('sendFrame');
|
|
|
851 |
frame.mask = this.maskOutgoingPackets;
|
|
|
852 |
var flushed = this.socket.write(frame.toBuffer(), cb);
|
|
|
853 |
this.outputBufferFull = !flushed;
|
|
|
854 |
return flushed;
|
|
|
855 |
};
|
|
|
856 |
|
|
|
857 |
module.exports = WebSocketConnection;
|
|
|
858 |
|
|
|
859 |
|
|
|
860 |
|
|
|
861 |
function instrumentSocketForDebugging(connection, socket) {
|
|
|
862 |
/* jshint loopfunc: true */
|
|
|
863 |
if (!connection._debug.enabled) { return; }
|
|
|
864 |
|
|
|
865 |
var originalSocketEmit = socket.emit;
|
|
|
866 |
socket.emit = function(event) {
|
|
|
867 |
connection._debug('||| Socket Event \'%s\'', event);
|
|
|
868 |
originalSocketEmit.apply(this, arguments);
|
|
|
869 |
};
|
|
|
870 |
|
|
|
871 |
for (var key in socket) {
|
|
|
872 |
if ('function' !== typeof(socket[key])) { continue; }
|
|
|
873 |
if (['emit'].indexOf(key) !== -1) { continue; }
|
|
|
874 |
(function(key) {
|
|
|
875 |
var original = socket[key];
|
|
|
876 |
if (key === 'on') {
|
|
|
877 |
socket[key] = function proxyMethod__EventEmitter__On() {
|
|
|
878 |
connection._debug('||| Socket method called: %s (%s)', key, arguments[0]);
|
|
|
879 |
return original.apply(this, arguments);
|
|
|
880 |
};
|
|
|
881 |
return;
|
|
|
882 |
}
|
|
|
883 |
socket[key] = function proxyMethod() {
|
|
|
884 |
connection._debug('||| Socket method called: %s', key);
|
|
|
885 |
return original.apply(this, arguments);
|
|
|
886 |
};
|
|
|
887 |
})(key);
|
|
|
888 |
}
|
|
|
889 |
}
|