Skip to content

Commit 299849b

Browse files
author
Kevin Roark
committed
A faster and smaller binary parser and protocol
This is a squash of a few commits. Below is a small summary of commits. Results from it: before the build size of socket.io-client was ~250K. Now it is ~215K. Tests I was doing here (https://github.com/kevin-roark/socketio-binaryexample/tree/speed-testing) take about 1/4 - 1/5 as long with this commit compared to msgpack. The first was the initial rewrite of the encoding, which removes msgpack and instead uses a sequence of engine.write's for a binary event. The first write is the packet metadata with placeholders in the json for any binary data. Then the following events are the raw binary data that get filled by the placeholders. The second commit was bug fixes that made the tests pass. The third commit was removing unnecssary packages from package.json. Fourth commit was adding nice comments, and 5th commit was merging upstream. The remaining commits involved merging with actual socket.io-parser, rather than the protocol repository. Oops.
1 parent 36f8aa8 commit 299849b

File tree

8 files changed

+248
-177
lines changed

8 files changed

+248
-177
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
language: node_js
22
node_js:
3-
- 0.10
3+
- 0.10
44
notifications:
55
irc: irc.freenode.org##socket.io
66
env:

binary.js

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* Modle requirements
3+
*/
4+
5+
var isArray = require('isarray');
6+
7+
/**
8+
* Replaces every Buffer | ArrayBuffer in packet with a numbered placeholder.
9+
* Anything with blobs or files should be fed through removeBlobs before coming
10+
* here.
11+
*
12+
* @param {Object} packet - socket.io event packet
13+
* @return {Object} with deconstructed packet and list of buffers
14+
* @api public
15+
*/
16+
17+
exports.deconstructPacket = function(packet) {
18+
var buffers = [];
19+
var packetData = packet.data;
20+
21+
function deconstructBinPackRecursive(data) {
22+
if (!data) return data;
23+
24+
if ((global.Buffer && Buffer.isBuffer(data)) ||
25+
(global.ArrayBuffer && data instanceof ArrayBuffer)) { // replace binary
26+
var placeholder = {_placeholder: true, num: buffers.length};
27+
buffers.push(data);
28+
return placeholder;
29+
} else if (isArray(data)) {
30+
var newData = new Array(data.length);
31+
for (var i = 0; i < data.length; i++) {
32+
newData[i] = deconstructBinPackRecursive(data[i]);
33+
}
34+
return newData;
35+
} else if ('object' == typeof data) {
36+
var newData = {};
37+
for (var key in data) {
38+
newData[key] = deconstructBinPackRecursive(data[key]);
39+
}
40+
return newData;
41+
}
42+
return data;
43+
}
44+
45+
var pack = packet;
46+
pack.data = deconstructBinPackRecursive(packetData);
47+
pack.attachments = buffers.length; // number of binary 'attachments'
48+
return {packet: pack, buffers: buffers};
49+
}
50+
51+
/**
52+
* Reconstructs a binary packet from its placeholder packet and buffers
53+
*
54+
* @param {Object} packet - event packet with placeholders
55+
* @param {Array} buffers - binary buffers to put in placeholder positions
56+
* @return {Object} reconstructed packet
57+
* @api public
58+
*/
59+
60+
exports.reconstructPacket = function(packet, buffers) {
61+
var curPlaceHolder = 0;
62+
63+
function reconstructBinPackRecursive(data) {
64+
if (data._placeholder) {
65+
var buf = buffers[data.num]; // appropriate buffer (should be natural order anyway)
66+
return buf;
67+
} else if (isArray(data)) {
68+
for (var i = 0; i < data.length; i++) {
69+
data[i] = reconstructBinPackRecursive(data[i]);
70+
}
71+
return data;
72+
} else if ('object' == typeof data) {
73+
for (var key in data) {
74+
data[key] = reconstructBinPackRecursive(data[key]);
75+
}
76+
return data;
77+
}
78+
return data;
79+
}
80+
81+
packet.data = reconstructBinPackRecursive(packet.data);
82+
packet.attachments = undefined; // no longer useful
83+
return packet;
84+
}
85+
86+
/**
87+
* Asynchronously removes Blobs or Files from data via
88+
* FileReader's readAsArrayBuffer method. Used before encoding
89+
* data as msgpack. Calls callback with the blobless data.
90+
*
91+
* @param {Object} data
92+
* @param {Function} callback
93+
* @api private
94+
*/
95+
96+
exports.removeBlobs = function(data, callback) {
97+
98+
function removeBlobsRecursive(obj, curKey, containingObject) {
99+
if (!obj) return obj;
100+
101+
// convert any blob
102+
if ((global.Blob && obj instanceof Blob) ||
103+
(global.File && obj instanceof File)) {
104+
pendingBlobs++;
105+
106+
// async filereader
107+
var fileReader = new FileReader();
108+
fileReader.onload = function() { // this.result == arraybuffer
109+
if (containingObject) {
110+
containingObject[curKey] = this.result;
111+
}
112+
else {
113+
bloblessData = this.result;
114+
}
115+
116+
// if nothing pending its callback time
117+
if(! --pendingBlobs) {
118+
callback(bloblessData);
119+
}
120+
};
121+
122+
fileReader.readAsArrayBuffer(obj); // blob -> arraybuffer
123+
}
124+
125+
if (isArray(obj)) { // handle array
126+
for (var i = 0; i < obj.length; i++) {
127+
removeBlobsRecursive(obj[i], i, obj);
128+
}
129+
} else if (obj && 'object' == typeof obj) { // and object
130+
for (var key in obj) {
131+
removeBlobsRecursive(obj[key], key, obj);
132+
}
133+
}
134+
}
135+
136+
var pendingBlobs = 0;
137+
var bloblessData = data;
138+
removeBlobsRecursive(bloblessData);
139+
if (!pendingBlobs) {
140+
callback(bloblessData);
141+
}
142+
}

0 commit comments

Comments
 (0)