Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example of custom parser #2929

Merged
merged 1 commit into from
May 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions examples/custom-parsers/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

# Socket.IO custom parsers

Since Socket.IO version 2.0.0, you can provide your custom parser, according to the needs of your application.

Several parsers are showcased here:

- the default one: [socket.io-parser](https://github.com/socketio/socket.io-parser)
- one based on msgpack: [socket.io-msgpack-parser](https://github.com/darrachequesne/socket.io-msgpack-parser)
- one based on native JSON: [socket.io-json-parser](https://github.com/darrachequesne/socket.io-json-parser)
- a custom one based on [schemapack](https://github.com/phretaddin/schemapack)

They are tested with various payloads:

- string: `['1', '2', ... '1000']`
- numeric: `[1, 2, ... 1000]`
- binary: `new Buffer(1000), where buf[i] = i`

## How to use

```
$ npm i && npm start
```

## Results

| bytes / packet | CONNECT packet | string | numeric | binary |
|----------------|----------------|--------|---------|-----------|
| default | 1 | 5903 | 3904 | 43 + 1000 |
| msgpack | 20 | 3919 | 2646 | 1029 |
| JSON | 20 | 5930 | 3931 | 3625 |
| schemapack | 20 | 3895 | 2005 | 1005 |

## Comparison

`default parser`
- supports any serializable datastructure, including Blob and File
- **but** binary payload is encoded as 2 packets

`msgpack`
- the size of payloads containing mostly numeric values will be greatly reduced
- **but** rely on [ArrayBuffer](https://caniuse.com/#feat=typedarrays) in the browser (IE > 9)

`JSON`
- optimized
- **but** does not support binary payloads

`schemapack`
- the most efficient in both speed and size
- **but** you have to provide a schema for each packet
21 changes: 21 additions & 0 deletions examples/custom-parsers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "parsers",
"version": "1.0.0",
"description": "Various socket.io parsers",
"scripts": {
"build": "webpack --config ./support/webpack.config.js",
"start": "npm run build && node ./src/server.js"
},
"author": "Damien Arrachequesne",
"license": "MIT",
"dependencies": {
"component-emitter": "^1.2.1",
"express": "^4.15.2",
"schemapack": "^1.4.2",
"socket.io": "socketio/socket.io",
"socket.io-client": "socketio/socket.io-client",
"socket.io-json-parser": "^1.0.0",
"socket.io-msgpack-parser": "^1.0.0",
"webpack": "^2.4.1"
}
}
13 changes: 13 additions & 0 deletions examples/custom-parsers/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Socket.IO custom parsers</title>
</head>
<body>
<script src="client1.bundle.js"></script>
<script src="client2.bundle.js"></script>
<script src="client3.bundle.js"></script>
<script src="client4.bundle.js"></script>
</body>
</html>
8 changes: 8 additions & 0 deletions examples/custom-parsers/src/client1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

const socket = require('socket.io-client')('localhost:3001', {});

socket.io.engine.on('data', (data) => console.log('[default]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));

socket.on('string', (data) => console.log('[default] [string]', data));
socket.on('numeric', (data) => console.log('[default] [numeric]', data));
socket.on('binary', (data) => console.log('[default] [binary]', data));
11 changes: 11 additions & 0 deletions examples/custom-parsers/src/client2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

const customParser = require('socket.io-msgpack-parser');
const socket = require('socket.io-client')('http://localhost:3002', {
parser: customParser
});

socket.io.engine.on('data', (data) => console.log('[msgpack]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));

socket.on('string', (data) => console.log('[msgpack] [string]', data));
socket.on('numeric', (data) => console.log('[msgpack] [numeric]', data));
socket.on('binary', (data) => console.log('[msgpack] [binary]', data));
11 changes: 11 additions & 0 deletions examples/custom-parsers/src/client3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

const customParser = require('socket.io-json-parser');
const socket = require('socket.io-client')('localhost:3003', {
parser: customParser
});

socket.io.engine.on('data', (data) => console.log('[json]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));

socket.on('string', (data) => console.log('[json] [string]', data));
socket.on('numeric', (data) => console.log('[json] [numeric]', data));
socket.on('binary', (data) => console.log('[json] [binary]', data));
11 changes: 11 additions & 0 deletions examples/custom-parsers/src/client4.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

const customParser = require('./custom-parser');
const socket = require('socket.io-client')('localhost:3004', {
parser: customParser
});

socket.io.engine.on('data', (data) => console.log('[custom]' + ' size= ' + (typeof data === 'string' ? data.length : data.byteLength)));

socket.on('string', (data) => console.log('[custom] [string]', data));
socket.on('numeric', (data) => console.log('[custom] [numeric]', data));
socket.on('binary', (data) => console.log('[custom] [binary]', data));
125 changes: 125 additions & 0 deletions examples/custom-parsers/src/custom-parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@

const Emitter = require('component-emitter');
const schemapack = require('schemapack');

/**
* Packet types (see https://github.com/socketio/socket.io-protocol)
*/

const TYPES = {
CONNECT: 0,
DISCONNECT: 1,
EVENT: 2,
ACK: 3,
ERROR: 4,
BINARY_EVENT: 5,
BINARY_ACK: 6
};

const stringSchema = schemapack.build({
_id: 'uint8',
data: [ 'string' ],
nsp: 'string'
});

const numericSchema = schemapack.build({
_id: 'uint8',
data: [ 'uint16' ],
nsp: 'string'
});

const binarySchema = schemapack.build({
_id: 'uint8',
data: 'buffer',
nsp: 'string'
});

const errorPacket = {
type: TYPES.ERROR,
data: 'parser error'
};

class Encoder {
encode (packet, callback) {
switch (packet.type) {
case TYPES.EVENT:
return callback([ this.pack(packet) ]);
default:
return callback([ JSON.stringify(packet) ]);
}
}
pack (packet) {
let eventName = packet.data[0];
let flatPacket = {
data: packet.data[1],
nsp: packet.nsp
};
switch (eventName) {
case 'string':
flatPacket._id = 1;
return stringSchema.encode(flatPacket);
case 'numeric':
flatPacket._id = 2;
return numericSchema.encode(flatPacket);
case 'binary':
flatPacket._id = 3;
return binarySchema.encode(flatPacket);
default:
throw new Error('unknown event name: ' + eventName);
}
}
}

class Decoder extends Emitter {
add (obj) {
if (typeof obj === 'string') {
this.parseJSON(obj);
} else {
this.parseBinary(obj);
}
}
parseJSON (obj) {
try {
let decoded = JSON.parse(obj);
this.emit('decoded', decoded);
} catch (e) {
this.emit('decoded', errorPacket);
}
}
parseBinary (obj) {
let view = new Uint8Array(obj);
let packetId = view[0];
try {
let packet = {
type: TYPES.EVENT
};
let decoded;
switch (packetId) {
case 1:
decoded = stringSchema.decode(obj);
packet.data = [ 'string', decoded.data ];
packet.nsp = decoded.nsp;
break;
case 2:
decoded = numericSchema.decode(obj);
packet.data = [ 'numeric', decoded.data ];
packet.nsp = decoded.nsp;
break;
case 3:
decoded = binarySchema.decode(obj);
packet.data = [ 'binary', decoded.data.buffer ];
packet.nsp = decoded.nsp;
break;
default:
throw new Error('unknown type');
}
this.emit('decoded', packet);
} catch (e) {
this.emit('decoded', errorPacket);
}
}
destroy () {}
}

exports.Encoder = Encoder;
exports.Decoder = Decoder;
55 changes: 55 additions & 0 deletions examples/custom-parsers/src/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

const express = require('express');
const app = express();
const server = require('http').createServer(app);
const path = require('path');
const port = process.env.PORT || 3000;

app.use(express.static(path.join(__dirname, '../public')));

server.listen(port, () => console.log('>>> http://localhost:' + port));

const io = require('socket.io');
const msgpackParser = require('socket.io-msgpack-parser');
const jsonParser = require('socket.io-json-parser');
const customParser = require('./custom-parser');

let server1 = io(3001, {});
let server2 = io(3002, {
parser: msgpackParser
});
let server3 = io(3003, {
parser: jsonParser
});
let server4 = io(3004, {
parser: customParser
});

let string = [];
let numeric = [];
let binary = new Buffer(1e3);
for (var i = 0; i < 1e3; i++) {
string.push('' + i);
numeric.push(i);
binary[i] = i;
}

server1.on('connect', onConnect(1000));
server2.on('connect', onConnect(2000));
server3.on('connect', onConnect(3000));
server4.on('connect', onConnect(4000));

function onConnect (delay) {
return function (socket) {
console.log('connect ' + socket.id);

setTimeout(() => {
socket.emit('string', string);
socket.emit('numeric', numeric);
socket.emit('binary', binary);
}, delay);

socket.on('disconnect', () => console.log('disconnect ' + socket.id));
};
}

15 changes: 15 additions & 0 deletions examples/custom-parsers/support/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

const path = require('path');

module.exports = {
entry: {
client1: './src/client1.js',
client2: './src/client2.js',
client3: './src/client3.js',
client4: './src/client4.js'
},
output: {
path: path.resolve(__dirname, '../public'),
filename: '[name].bundle.js'
}
};