diff --git a/examples/custom-parsers/README.md b/examples/custom-parsers/README.md new file mode 100644 index 0000000000..be00e9f3b8 --- /dev/null +++ b/examples/custom-parsers/README.md @@ -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 diff --git a/examples/custom-parsers/package.json b/examples/custom-parsers/package.json new file mode 100644 index 0000000000..be724174f3 --- /dev/null +++ b/examples/custom-parsers/package.json @@ -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" + } +} diff --git a/examples/custom-parsers/public/index.html b/examples/custom-parsers/public/index.html new file mode 100644 index 0000000000..a0cb336744 --- /dev/null +++ b/examples/custom-parsers/public/index.html @@ -0,0 +1,13 @@ + + + + + Socket.IO custom parsers + + + + + + + + diff --git a/examples/custom-parsers/src/client1.js b/examples/custom-parsers/src/client1.js new file mode 100644 index 0000000000..f73f775c64 --- /dev/null +++ b/examples/custom-parsers/src/client1.js @@ -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)); diff --git a/examples/custom-parsers/src/client2.js b/examples/custom-parsers/src/client2.js new file mode 100644 index 0000000000..a98af8b1d2 --- /dev/null +++ b/examples/custom-parsers/src/client2.js @@ -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)); diff --git a/examples/custom-parsers/src/client3.js b/examples/custom-parsers/src/client3.js new file mode 100644 index 0000000000..9bfb6c68dd --- /dev/null +++ b/examples/custom-parsers/src/client3.js @@ -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)); diff --git a/examples/custom-parsers/src/client4.js b/examples/custom-parsers/src/client4.js new file mode 100644 index 0000000000..b44168ac17 --- /dev/null +++ b/examples/custom-parsers/src/client4.js @@ -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)); diff --git a/examples/custom-parsers/src/custom-parser.js b/examples/custom-parsers/src/custom-parser.js new file mode 100644 index 0000000000..74c6b42ce6 --- /dev/null +++ b/examples/custom-parsers/src/custom-parser.js @@ -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; diff --git a/examples/custom-parsers/src/server.js b/examples/custom-parsers/src/server.js new file mode 100644 index 0000000000..3b20d1703d --- /dev/null +++ b/examples/custom-parsers/src/server.js @@ -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)); + }; +} + diff --git a/examples/custom-parsers/support/webpack.config.js b/examples/custom-parsers/support/webpack.config.js new file mode 100644 index 0000000000..34dcc13308 --- /dev/null +++ b/examples/custom-parsers/support/webpack.config.js @@ -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' + } +};