diff --git a/README.md b/README.md
index 3b3d3ae47..eed1bfa02 100644
--- a/README.md
+++ b/README.md
@@ -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
});
```
@@ -67,6 +68,23 @@ httpServer.on('request', function (req, res) {
```
+Sending and receiving binary
+
+```html
+
+
+```
+
For more information on the client refer to the
[engine-client](http://github.com/learnboost/engine.io-client) repository.
@@ -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**
@@ -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.
- `Function`: optional, a callback executed when the message gets flushed out by the transport
- **Returns** `Socket` for chaining
- `close`
diff --git a/lib/server.js b/lib/server.js
index 82511e2b5..84607f2ec 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -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);
@@ -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 {
diff --git a/lib/transports/polling-xhr.js b/lib/transports/polling-xhr.js
index dde63aa98..8bc1d50fa 100644
--- a/lib/transports/polling-xhr.js
+++ b/lib/transports/polling-xhr.js
@@ -5,6 +5,7 @@
var Polling = require('./polling');
var Transport = require('../transport');
+var debug = require('debug')('engine:polling-xhr');
/**
* Module exports.
@@ -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
diff --git a/lib/transports/polling.js b/lib/transports/polling.js
index 7b40bb9bc..72d435319 100644
--- a/lib/transports/polling.js
+++ b/lib/transports/polling.js
@@ -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);
@@ -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 () {
@@ -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'); }
}
};
@@ -171,7 +177,7 @@ 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();
@@ -179,7 +185,9 @@ Polling.prototype.onData = function (data) {
}
self.onPacket(packet);
- });
+ };
+
+ parser.decodePayload(data, callback);
};
/**
@@ -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);
+ });
};
/**
diff --git a/lib/transports/websocket.js b/lib/transports/websocket.js
index 5582c58fc..59b8eb7b1 100644
--- a/lib/transports/websocket.js
+++ b/lib/transports/websocket.js
@@ -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');
+ });
});
}
};
diff --git a/test/jsonp.js b/test/jsonp.js
index 28ef65364..42e3935b7 100644
--- a/test/jsonp.js
+++ b/test/jsonp.js
@@ -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 () {
diff --git a/test/server.js b/test/server.js
index f5b81b247..7593b91af 100644
--- a/test/server.js
+++ b/test/server.js
@@ -63,7 +63,7 @@ describe('server', function () {
it('should send the io cookie', function (done) {
var engine = listen(function (port) {
request.get('http://localhost:%d/engine.io/default/'.s(port))
- .query({ transport: 'polling' })
+ .query({ transport: 'polling', b64: 1 })
.end(function (res) {
// hack-obtain sid
var sid = res.text.match(/"sid":"([^"]+)"/)[1];
@@ -76,7 +76,7 @@ describe('server', function () {
it('should send the io cookie custom name', function (done) {
var engine = listen({ cookie: 'woot' }, function (port) {
request.get('http://localhost:%d/engine.io/default/'.s(port))
- .query({ transport: 'polling' })
+ .query({ transport: 'polling', b64: 1 })
.end(function (res) {
var sid = res.text.match(/"sid":"([^"]+)"/)[1];
expect(res.headers['set-cookie'][0]).to.be('woot=' + sid);
@@ -833,6 +833,196 @@ describe('server', function () {
});
});
+ it('should arrive when binary data is sent as Int8Array (ws)', function (done) {
+ var binaryData = new Int8Array(5);
+ for (var i = 0; i < binaryData.length; i++) {
+ binaryData[i] = i;
+ }
+
+ var opts = { allowUpgrades: false, transports: ['websocket'] };
+ var engine = listen(opts, function(port) {
+ var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] })
+
+ engine.on('connection', function (conn) {
+ conn.send(binaryData);
+ });
+
+ socket.on('open', function () {
+ socket.on('message', function(msg) {
+ for (var i = 0; i < binaryData.length; i++) {
+ var num = msg.readInt8(i);
+ expect(num).to.be(i);
+ }
+ done();
+ });
+ });
+ });
+ });
+
+ it('should arrive when binary data is sent as Int32Array (ws)', function (done) {
+ var binaryData = new Int32Array(5);
+ for (var i = 0; i < binaryData.length; i++) {
+ binaryData[i] = (i + 100) * 9823;
+ }
+
+ var opts = { allowUpgrades: false, transports: ['websocket'] };
+ var engine = listen(opts, function(port) {
+ var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] })
+
+ engine.on('connection', function (conn) {
+ conn.send(binaryData);
+ });
+
+ socket.on('open', function () {
+ socket.on('message', function(msg) {
+ for (var i = 0, ii = 0; i < binaryData.length; i += 4, ii++) {
+ var num = msg.readInt32LE(i);
+ expect(num).to.be((ii + 100) * 9823);
+ }
+ done();
+ });
+ });
+ });
+ });
+
+ it('should arrive when binary data is sent as Int32Array, given as ArrayBuffer(ws)', function (done) {
+ var binaryData = new Int32Array(5);
+ for (var i = 0; i < binaryData.length; i++) {
+ binaryData[i] = (i + 100) * 9823;
+ }
+
+ var opts = { allowUpgrades: false, transports: ['websocket'] };
+ var engine = listen(opts, function(port) {
+ var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] })
+
+ engine.on('connection', function (conn) {
+ conn.send(binaryData.buffer);
+ });
+
+ socket.on('open', function () {
+ socket.on('message', function(msg) {
+ for (var i = 0, ii = 0; i < binaryData.length; i += 4, ii++) {
+ var num = msg.readInt32LE(i);
+ expect(num).to.be((ii + 100) * 9823);
+ }
+ done();
+ });
+ });
+ });
+ });
+
+ it('should arrive when binary data is sent as Buffer (ws)', function (done) {
+ var binaryData = Buffer(5);
+ for (var i = 0; i < binaryData.length; i++) {
+ binaryData.writeInt8(i, i);
+ }
+
+ var opts = { allowUpgrades: false, transports: ['websocket'] };
+ var engine = listen(opts, function(port) {
+ var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+
+ engine.on('connection', function (conn) {
+ conn.send(binaryData);
+ });
+
+ socket.on('open', function () {
+ socket.on('message', function(msg) {
+ for (var i = 0; i < binaryData.length; i++) {
+ var num = msg.readInt8(i);
+ expect(num).to.be(i);
+ }
+ done();
+ });
+ });
+ });
+ });
+
+ it('should arrive when binary data sent as Buffer (polling)', function (done) {
+ var binaryData = Buffer(5);
+ for (var i = 0; i < binaryData.length; i++) {
+ binaryData.writeInt8(i, i);
+ }
+
+ var opts = { allowUpgrades: false, transports: ['polling'] };
+ var engine = listen(opts, function(port) {
+ var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+
+ engine.on('connection', function (conn) {
+ conn.send(binaryData);
+ });
+
+ socket.on('open', function() {
+ socket.on('message', function(msg) {
+ for (var i = 0; i < binaryData.length; i++) {
+ var num = msg.readInt8(i);
+ expect(num).to.be(i);
+ }
+
+ done();
+ });
+ });
+ });
+ });
+
+ it('should arrive as ArrayBuffer if requested when binary data sent as Buffer (ws)', function (done) {
+ var binaryData = Buffer(5);
+ for (var i = 0; i < binaryData.length; i++) {
+ binaryData.writeInt8(i, i);
+ }
+
+ var opts = { allowUpgrades: false, transports: ['websocket'] };
+ var engine = listen(opts, function(port) {
+ var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['websocket'] });
+ socket.binaryType = 'arraybuffer';
+
+ engine.on('connection', function (conn) {
+ conn.send(binaryData);
+ });
+
+ socket.on('open', function() {
+ socket.on('message', function(msg) {
+ expect(msg instanceof ArrayBuffer).to.be(true);
+ var intArray = new Int8Array(msg);
+ for (var i = 0; i < binaryData.length; i++) {
+ expect(intArray[i]).to.be(i);
+ }
+
+ done();
+ });
+ });
+ });
+ });
+
+ it('should arrive as ArrayBuffer if requested when binary data sent as Buffer (polling)', function (done) {
+ var binaryData = Buffer(5);
+ for (var i = 0; i < binaryData.length; i++) {
+ binaryData.writeInt8(i, i);
+ }
+
+ var opts = { allowUpgrades: false, transports: ['polling'] };
+ var engine = listen(opts, function(port) {
+ var socket = new eioc.Socket('ws://localhost:%d'.s(port), { transports: ['polling'] });
+ socket.binaryType = 'arraybuffer';
+
+ engine.on('connection', function (conn) {
+ conn.send(binaryData);
+ });
+
+ socket.on('open', function() {
+ socket.on('message', function(msg) {
+ expect(msg instanceof ArrayBuffer).to.be(true);
+ var intArray = new Int8Array(msg);
+ for (var i = 0; i < binaryData.length; i++) {
+ expect(intArray[i]).to.be(i);
+ }
+
+ done();
+ });
+ });
+ });
+ });
+
+
it('should trigger a flush/drain event', function(done){
var engine = listen({ allowUpgrades: false }, function(port){
engine.on('connection', function(socket){