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

Binary support #218

Merged
merged 9 commits into from Feb 19, 2014
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 22 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 @@ -67,6 +68,23 @@ httpServer.on('request', function (req, res) {
</script>
```

Sending and receiving binary

```html
<script src="/path/to/engine.io.js"></script>
<script>
var socket = new eio.Socket('ws://localhost/');
socket.binaryType = 'blob'; // receives Blob instead of ArrayBuffer (default)
socket.on('open', function () {
socket.send(new Int8Array(5));
socket.on('message', function (data) {
// data instanceof Blob => true when receiving binary
});
socket.on('close', function () { });
});
</script>
```

For more information on the client refer to the
[engine-client](http://github.com/learnboost/engine.io-client) repository.

Expand Down Expand Up @@ -220,7 +238,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
- `error`
- Fired when an error occurs.
- **Arguments**
Expand Down Expand Up @@ -253,9 +271,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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about Blob?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and BlobBuilder

- `Function`: optional, a callback executed when the message gets flushed out by the transport
- **Returns** `Socket` for chaining
- `close`
Expand Down
10 changes: 10 additions & 0 deletions lib/server.js
Expand Up @@ -205,6 +205,11 @@ Server.prototype.handshake = function(transport, req){

try {
var transport = new transports[transport](req);
if (req.query && req.query.b64) {
transport.supportsBinary = false;
} else {
transport.supportsBinary = true;
}
}
catch (e) {
sendErrorMessage(req.res, Server.errors.BAD_REQUEST);
Expand Down Expand Up @@ -282,6 +287,11 @@ Server.prototype.onWebSocket = function(req, socket){
} else {
debug('upgrading existing transport');
var transport = new transports[req.query.transport](req);
if (req.query && req.query.b64) {
transport.supportsBinary = false;
} else {
transport.supportsBinary = true;
}
this.clients[id].maybeUpgrade(transport);
}
} else {
Expand Down
11 changes: 9 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 Down Expand Up @@ -36,9 +37,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';
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
25 changes: 18 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 = 'application/octet-stream' == req.headers['content-type'];

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,11 @@ 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 +163,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 +177,17 @@ 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);
});
};

parser.decodePayload(data, callback);
};

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

this.write(parser.encodePayload(packets));
var self = this;
parser.encodePayload(packets, this.supportsBinary, function(data) {
self.write(data);
});
};

/**
Expand Down
17 changes: 9 additions & 8 deletions lib/transports/websocket.js
Expand Up @@ -83,15 +83,16 @@ WebSocket.prototype.onData = function (data) {
*/

WebSocket.prototype.send = function (packets) {
var self = this;
for (var i = 0, l = packets.length; i < l; i++) {
var data = parser.encodePacket(packets[i]);
debug('writing "%s"', data);
this.writable = false;
var self = this;
this.socket.send(data, function (err){
if (err) return self.onError('write error', err.stack);
self.writable = true;
self.emit('drain');
parser.encodePacket(packets[i], this.supportsBinary, function(data) {
debug('writing "%s"', data);
self.writable = false;
self.socket.send(data, function (err){
if (err) return self.onError('write error', err.stack);
self.writable = true;
self.emit('drain');
});
});
}
};
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