I'm not 100% sure but from what I read when I send a blob (binary data) over websocket, the blob does not contain any file information. (Also the official specification states that wesockets only send the raw binary)
- the filesize
- the mimetype
- user info (explain later)
I'm using
Testing:
Sending directly the blob from an input file.
ws.send(this.files[0]) //this should already contain the info
Creating a new blob with the native JavaScript API from file setting the proper mimetype.
ws.send(new Blob([this.files[0]],{type:this.files[0].type})); //also this
on both sides you can get only the effective blob without any other information.
Is it possible to append let's say a 4kb predefined JSON data converted also to binary that contains important information like the mimetype and the filesize, and then just split off the 4kb when needed?
{"mime":"txt/plain","size":345}____________4KB_REST_OF_THE_BINARY
OR
ws.send({"mime":"txt\/plain","size":345})
ws.send(this.files[0])
Even if the first one is the worst solution ever it would allow me to send everything at one time.
The second one has a big problem:
it's a chat that allows to send also files like documents, images, music videos.
I could write some sort of handshaking system when sending the file/user info before I send the binary data.
BUT
if another person sends also a file, as it's async, the handshaking system has no chance to determine which file is the right one for the correct user and mimetype.
So how do you properly send a binary file in a multiuser async environment?
I know I can convert to base64 but that's 30% bigger.
btw. Totally disappointed with Apple... while Chrome shows every binary data properly, my iOS devices are not able to handle blob's, only images will show in blob or base64 format, not even a simple txt file. Basically only a <img>
tag can read dynamic files.
How everything works (now):
- user sends a file
- nodejs gets the binary data, also user info... but not mimetype, filename, size.
- nodejs broadcasts the raw binary file to all the users.(can't specify user & file info)
- clients create a bloburl (who send that? XD).
EDIT
what I have now:
client 1 (sends a file)CHROME
fileInput.addEventListener('change',function(e){
var file=this.files[0];
ws.send(new Blob([file],{
type:file.type //<- SET MIMETYPE
}));
//file.size
},false);
note: file
is already a blob ... but this is how you would normally create a new blob specifying the mimetype.
server (broadcasts the binary data to the other clients)NODEJS
aaaaaand the mimetype is gone...
ws.addListener('message',function(binary){
var b=0,c=wss.clients.length;
while(b<c){
wss.clients[b++].send(binary)
}
});
client 2 (receives the binary)CHROME
ws.addEventListener('message',function(msg){
var blob=new Blob([msg.data],{
type:'application/octet-stream' //<- LOST
});
var file=window.URL.createObjectURL(blob);
},false);
note: m.data
is already a blob ... but this is how you would normally create a new blob specifying the mimetype which is lost.
In client 2 I need the mimetype and naturally I also need the info about the user, which can be retrieved from client 1 or the server (not a good choice)...
I'm not 100% sure but from what I read when I send a blob (binary data) over websocket, the blob does not contain any file information. (Also the official specification states that wesockets only send the raw binary)
- the filesize
- the mimetype
- user info (explain later)
I'm using https://github./websockets/ws
Testing:
Sending directly the blob from an input file.
ws.send(this.files[0]) //this should already contain the info
Creating a new blob with the native JavaScript API from file setting the proper mimetype.
ws.send(new Blob([this.files[0]],{type:this.files[0].type})); //also this
on both sides you can get only the effective blob without any other information.
Is it possible to append let's say a 4kb predefined JSON data converted also to binary that contains important information like the mimetype and the filesize, and then just split off the 4kb when needed?
{"mime":"txt/plain","size":345}____________4KB_REST_OF_THE_BINARY
OR
ws.send({"mime":"txt\/plain","size":345})
ws.send(this.files[0])
Even if the first one is the worst solution ever it would allow me to send everything at one time.
The second one has a big problem:
it's a chat that allows to send also files like documents, images, music videos.
I could write some sort of handshaking system when sending the file/user info before I send the binary data.
BUT
if another person sends also a file, as it's async, the handshaking system has no chance to determine which file is the right one for the correct user and mimetype.
So how do you properly send a binary file in a multiuser async environment?
I know I can convert to base64 but that's 30% bigger.
btw. Totally disappointed with Apple... while Chrome shows every binary data properly, my iOS devices are not able to handle blob's, only images will show in blob or base64 format, not even a simple txt file. Basically only a <img>
tag can read dynamic files.
How everything works (now):
- user sends a file
- nodejs gets the binary data, also user info... but not mimetype, filename, size.
- nodejs broadcasts the raw binary file to all the users.(can't specify user & file info)
- clients create a bloburl (who send that? XD).
EDIT
what I have now:
client 1 (sends a file)CHROME
fileInput.addEventListener('change',function(e){
var file=this.files[0];
ws.send(new Blob([file],{
type:file.type //<- SET MIMETYPE
}));
//file.size
},false);
note: file
is already a blob ... but this is how you would normally create a new blob specifying the mimetype.
server (broadcasts the binary data to the other clients)NODEJS
aaaaaand the mimetype is gone...
ws.addListener('message',function(binary){
var b=0,c=wss.clients.length;
while(b<c){
wss.clients[b++].send(binary)
}
});
client 2 (receives the binary)CHROME
ws.addEventListener('message',function(msg){
var blob=new Blob([msg.data],{
type:'application/octet-stream' //<- LOST
});
var file=window.URL.createObjectURL(blob);
},false);
note: m.data
is already a blob ... but this is how you would normally create a new blob specifying the mimetype which is lost.
In client 2 I need the mimetype and naturally I also need the info about the user, which can be retrieved from client 1 or the server (not a good choice)...
Share Improve this question edited Jun 18, 2024 at 14:06 Jason Aller 3,65228 gold badges41 silver badges39 bronze badges asked Apr 2, 2015 at 14:08 coccococco 16.7k7 gold badges64 silver badges77 bronze badges 4- Have you thought about using BinaryPack? github./binaryjs/node-binarypack github./binaryjs/js-binarypack – Ben Commented Apr 8, 2015 at 17:24
- sorry, no, i'm trying to achieve it with ws (websockets) – cocco Commented Apr 8, 2015 at 17:52
- Right, BinaryPack is what BinaryJS uses to stream files, etc. over websockets binaryjs. – Ben Commented Apr 8, 2015 at 18:33
- i mean i'm trying to get this to work without another external lib. i searching for a proper way using this type of binary sending system ...github./websockets/ws – cocco Commented Apr 8, 2015 at 19:39
2 Answers
Reset to default 4 +50You're a bit out of luck with this because Node doesn't support the Blob
interface and so any data you send or receive in Binary with Node is just Binary. You would have to have something that knew how to interpret a Blob object.
Here's an idea, and let me know if this works. Reading through the documentation for websockets\ws
it says it supports sending and receiving ArrayBuffers
. Which means you can use TypedArrays.
Here's where it gets nasty. You set a certain fixed n
number of bytes at the beginning of every TypedArray
to signal the mime type encoded in utf8 or what have you, and the rest of your TypedArray
contains your file's bytes.
I would remend using UInt8Array
because utf8
characters are 8 bits long and your text will probably be readable when encoded that way. As for the file bits you'll probably just end up writing those down somewhere and appending an ending to it.
Also note, this method of interpretation works both ways whether from Node or in the Browser.
This solution is really just a form of type casting and you might get some unexpected results. The fixed length of your mime type field is crucial.
Here it is illustrated. Copy, paste, set the image file to whatever you want and then run that. You'll see the mime type I set pop out.
var fs = require('fs');
//https://stackoverflow./questions/8609289/convert-a-binary-nodejs-buffer-to-javascript-arraybuffer
function toUint8Array(buffer) {
var ab = new ArrayBuffer(buffer.length);
var array = new Uint8Array(ab);
for (var i = 0; i < buffer.length; ++i) {
array[i] = buffer[i];
}
return array;
}
//data is a raw Buffer object
fs.readFile('./ducklings.png', function (err, data) {
var mime = new Buffer('image/png');
var allBuffed = Buffer.concat([mime, data]);
var array = toUint8Array(allBuffed);
var mimeBytes = array.subarray(0,9); //number of characters in mime Buffer
console.log(String.fromCharCode.apply(null, mimeBytes));
});
Here's how you do it on the client side:
SOLUTION A: GET A PACKAGE
Get buffer, an implementation of Node's Buffer API for browsers. The solution to concatenate Byte buffers will work exactly as before. You can append fields like To: and what not as well. The way you format your headers in order to best serve your clients will be an evolving process I'm sure.
SOLUTION B: OLD SCHOOL
STEP 1: Convert your Blob to an ArrayBuffer
Notes: How to convert a String to an ArrayBuffer
var fr = new FileReader();
fr.addEventListener('loadend', function () {
//Asynchronous action in part 2.
var message = concatenateBuffers(headerStringAsBuffer, fr.result);
ws.send(message);
});
fr.readAsArrayBuffer(blob);
STEP 2: Concatenate ArrayBuffers
function concatenateBuffers(buffA, buffB) {
var byteLength = buffA.byteLength + buffB.byteLength;
var resultBuffer = new ArrayBuffer(byteLength);
//wrap ArrayBuffer in a typedArray/view
var resultView = new Uint8Array(resultBuffer);
var viewA = new Uint8Array(resultBuffer);
var viewB = new Uint8Array(resultBuffer);
//Copy 8 bit integers AKA Bytes
resultView.set(viewA);
resultView.set(viewB, viewA.byteLength);
return resultView.buffer
}
STEP 3: Receive and Reblob
I'm not going to repeat how to convert the concatenated String bytes back into a string because I've done it in the server example, but for turning the file bytes into a blob of your mime type is fairly simple.
new Blob(buffer.slice(offset, buffer.byteLength), {type: mimetype});
This Gist by robnyman
goes into further details on how you would use an image transmitted via XHR, put it into localstorage, and use it in an image tag on your page.
I liked @Breedly's idea of prepending a fixed length byte array to indicate mime type of the ArrayBuffer so I created this npm package that I use when dealing with websockets but maybe others' might find it useful.
Example usage
const {
arrayBufferWithMime,
arrayBufferMimeDecouple
} = require('arraybuffer-mime')
// some image array buffer
const uint8 = new Uint8Array(1)
uint8[0] = 1
const ab = uint8.buffer
const mime = 'image/png'
const abWithMime = arrayBufferWithMime(ab, mime)
const {mime, arrayBuffer} = arrayBufferMimeDecouple(abWithMime)
console.log(mime) // "image/png"
console.log(arrayBuffer) // ArrayBuffer