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 17var util = require('util'); 18var utils = require('./utils'); 19var unit_lib = require('./build/Release/unit-http'); 20var EventEmitter = require('events').EventEmitter; 21var WebSocketFrame = require('./websocket_frame'); 22var bufferAllocUnsafe = utils.bufferAllocUnsafe; 23var bufferFromString = utils.bufferFromString; 24 25// Connected, fully-open, ready to send and receive frames 26const STATE_OPEN = 'open'; 27// Received a close frame from the remote peer 28const STATE_PEER_REQUESTED_CLOSE = 'peer_requested_close'; 29// Sent close frame to remote peer. No further data can be sent. 30const STATE_ENDING = 'ending'; 31// Connection is fully closed. No further data can be sent or received. 32const STATE_CLOSED = 'closed'; 33 34var idCounter = 0; 35 36function WebSocketConnection(socket, extensions, protocol, maskOutgoingPackets, config) { 37 this._debug = utils.BufferingLogger('websocket:connection', ++idCounter); 38 this._debug('constructor'); 39 40 if (this._debug.enabled) { 41 instrumentSocketForDebugging(this, socket); 42 } 43 44 // Superclass Constructor 45 EventEmitter.call(this); 46 47 this._pingListenerCount = 0; 48 this.on('newListener', function(ev) { 49 if (ev === 'ping'){ 50 this._pingListenerCount++; 51 } 52 }).on('removeListener', function(ev) { 53 if (ev === 'ping') { 54 this._pingListenerCount--; 55 } 56 }); 57 58 this.config = config; 59 this.socket = socket; 60 this.protocol = protocol; 61 this.extensions = extensions; 62 this.remoteAddress = socket.remoteAddress; 63 this.closeReasonCode = -1; 64 this.closeDescription = null; 65 this.closeEventEmitted = false; 66 67 // We have to mask outgoing packets if we're acting as a WebSocket client. 68 this.maskOutgoingPackets = maskOutgoingPackets; 69 70 this.fragmentationSize = 0; // data received so far... 71 this.frameQueue = []; 72 73 // Various bits of connection state 74 this.connected = true; 75 this.state = STATE_OPEN; 76 this.waitingForCloseResponse = false; 77 // Received TCP FIN, socket's readable stream is finished. 78 this.receivedEnd = false; 79 80 this.closeTimeout = this.config.closeTimeout; 81 this.assembleFragments = this.config.assembleFragments; 82 this.maxReceivedMessageSize = this.config.maxReceivedMessageSize; 83 84 this.outputBufferFull = false; 85 this.inputPaused = false; 86 this._closeTimerHandler = this.handleCloseTimer.bind(this); 87 88 // Disable nagle algorithm? 89 this.socket.setNoDelay(this.config.disableNagleAlgorithm); 90 91 // Make sure there is no socket inactivity timeout 92 this.socket.setTimeout(0); 93 94 // The HTTP Client seems to subscribe to socket error events 95 // and re-dispatch them in such a way that doesn't make sense 96 // for users of our client, so we want to make sure nobody 97 // else is listening for error events on the socket besides us. 98 this.socket.removeAllListeners('error'); 99 100 this._set_sock(this.socket); 101} 102 103WebSocketConnection.prototype._set_sock = unit_lib.websocket_set_sock; 104WebSocketConnection.prototype._end = unit_lib.response_end; 105 106WebSocketConnection.CLOSE_REASON_NORMAL = 1000; 107WebSocketConnection.CLOSE_REASON_GOING_AWAY = 1001; 108WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR = 1002; 109WebSocketConnection.CLOSE_REASON_UNPROCESSABLE_INPUT = 1003; 110WebSocketConnection.CLOSE_REASON_RESERVED = 1004; // Reserved value. Undefined meaning. 111WebSocketConnection.CLOSE_REASON_NOT_PROVIDED = 1005; // Not to be used on the wire 112WebSocketConnection.CLOSE_REASON_ABNORMAL = 1006; // Not to be used on the wire 113WebSocketConnection.CLOSE_REASON_INVALID_DATA = 1007; 114WebSocketConnection.CLOSE_REASON_POLICY_VIOLATION = 1008; 115WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG = 1009; 116WebSocketConnection.CLOSE_REASON_EXTENSION_REQUIRED = 1010; 117WebSocketConnection.CLOSE_REASON_INTERNAL_SERVER_ERROR = 1011; 118WebSocketConnection.CLOSE_REASON_TLS_HANDSHAKE_FAILED = 1015; // Not to be used on the wire 119 120WebSocketConnection.CLOSE_DESCRIPTIONS = { 121 1000: 'Normal connection closure', 122 1001: 'Remote peer is going away', 123 1002: 'Protocol error', 124 1003: 'Unprocessable input', 125 1004: 'Reserved', 126 1005: 'Reason not provided', 127 1006: 'Abnormal closure, no further detail available', 128 1007: 'Invalid data received', 129 1008: 'Policy violation', 130 1009: 'Message too big', 131 1010: 'Extension requested by client is required', 132 1011: 'Internal Server Error', 133 1015: 'TLS Handshake Failed' 134}; 135 136function validateCloseReason(code) { 137 if (code < 1000) { 138 // Status codes in the range 0-999 are not used 139 return false; 140 } 141 if (code >= 1000 && code <= 2999) { 142 // Codes from 1000 - 2999 are reserved for use by the protocol. Only 143 // a few codes are defined, all others are currently illegal. 144 return [1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014].indexOf(code) !== -1; 145 } 146 if (code >= 3000 && code <= 3999) { 147 // Reserved for use by libraries, frameworks, and applications. 148 // Should be registered with IANA. Interpretation of these codes is 149 // undefined by the WebSocket protocol. 150 return true; 151 } 152 if (code >= 4000 && code <= 4999) { 153 // Reserved for private use. Interpretation of these codes is 154 // undefined by the WebSocket protocol. 155 return true; 156 } 157 if (code >= 5000) { 158 return false; 159 } 160} 161 162util.inherits(WebSocketConnection, EventEmitter); 163 164WebSocketConnection.prototype._addSocketEventListeners = function() { 165 this.socket.on('error', this.handleSocketError.bind(this)); 166 this.socket.on('end', this.handleSocketEnd.bind(this)); 167 this.socket.on('close', this.handleSocketClose.bind(this)); 168}; 169 170WebSocketConnection.prototype.handleSocketError = function(error) { 171 this._debug('handleSocketError: %j', error); 172 if (this.state === STATE_CLOSED) { 173 // See https://github.com/theturtle32/WebSocket-Node/issues/288 174 this._debug(' --- Socket \'error\' after \'close\''); 175 return; 176 } 177 this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL; 178 this.closeDescription = 'Socket Error: ' + error.syscall + ' ' + error.code; 179 this.connected = false; 180 this.state = STATE_CLOSED; 181 this.fragmentationSize = 0; 182 if (utils.eventEmitterListenerCount(this, 'error') > 0) { 183 this.emit('error', error); 184 } 185 this.socket.destroy(error); 186 this._debug.printOutput(); 187 188 this._end(); 189}; 190 191WebSocketConnection.prototype.handleSocketEnd = function() { 192 this._debug('handleSocketEnd: received socket end. state = %s', this.state); 193 this.receivedEnd = true; 194 if (this.state === STATE_CLOSED) { 195 // When using the TLS module, sometimes the socket will emit 'end' 196 // after it emits 'close'. I don't think that's correct behavior, 197 // but we should deal with it gracefully by ignoring it. 198 this._debug(' --- Socket \'end\' after \'close\''); 199 return; 200 } 201 if (this.state !== STATE_PEER_REQUESTED_CLOSE && 202 this.state !== STATE_ENDING) { 203 this._debug(' --- UNEXPECTED socket end.'); 204 this.socket.end(); 205 206 this._end(); 207 } 208}; 209 210WebSocketConnection.prototype.handleSocketClose = function(hadError) { 211 this._debug('handleSocketClose: received socket close'); 212 this.socketHadError = hadError; 213 this.connected = false; 214 this.state = STATE_CLOSED; 215 // If closeReasonCode is still set to -1 at this point then we must 216 // not have received a close frame!! 217 if (this.closeReasonCode === -1) { 218 this.closeReasonCode = WebSocketConnection.CLOSE_REASON_ABNORMAL; 219 this.closeDescription = 'Connection dropped by remote peer.'; 220 } 221 this.clearCloseTimer(); 222 if (!this.closeEventEmitted) { 223 this.closeEventEmitted = true; 224 this._debug('-- Emitting WebSocketConnection close event'); 225 this.emit('close', this.closeReasonCode, this.closeDescription); 226 } 227}; 228 229WebSocketConnection.prototype.close = function(reasonCode, description) { 230 if (this.connected) { 231 this._debug('close: Initating clean WebSocket close sequence.'); 232 if ('number' !== typeof reasonCode) { 233 reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; 234 } 235 if (!validateCloseReason(reasonCode)) { 236 throw new Error('Close code ' + reasonCode + ' is not valid.'); 237 } 238 if ('string' !== typeof description) { 239 description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode]; 240 } 241 this.closeReasonCode = reasonCode; 242 this.closeDescription = description; 243 this.setCloseTimer(); 244 this.sendCloseFrame(this.closeReasonCode, this.closeDescription); 245 this.state = STATE_ENDING; 246 this.connected = false; 247 } 248}; 249 250WebSocketConnection.prototype.drop = function(reasonCode, description, skipCloseFrame) { 251 this._debug('drop'); 252 if (typeof(reasonCode) !== 'number') { 253 reasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; 254 } 255 256 if (typeof(description) !== 'string') { 257 // If no description is provided, try to look one up based on the 258 // specified reasonCode. 259 description = WebSocketConnection.CLOSE_DESCRIPTIONS[reasonCode]; 260 } 261 262 this._debug('Forcefully dropping connection. skipCloseFrame: %s, code: %d, description: %s', 263 skipCloseFrame, reasonCode, description 264 ); 265 266 this.closeReasonCode = reasonCode; 267 this.closeDescription = description; 268 this.frameQueue = []; 269 this.fragmentationSize = 0; 270 if (!skipCloseFrame) { 271 this.sendCloseFrame(reasonCode, description); 272 } 273 this.connected = false; 274 this.state = STATE_CLOSED; 275 this.clearCloseTimer(); 276 277 if (!this.closeEventEmitted) { 278 this.closeEventEmitted = true; 279 this._debug('Emitting WebSocketConnection close event'); 280 this.emit('close', this.closeReasonCode, this.closeDescription); 281 } 282 283 this._debug('Drop: destroying socket'); 284 this.socket.destroy(); 285 286 this._end(); 287}; 288 289WebSocketConnection.prototype.setCloseTimer = function() { 290 this._debug('setCloseTimer'); 291 this.clearCloseTimer(); 292 this._debug('Setting close timer'); 293 this.waitingForCloseResponse = true; 294 this.closeTimer = setTimeout(this._closeTimerHandler, this.closeTimeout); 295}; 296 297WebSocketConnection.prototype.clearCloseTimer = function() { 298 this._debug('clearCloseTimer'); 299 if (this.closeTimer) { 300 this._debug('Clearing close timer'); 301 clearTimeout(this.closeTimer); 302 this.waitingForCloseResponse = false; 303 this.closeTimer = null; 304 } 305}; 306 307WebSocketConnection.prototype.handleCloseTimer = function() { 308 this._debug('handleCloseTimer'); 309 this.closeTimer = null; 310 if (this.waitingForCloseResponse) { 311 this._debug('Close response not received from client. Forcing socket end.'); 312 this.waitingForCloseResponse = false; 313 this.state = STATE_CLOSED; 314 this.socket.end(); 315 316 this._end(); 317 } 318}; 319 320WebSocketConnection.prototype.processFrame = function(frame) { 321 if (!this.connected) { 322 return; 323 } 324 325 this._debug('processFrame'); 326 this._debug(' -- frame: %s', frame); 327 328 // Any non-control opcode besides 0x00 (continuation) received in the 329 // middle of a fragmented message is illegal. 330 if (this.frameQueue.length !== 0 && (frame.opcode > 0x00 && frame.opcode < 0x08)) { 331 this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, 332 'Illegal frame opcode 0x' + frame.opcode.toString(16) + ' ' + 333 'received in middle of fragmented message.'); 334 return; 335 } 336 337 switch(frame.opcode) { 338 case 0x02: // WebSocketFrame.BINARY_FRAME 339 this._debug('-- Binary Frame'); 340 if (this.assembleFragments) { 341 if (frame.fin) { 342 // Complete single-frame message received 343 this._debug('---- Emitting \'message\' event'); 344 this.emit('message', { 345 type: 'binary', 346 binaryData: frame.binaryPayload 347 }); 348 } 349 else { 350 // beginning of a fragmented message 351 this.frameQueue.push(frame); 352 this.fragmentationSize = frame.length; 353 } 354 } 355 break; 356 case 0x01: // WebSocketFrame.TEXT_FRAME 357 this._debug('-- Text Frame'); 358 if (this.assembleFragments) { 359 if (frame.fin) { 360 // Complete single-frame message received 361 this._debug('---- Emitting \'message\' event'); 362 this.emit('message', { 363 type: 'utf8', 364 utf8Data: frame.binaryPayload.toString('utf8') 365 }); 366 } 367 else { 368 // beginning of a fragmented message 369 this.frameQueue.push(frame); 370 this.fragmentationSize = frame.length; 371 } 372 } 373 break; 374 case 0x00: // WebSocketFrame.CONTINUATION 375 this._debug('-- Continuation Frame'); 376 if (this.assembleFragments) { 377 if (this.frameQueue.length === 0) { 378 this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, 379 'Unexpected Continuation Frame'); 380 return; 381 } 382 383 this.fragmentationSize += frame.length; 384 385 if (this.fragmentationSize > this.maxReceivedMessageSize) { 386 this.drop(WebSocketConnection.CLOSE_REASON_MESSAGE_TOO_BIG, 387 'Maximum message size exceeded.'); 388 return; 389 } 390 391 this.frameQueue.push(frame); 392 393 if (frame.fin) { 394 // end of fragmented message, so we process the whole 395 // message now. We also have to decode the utf-8 data 396 // for text frames after combining all the fragments. 397 var bytesCopied = 0; 398 var binaryPayload = bufferAllocUnsafe(this.fragmentationSize); 399 var opcode = this.frameQueue[0].opcode; 400 this.frameQueue.forEach(function (currentFrame) { 401 currentFrame.binaryPayload.copy(binaryPayload, bytesCopied); 402 bytesCopied += currentFrame.binaryPayload.length; 403 }); 404 this.frameQueue = []; 405 this.fragmentationSize = 0; 406 407 switch (opcode) { 408 case 0x02: // WebSocketOpcode.BINARY_FRAME 409 this.emit('message', { 410 type: 'binary', 411 binaryData: binaryPayload 412 }); 413 break; 414 case 0x01: // WebSocketOpcode.TEXT_FRAME 415 this.emit('message', { 416 type: 'utf8', 417 utf8Data: binaryPayload.toString('utf8') 418 }); 419 break; 420 default: 421 this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, 422 'Unexpected first opcode in fragmentation sequence: 0x' + opcode.toString(16)); 423 return; 424 } 425 } 426 } 427 break; 428 case 0x09: // WebSocketFrame.PING 429 this._debug('-- Ping Frame'); 430 431 if (this._pingListenerCount > 0) { 432 // logic to emit the ping frame: this is only done when a listener is known to exist 433 // Expose a function allowing the user to override the default ping() behavior 434 var cancelled = false; 435 var cancel = function() { 436 cancelled = true; 437 }; 438 this.emit('ping', cancel, frame.binaryPayload); 439 440 // Only send a pong if the client did not indicate that he would like to cancel 441 if (!cancelled) { 442 this.pong(frame.binaryPayload); 443 } 444 } 445 else { 446 this.pong(frame.binaryPayload); 447 } 448 449 break; 450 case 0x0A: // WebSocketFrame.PONG 451 this._debug('-- Pong Frame'); 452 this.emit('pong', frame.binaryPayload); 453 break; 454 case 0x08: // WebSocketFrame.CONNECTION_CLOSE 455 this._debug('-- Close Frame'); 456 if (this.waitingForCloseResponse) { 457 // Got response to our request to close the connection. 458 // Close is complete, so we just hang up. 459 this._debug('---- Got close response from peer. Completing closing handshake.'); 460 this.clearCloseTimer(); 461 this.waitingForCloseResponse = false; 462 this.state = STATE_CLOSED; 463 this.socket.end(); 464 465 this._end(); 466 return; 467 } 468 469 this._debug('---- Closing handshake initiated by peer.'); 470 // Got request from other party to close connection. 471 // Send back acknowledgement and then hang up. 472 this.state = STATE_PEER_REQUESTED_CLOSE; 473 var respondCloseReasonCode; 474 475 // Make sure the close reason provided is legal according to 476 // the protocol spec. Providing no close status is legal. 477 // WebSocketFrame sets closeStatus to -1 by default, so if it 478 // is still -1, then no status was provided. 479 if (frame.invalidCloseFrameLength) { 480 this.closeReasonCode = 1005; // 1005 = No reason provided. 481 respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; 482 } 483 else if (frame.closeStatus === -1 || validateCloseReason(frame.closeStatus)) { 484 this.closeReasonCode = frame.closeStatus; 485 respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; 486 } 487 else { 488 this.closeReasonCode = frame.closeStatus; 489 respondCloseReasonCode = WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR; 490 } 491 492 // If there is a textual description in the close frame, extract it. 493 if (frame.binaryPayload.length > 1) { 494 this.closeDescription = frame.binaryPayload.toString('utf8'); 495 } 496 else { 497 this.closeDescription = WebSocketConnection.CLOSE_DESCRIPTIONS[this.closeReasonCode]; 498 } 499 this._debug( 500 '------ Remote peer %s - code: %d - %s - close frame payload length: %d', 501 this.remoteAddress, this.closeReasonCode, 502 this.closeDescription, frame.length 503 ); 504 this._debug('------ responding to remote peer\'s close request.'); 505 this.drop(respondCloseReasonCode, null); 506 this.connected = false; 507 break; 508 default: 509 this._debug('-- Unrecognized Opcode %d', frame.opcode); 510 this.drop(WebSocketConnection.CLOSE_REASON_PROTOCOL_ERROR, 511 'Unrecognized Opcode: 0x' + frame.opcode.toString(16)); 512 break; 513 } 514}; 515 516WebSocketConnection.prototype.send = function(data, cb) { 517 this._debug('send'); 518 if (Buffer.isBuffer(data)) { 519 this.sendBytes(data, cb); 520 } 521 else if (typeof(data['toString']) === 'function') { 522 this.sendUTF(data, cb); 523 } 524 else { 525 throw new Error('Data provided must either be a Node Buffer or implement toString()'); 526 } 527}; 528 529WebSocketConnection.prototype.sendUTF = function(data, cb) { 530 data = bufferFromString(data.toString(), 'utf8'); 531 this._debug('sendUTF: %d bytes', data.length); 532 533 var frame = new WebSocketFrame(); 534 frame.opcode = 0x01; // WebSocketOpcode.TEXT_FRAME 535 frame.binaryPayload = data; 536 537 this.fragmentAndSend(frame, cb); 538}; 539 540WebSocketConnection.prototype.sendBytes = function(data, cb) { 541 this._debug('sendBytes'); 542 if (!Buffer.isBuffer(data)) { 543 throw new Error('You must pass a Node Buffer object to WebSocketConnection.prototype.sendBytes()'); 544 } 545 546 var frame = new WebSocketFrame(); 547 frame.opcode = 0x02; // WebSocketOpcode.BINARY_FRAME 548 frame.binaryPayload = data; 549 550 this.fragmentAndSend(frame, cb); 551}; 552 553WebSocketConnection.prototype.ping = function(data) { 554 this._debug('ping'); 555 556 var frame = new WebSocketFrame(); 557 frame.opcode = 0x09; // WebSocketOpcode.PING 558 frame.fin = true; 559 560 if (data) { 561 if (!Buffer.isBuffer(data)) { 562 data = bufferFromString(data.toString(), 'utf8'); 563 } 564 if (data.length > 125) { 565 this._debug('WebSocket: Data for ping is longer than 125 bytes. Truncating.'); 566 data = data.slice(0,124); 567 } 568 frame.binaryPayload = data; 569 } 570 571 this.sendFrame(frame); 572}; 573 574// Pong frames have to echo back the contents of the data portion of the 575// ping frame exactly, byte for byte. 576WebSocketConnection.prototype.pong = function(binaryPayload) { 577 this._debug('pong'); 578 579 var frame = new WebSocketFrame(); 580 frame.opcode = 0x0A; // WebSocketOpcode.PONG 581 if (Buffer.isBuffer(binaryPayload) && binaryPayload.length > 125) { 582 this._debug('WebSocket: Data for pong is longer than 125 bytes. Truncating.'); 583 binaryPayload = binaryPayload.slice(0,124); 584 } 585 frame.binaryPayload = binaryPayload; 586 frame.fin = true; 587 588 this.sendFrame(frame); 589}; 590 591WebSocketConnection.prototype.fragmentAndSend = function(frame, cb) { 592 this._debug('fragmentAndSend'); 593 if (frame.opcode > 0x07) { 594 throw new Error('You cannot fragment control frames.'); 595 } 596 597 var threshold = this.config.fragmentationThreshold; 598 var length = frame.binaryPayload.length; 599 600 // Send immediately if fragmentation is disabled or the message is not 601 // larger than the fragmentation threshold. 602 if (!this.config.fragmentOutgoingMessages || (frame.binaryPayload && length <= threshold)) { 603 frame.fin = true; 604 this.sendFrame(frame, cb); 605 return; 606 } 607 608 var numFragments = Math.ceil(length / threshold); 609 var sentFragments = 0; 610 var sentCallback = function fragmentSentCallback(err) { 611 if (err) { 612 if (typeof cb === 'function') { 613 // pass only the first error 614 cb(err); 615 cb = null; 616 } 617 return; 618 } 619 ++sentFragments; 620 if ((sentFragments === numFragments) && (typeof cb === 'function')) { 621 cb(); 622 } 623 }; 624 for (var i=1; i <= numFragments; i++) { 625 var currentFrame = new WebSocketFrame(); 626 627 // continuation opcode except for first frame. 628 currentFrame.opcode = (i === 1) ? frame.opcode : 0x00; 629 630 // fin set on last frame only 631 currentFrame.fin = (i === numFragments); 632 633 // length is likely to be shorter on the last fragment 634 var currentLength = (i === numFragments) ? length - (threshold * (i-1)) : threshold; 635 var sliceStart = threshold * (i-1); 636 637 // Slice the right portion of the original payload 638 currentFrame.binaryPayload = frame.binaryPayload.slice(sliceStart, sliceStart + currentLength); 639 640 this.sendFrame(currentFrame, sentCallback); 641 } 642}; 643 644WebSocketConnection.prototype.sendCloseFrame = function(reasonCode, description, cb) { 645 if (typeof(reasonCode) !== 'number') { 646 reasonCode = WebSocketConnection.CLOSE_REASON_NORMAL; 647 } 648 649 this._debug('sendCloseFrame state: %s, reasonCode: %d, description: %s', this.state, reasonCode, description); 650 651 if (this.state !== STATE_OPEN && this.state !== STATE_PEER_REQUESTED_CLOSE) { return; } 652 653 var frame = new WebSocketFrame(); 654 frame.fin = true; 655 frame.opcode = 0x08; // WebSocketOpcode.CONNECTION_CLOSE 656 frame.closeStatus = reasonCode; 657 if (typeof(description) === 'string') { 658 frame.binaryPayload = bufferFromString(description, 'utf8'); 659 } 660 661 this.sendFrame(frame, cb); 662 this.socket.end(); 663}; 664 665WebSocketConnection.prototype._send_frame = unit_lib.websocket_send_frame; 666 667WebSocketConnection.prototype.sendFrame = function(frame, cb) { 668 this._debug('sendFrame'); 669 670 frame.mask = this.maskOutgoingPackets; 671 672 this._send_frame(frame); 673 674 if (typeof cb === 'function') { 675 cb(); 676 } 677 678 var flushed = 0; // this.socket.write(frame.toBuffer(), cb); 679 this.outputBufferFull = !flushed; 680 return flushed; 681}; 682 683module.exports = WebSocketConnection; 684