mirror of
https://github.com/socketio/socket.io.git
synced 2026-04-30 03:00:39 -04:00
Separated the encoding and decoding into two public-facing objects, Encoder and Decoder. Both objects take nothing on construction. Encoder has a single method, encode, that mimics the previous version's function encode (takes a packet object and a callback). Decoder has a single method too, add, that takes any object (packet string or binary data). Decoder emits a 'decoded' event when it has received all of the parts of a packet. The only parameter for the decoded event is the reconstructed packet. I am hesitant about the Encoder.encode vs Decoder.add thing. Should it be more consistent, or should it stay like this where the function names are more descriptive? Also, rewrote the test helper functions to deal with new event-based decoding. Wrote a new test in test/arraybuffer.js that tests for memory leaks in Decoder as well.
372 lines
6.9 KiB
JavaScript
372 lines
6.9 KiB
JavaScript
|
|
/**
|
|
* Module dependencies.
|
|
*/
|
|
|
|
var debug = require('debug')('socket.io-parser');
|
|
var json = require('json3');
|
|
var isArray = require('isarray');
|
|
var Emitter = require('emitter');
|
|
var binary = require('./binary');
|
|
|
|
|
|
/**
|
|
* Protocol version.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
exports.protocol = 1;
|
|
|
|
/**
|
|
* Packet types.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
exports.types = [
|
|
'CONNECT',
|
|
'DISCONNECT',
|
|
'EVENT',
|
|
'BINARY_EVENT',
|
|
'ACK',
|
|
'ERROR'
|
|
];
|
|
|
|
/**
|
|
* Packet type `connect`.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
exports.CONNECT = 0;
|
|
|
|
/**
|
|
* Packet type `disconnect`.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
exports.DISCONNECT = 1;
|
|
|
|
/**
|
|
* Packet type `event`.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
exports.EVENT = 2;
|
|
|
|
/**
|
|
* Packet type `ack`.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
exports.ACK = 3;
|
|
|
|
/**
|
|
* Packet type `error`.
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
exports.ERROR = 4;
|
|
|
|
/**
|
|
* Packet type 'binary event'
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
exports.BINARY_EVENT = 5;
|
|
|
|
exports.Encoder = Encoder
|
|
|
|
/**
|
|
* A socket.io Encoder instance
|
|
*
|
|
* @api public
|
|
*/
|
|
function Encoder() {};
|
|
|
|
/**
|
|
* Encode a packet as a single string if non-binary, or as a
|
|
* buffer sequence, depending on packet type.
|
|
*
|
|
* @param {Object} obj - packet object
|
|
* @param {Function} callback - function to handle encodings (likely engine.write)
|
|
* @return Calls callback with Array of encodings
|
|
* @api public
|
|
*/
|
|
|
|
Encoder.prototype.encode = function(obj, callback){
|
|
debug('encoding packet %j', obj);
|
|
|
|
if (obj.type == exports.BINARY_EVENT) {
|
|
encodeAsBinary(obj, callback);
|
|
}
|
|
else {
|
|
var encoding = encodeAsString(obj);
|
|
callback([encoding]);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Encode packet as string.
|
|
*
|
|
* @param {Object} packet
|
|
* @return {String} encoded
|
|
* @api private
|
|
*/
|
|
|
|
function encodeAsString(obj) {
|
|
var str = '';
|
|
var nsp = false;
|
|
|
|
// first is type
|
|
str += obj.type;
|
|
|
|
// attachments if we have them
|
|
if (exports.BINARY_EVENT == obj.type) {
|
|
str += obj.attachments;
|
|
str += '-';
|
|
}
|
|
|
|
// if we have a namespace other than `/`
|
|
// we append it followed by a comma `,`
|
|
if (obj.nsp && '/' != obj.nsp) {
|
|
nsp = true;
|
|
str += obj.nsp;
|
|
}
|
|
|
|
// immediately followed by the id
|
|
if (null != obj.id) {
|
|
if (nsp) {
|
|
str += ',';
|
|
nsp = false;
|
|
}
|
|
str += obj.id;
|
|
}
|
|
|
|
// json data
|
|
if (null != obj.data) {
|
|
if (nsp) str += ',';
|
|
str += json.stringify(obj.data);
|
|
}
|
|
|
|
debug('encoded %j as %s', obj, str);
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* Encode packet as 'buffer sequence' by removing blobs, and
|
|
* deconstructing packet into object with placeholders and
|
|
* a list of buffers.
|
|
*
|
|
* @param {Object} packet
|
|
* @return {Buffer} encoded
|
|
* @api private
|
|
*/
|
|
|
|
function encodeAsBinary(obj, callback) {
|
|
|
|
function writeEncoding(bloblessData) {
|
|
var deconstruction = binary.deconstructPacket(bloblessData);
|
|
var pack = encodeAsString(deconstruction.packet);
|
|
var buffers = deconstruction.buffers;
|
|
|
|
buffers.unshift(pack); // add packet info to beginning of data list
|
|
callback(buffers); // write all the buffers
|
|
}
|
|
|
|
binary.removeBlobs(obj, writeEncoding);
|
|
}
|
|
|
|
exports.Decoder = Decoder
|
|
|
|
/**
|
|
* A socket.io Decoder instance
|
|
*
|
|
* @return {Object} decoder
|
|
* @api public
|
|
*/
|
|
|
|
function Decoder() {
|
|
this.reconstructor = null;
|
|
}
|
|
|
|
/**
|
|
* Mix in `Emitter` with Decoder.
|
|
*/
|
|
|
|
Emitter(Decoder.prototype);
|
|
|
|
/**
|
|
* Decodes an ecoded packet string into packet JSON.
|
|
*
|
|
* @param {String} obj - encoded packet
|
|
* @return {Object} packet
|
|
* @api public
|
|
*/
|
|
|
|
Decoder.prototype.add = function(obj) {
|
|
var packet;
|
|
if ('string' == typeof obj) {
|
|
packet = decodeString(obj);
|
|
if (packet.type == exports.BINARY_EVENT) { // binary packet's json
|
|
this.reconstructor = new BinaryReconstructor(packet);
|
|
} else { // non-binary full packet
|
|
this.emit('decoded', packet);
|
|
}
|
|
}
|
|
else if ((global.Buffer && Buffer.isBuffer(obj)) ||
|
|
(global.ArrayBuffer && obj instanceof ArrayBuffer) ||
|
|
obj.base64) { // raw binary data
|
|
if (!this.reconstructor) {
|
|
throw new Error('got binary data when not reconstructing a packet');
|
|
} else {
|
|
packet = this.reconstructor.takeBinaryData(obj);
|
|
if (packet) { // received final buffer
|
|
this.reconstructor = null;
|
|
this.emit('decoded', packet);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
throw new Error('Unknown type: ' + obj);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Decode a packet String (JSON data)
|
|
*
|
|
* @param {String} str
|
|
* @return {Object} packet
|
|
* @api private
|
|
*/
|
|
|
|
function decodeString(str) {
|
|
var p = {};
|
|
var i = 0;
|
|
|
|
// look up type
|
|
p.type = Number(str.charAt(0));
|
|
if (null == exports.types[p.type]) return error();
|
|
|
|
// look up attachments if type binary
|
|
if (exports.BINARY_EVENT == p.type) {
|
|
p.attachments = '';
|
|
while (str.charAt(++i) != '-') {
|
|
p.attachments += str.charAt(i);
|
|
}
|
|
p.attachments = Number(p.attachments);
|
|
}
|
|
|
|
// look up namespace (if any)
|
|
if ('/' == str.charAt(i + 1)) {
|
|
p.nsp = '';
|
|
while (++i) {
|
|
var c = str.charAt(i);
|
|
if (',' == c) break;
|
|
p.nsp += c;
|
|
if (i + 1 == str.length) break;
|
|
}
|
|
} else {
|
|
p.nsp = '/';
|
|
}
|
|
|
|
// look up id
|
|
var next = str.charAt(i + 1);
|
|
if ('' != next && Number(next) == next) {
|
|
p.id = '';
|
|
while (++i) {
|
|
var c = str.charAt(i);
|
|
if (null == c || Number(c) != c) {
|
|
--i;
|
|
break;
|
|
}
|
|
p.id += str.charAt(i);
|
|
if (i + 1 == str.length) break;
|
|
}
|
|
p.id = Number(p.id);
|
|
}
|
|
|
|
// look up json data
|
|
if (str.charAt(++i)) {
|
|
try {
|
|
p.data = json.parse(str.substr(i));
|
|
} catch(e){
|
|
return error();
|
|
}
|
|
}
|
|
|
|
debug('decoded %s as %j', str, p);
|
|
return p;
|
|
};
|
|
|
|
/**
|
|
* Deallocates a parser's resources
|
|
*
|
|
* @api public
|
|
*/
|
|
|
|
Decoder.prototype.destroy = function() {
|
|
if (this.reconstructor) {
|
|
this.reconstructor.finishedReconstruction();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A manager of a binary event's 'buffer sequence'. Should
|
|
* be constructed whenever a packet of type BINARY_EVENT is
|
|
* decoded.
|
|
*
|
|
* @param {Object} packet
|
|
* @return {BinaryReconstructor} initialized reconstructor
|
|
* @api private
|
|
*/
|
|
|
|
function BinaryReconstructor(packet) {
|
|
this.reconPack = packet;
|
|
this.buffers = [];
|
|
}
|
|
|
|
/**
|
|
* Method to be called when binary data received from connection
|
|
* after a BINARY_EVENT packet.
|
|
*
|
|
* @param {Buffer | ArrayBuffer} binData - the raw binary data received
|
|
* @return {null | Object} returns null if more binary data is expected or
|
|
* a reconstructed packet object if all buffers have been received.
|
|
* @api private
|
|
*/
|
|
|
|
BinaryReconstructor.prototype.takeBinaryData = function(binData) {
|
|
this.buffers.push(binData);
|
|
if (this.buffers.length == this.reconPack.attachments) { // done with buffer list
|
|
var packet = binary.reconstructPacket(this.reconPack, this.buffers);
|
|
this.finishedReconstruction();
|
|
return packet;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Cleans up binary packet reconstruction variables.
|
|
*
|
|
* @api private
|
|
*/
|
|
|
|
BinaryReconstructor.prototype.finishedReconstruction = function() {
|
|
this.reconPack = null;
|
|
this.buffers = [];
|
|
}
|
|
|
|
function error(data){
|
|
return {
|
|
type: exports.ERROR,
|
|
data: 'parser error'
|
|
};
|
|
}
|