Skip to content

Commit

Permalink
Binary support
Browse files Browse the repository at this point in the history
  • Loading branch information
Tony Kovanen committed Jan 31, 2014
1 parent 461918b commit dbe140c
Show file tree
Hide file tree
Showing 9 changed files with 258 additions and 14 deletions.
11 changes: 8 additions & 3 deletions README.md
Expand Up @@ -19,6 +19,7 @@ var engine = require('engine.io')

server.on('connection', function (socket) {
socket.send('utf 8 string');
socket.send(new Buffer([0, 1, 2, 3, 4, 5])); // binary data
});
```

Expand Down Expand Up @@ -60,7 +61,10 @@ httpServer.on('request', function (req, res) {
<script src="/path/to/engine.io.js"></script>
<script>
var socket = new eio.Socket('ws://localhost/');
// by default binary type is: socket.binaryType = 'arraybuffer'
socket.binaryType = 'blob';
socket.on('open', function () {
socket.send(new Int8Array(15));
socket.on('message', function (data) { });
socket.on('close', function () { });
});
Expand Down Expand Up @@ -220,7 +224,7 @@ A representation of a client. _Inherits from EventEmitter_.
- `message`
- Fired when the client sends a message.
- **Arguments**
- `String`: Unicode string
- `String` or `Buffer`: Unicode string or Buffer with binary contents

This comment has been minimized.

Copy link
@plievone

plievone Mar 21, 2014

After this change, what is the best practice to avoid clients killing the server by sending binary data when string is expected and vice versa? I guess checking typeof message === 'string' always, for example? Perhaps it would be better to mention this somewhere.

- `error`
- Fired when an error occurs.
- **Arguments**
Expand Down Expand Up @@ -253,9 +257,10 @@ A representation of a client. _Inherits from EventEmitter_.
##### Methods

- `send`:
- Sends a message, performing `message = toString(arguments[0])`.
- Sends a message, performing `message = toString(arguments[0])` unless
sending binary data, which is sent as is.
- **Parameters**
- `String`: a string or any object implementing `toString()`, with outgoing data
- `String``Buffer` | `ArrayBuffer` | `ArrayBufferView`: a string or any object implementing `toString()`, with outgoing data, or a Buffer or ArrayBuffer with binary data. Also any ArrayBufferView can be sent as is.
- `Function`: optional, a callback executed when the message gets flushed out by the transport
- **Returns** `Socket` for chaining
- `close`
Expand Down
1 change: 1 addition & 0 deletions lib/server.js
Expand Up @@ -205,6 +205,7 @@ Server.prototype.handshake = function(transport, req){

try {
var transport = new transports[transport](req);
if (req.query && req.query.b64) transport.supportsBinary = false;
}
catch (e) {
sendErrorMessage(req.res, Server.errors.BAD_REQUEST);
Expand Down
6 changes: 6 additions & 0 deletions lib/transports/flashsocket.js
Expand Up @@ -44,6 +44,12 @@ FlashSocket.prototype.name = 'flashsocket';

FlashSocket.prototype.supportsFraming = true;

/**
* Supports sending binary data.
* @api public
*/
FlashSocket.prototype.supportsBinary = false;

/**
* Listens for new configuration changes of the Manager
* this way we can enable and disable the flash server.
Expand Down
8 changes: 8 additions & 0 deletions lib/transports/polling-jsonp.js
Expand Up @@ -31,6 +31,14 @@ function JSONP (req) {

JSONP.prototype.__proto__ = Polling.prototype;

/**
* Supports sending binary data.
* @api public
*/

JSONP.prototype.supportsBinary = false;


/**
* Handles incoming data.
* Due to a bug in \n handling by browsers, we expect a escaped string.
Expand Down
18 changes: 16 additions & 2 deletions lib/transports/polling-xhr.js
Expand Up @@ -5,6 +5,7 @@

var Polling = require('./polling');
var Transport = require('../transport');
var debug = require('debug')('engine:polling-xhr');

/**
* Module exports.
Expand All @@ -28,6 +29,13 @@ function XHR(req){

XHR.prototype.__proto__ = Polling.prototype;

/**
* Supports sending binary data.
* @api public
*/

XHR.prototype.supportsBinary = true;

/**
* Frames data prior to write.
*
Expand All @@ -36,9 +44,15 @@ XHR.prototype.__proto__ = Polling.prototype;

XHR.prototype.doWrite = function(data){
// explicit UTF-8 is required for pages not served under utf
var isString = typeof data === 'string' || data === undefined;
var contentType = (isString)
? 'text/plain; charset=UTF-8'
: 'application/octet-stream';
var contentLength = '' + ((isString) ? Buffer.byteLength(data) : data.length);

var headers = {
'Content-Type': 'text/plain; charset=UTF-8',
'Content-Length': Buffer.byteLength(data)
'Content-Type': contentType,
'Content-Length': contentLength
};

// prevent XSS warnings on IE
Expand Down
21 changes: 14 additions & 7 deletions lib/transports/polling.js
Expand Up @@ -118,14 +118,16 @@ Polling.prototype.onDataRequest = function (req, res) {
this.onError('data request overlap from client');
res.writeHead(500);
} else {
var isBinary = req.headers['content-type'] === 'application/octet-stream';

this.dataReq = req;
this.dataRes = res;

var chunks = ''
var chunks = (isBinary) ? new Buffer(0) : ''
, self = this

function cleanup () {
chunks = '';
chunks = (isBinary) ? new Buffer(0) : '';
req.removeListener('data', onData);
req.removeListener('end', onEnd);
req.removeListener('close', onClose);
Expand All @@ -138,7 +140,8 @@ Polling.prototype.onDataRequest = function (req, res) {
};

function onData (data) {
chunks += data;
if (typeof data === 'string') chunks += data;
else chunks = Buffer.concat([chunks, data]);
};

function onEnd () {
Expand All @@ -157,7 +160,7 @@ Polling.prototype.onDataRequest = function (req, res) {
req.on('close', onClose);
req.on('data', onData);
req.on('end', onEnd);
req.setEncoding('utf8');
if (!isBinary) req.setEncoding('utf8');
}
};

Expand All @@ -171,15 +174,18 @@ Polling.prototype.onDataRequest = function (req, res) {
Polling.prototype.onData = function (data) {
debug('received "%s"', data);
var self = this;
parser.decodePayload(data, function(packet){
var callback = function(packet) {
if ('close' == packet.type) {
debug('got xhr close packet');
self.onClose();
return false;
}

self.onPacket(packet);
});
};

if (typeof data === 'string') parser.decodePayload(data, callback);
else parser.decodePayloadAsBinary(data, callback);
};

/**
Expand All @@ -197,7 +203,8 @@ Polling.prototype.send = function (packets) {
this.shouldClose = null;
}

this.write(parser.encodePayload(packets));
if (!this.supportsBinary) this.write(parser.encodePayload(packets));
else this.write(parser.encodePayloadAsBinary(packets));
};

/**
Expand Down
6 changes: 6 additions & 0 deletions lib/transports/websocket.js
Expand Up @@ -63,6 +63,12 @@ WebSocket.prototype.handlesUpgrades = true;

WebSocket.prototype.supportsFraming = true;

/**
* Supports sending binary data.
* @api public
*/
WebSocket.prototype.supportsBinary = true;

/**
* Processes the incoming data.
*
Expand Down
21 changes: 21 additions & 0 deletions test/jsonp.js
Expand Up @@ -115,6 +115,27 @@ describe('JSONP', function () {
});
});
});

it('should arrive from server to client and back with binary data (pollingJSONP)', function(done) {
var binaryData = new Buffer(5);
for (var i = 0; i < 5; i++) binaryData[i] = i;
var engine = listen( { allowUpgrades: false, transports: ['polling'] }, function (port) {
var socket = new eioc.Socket('ws://localhost:' + port, { transports: ['polling'], forceJSONP: true, upgrade: false});
engine.on('connection', function (conn) {
conn.on('message', function (msg) {
conn.send(msg);
});
});

socket.on('open', function() {
socket.send(binaryData);
socket.on('message', function (msg) {
for (var i = 0; i < msg.length; i++) expect(msg[i]).to.be(i);
done();
});
});
});
});
});

describe('close', function () {
Expand Down

0 comments on commit dbe140c

Please sign in to comment.