Receiver.hixie.js (4228B)
1/*! 2 * ws: a node.js websocket client 3 * Copyright(c) 2011 Einar Otto Stangvik <einaros@gmail.com> 4 * MIT Licensed 5 */ 6 7var util = require('util'); 8 9/** 10 * State constants 11 */ 12 13var EMPTY = 0 14 , BODY = 1; 15var BINARYLENGTH = 2 16 , BINARYBODY = 3; 17 18/** 19 * Hixie Receiver implementation 20 */ 21 22function Receiver () { 23 if (this instanceof Receiver === false) { 24 throw new TypeError("Classes can't be function-called"); 25 } 26 27 this.state = EMPTY; 28 this.buffers = []; 29 this.messageEnd = -1; 30 this.spanLength = 0; 31 this.dead = false; 32 33 this.onerror = function() {}; 34 this.ontext = function() {}; 35 this.onbinary = function() {}; 36 this.onclose = function() {}; 37 this.onping = function() {}; 38 this.onpong = function() {}; 39} 40 41module.exports = Receiver; 42 43/** 44 * Add new data to the parser. 45 * 46 * @api public 47 */ 48 49Receiver.prototype.add = function(data) { 50 if (this.dead) return; 51 var self = this; 52 function doAdd() { 53 if (self.state === EMPTY) { 54 if (data.length == 2 && data[0] == 0xFF && data[1] == 0x00) { 55 self.reset(); 56 self.onclose(); 57 return; 58 } 59 if (data[0] === 0x80) { 60 self.messageEnd = 0; 61 self.state = BINARYLENGTH; 62 data = data.slice(1); 63 } else { 64 65 if (data[0] !== 0x00) { 66 self.error('payload must start with 0x00 byte', true); 67 return; 68 } 69 data = data.slice(1); 70 self.state = BODY; 71 72 } 73 } 74 if (self.state === BINARYLENGTH) { 75 var i = 0; 76 while ((i < data.length) && (data[i] & 0x80)) { 77 self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); 78 ++i; 79 } 80 if (i < data.length) { 81 self.messageEnd = 128 * self.messageEnd + (data[i] & 0x7f); 82 self.state = BINARYBODY; 83 ++i; 84 } 85 if (i > 0) 86 data = data.slice(i); 87 } 88 if (self.state === BINARYBODY) { 89 var dataleft = self.messageEnd - self.spanLength; 90 if (data.length >= dataleft) { 91 // consume the whole buffer to finish the frame 92 self.buffers.push(data); 93 self.spanLength += dataleft; 94 self.messageEnd = dataleft; 95 return self.parse(); 96 } 97 // frame's not done even if we consume it all 98 self.buffers.push(data); 99 self.spanLength += data.length; 100 return; 101 } 102 self.buffers.push(data); 103 if ((self.messageEnd = bufferIndex(data, 0xFF)) != -1) { 104 self.spanLength += self.messageEnd; 105 return self.parse(); 106 } 107 else self.spanLength += data.length; 108 } 109 while(data) data = doAdd(); 110}; 111 112/** 113 * Releases all resources used by the receiver. 114 * 115 * @api public 116 */ 117 118Receiver.prototype.cleanup = function() { 119 this.dead = true; 120 this.state = EMPTY; 121 this.buffers = []; 122}; 123 124/** 125 * Process buffered data. 126 * 127 * @api public 128 */ 129 130Receiver.prototype.parse = function() { 131 var output = new Buffer(this.spanLength); 132 var outputIndex = 0; 133 for (var bi = 0, bl = this.buffers.length; bi < bl - 1; ++bi) { 134 var buffer = this.buffers[bi]; 135 buffer.copy(output, outputIndex); 136 outputIndex += buffer.length; 137 } 138 var lastBuffer = this.buffers[this.buffers.length - 1]; 139 if (this.messageEnd > 0) lastBuffer.copy(output, outputIndex, 0, this.messageEnd); 140 if (this.state !== BODY) --this.messageEnd; 141 var tail = null; 142 if (this.messageEnd < lastBuffer.length - 1) { 143 tail = lastBuffer.slice(this.messageEnd + 1); 144 } 145 this.reset(); 146 this.ontext(output.toString('utf8')); 147 return tail; 148}; 149 150/** 151 * Handles an error 152 * 153 * @api private 154 */ 155 156Receiver.prototype.error = function (reason, terminate) { 157 if (this.dead) return; 158 this.reset(); 159 if(typeof reason == 'string'){ 160 this.onerror(new Error(reason), terminate); 161 } 162 else if(reason.constructor == Error){ 163 this.onerror(reason, terminate); 164 } 165 else{ 166 this.onerror(new Error("An error occured"),terminate); 167 } 168 return this; 169}; 170 171/** 172 * Reset parser state 173 * 174 * @api private 175 */ 176 177Receiver.prototype.reset = function (reason) { 178 if (this.dead) return; 179 this.state = EMPTY; 180 this.buffers = []; 181 this.messageEnd = -1; 182 this.spanLength = 0; 183}; 184 185/** 186 * Internal api 187 */ 188 189function bufferIndex(buffer, byte) { 190 for (var i = 0, l = buffer.length; i < l; ++i) { 191 if (buffer[i] === byte) return i; 192 } 193 return -1; 194}