Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to send JSON object with ArrayBuffer to websocket?

I'm trying to simply upload a file to a node.js server.

To do this, I'm using the file API and readAsArrayBuffer. Here's the code that's called when the input file "change" event is fired, along with some hepler functions (and I'm using the COBY library for socket sending and other event setup, the binaryType is set to arraybuffer):

COBY.events = {
  "file": (e) => {
       files = Array.from(e.target.files);
       startReadingFile(files[0]);
   }
};

function startReadingFile(file) {
   readFileFrom(file, 0, chunkSize, (array, r) => {
       COBY.socketSend({"start uploading a file": {
           name:file.name,
           type:file.type,
           size:file.size,
           data:(array)
       }});
       console.log("didnt something?", r, Array.from(r));
   });
}

function readFileFrom(file, start, end, callback) {
   var sliced = file.slice(start, end);
   var reader = new FileReader();
   reader.onload = (event) => {
       result = (event.target.result);
       var arr = Array.from(new Uint8Array(result));
       if(callback && callback.constructor == Function) {
           currentPosition = end;
           callback(arr, result);
       }
   }
   reader.readAsArrayBuffer(sliced);
}

And on my server (I'm using the coby-node library which is the node.js version of the COBY client library):

var coby = require("coby-node");
var fs = require("fs");
var files = {};
var kilobyte = 1024;

function makeBigFile(name, number) {
    var test = fs.createWriteStream("./" + name, {flags: "w+"});
    console.log("OK?",name);
    [...Array(number)].forEach((x, i) => test.write(i+"\n"));
}

//makeBigFile("OKthere.txt", 12356);
coby.startAdanServer({
    onOpen:(cs) => {
        console.log("something just connected! Let's send it something");
     //   cs.send({"Whoa man !":1234});
        cs.send({asdf :3456789});
    },

    onAdanMessage: (cs, msg) => {
     //   console.log("HMM weird just got this message...", msg);
    },

    adanFunctions: {
        "do something important": (cs, data) => {
            console.log("I just got some message:", data);
            console.log(cs.server.broadcast);
            cs.server.broadcast({"look out":"here I am"}, {
                current: cs
            });

            cs.send({message:"OK I did it I think"});
        },
        "start uploading a file": (cs, data) => {
            if(data.data && data.data.constructor == Array) {
                var name = data["name"]
                files[name] = {
                    totalSize:data.size,
                    downloadedSize:0
                };
                
                files[name]["handler"] = fs.createWriteStream("./" + data.name, {
                    flags: "w+"
                });

                files[name]["handler"].on("error", (err) => {
                    console.log("OY vay", err);
                });
                cs.send({"ok dude I need more": {
                    name:name,
                    bytePositionToWriteTo:0,
                    totalLength:files[name]["totalSize"]
                }});
            }
        },
        "continue uploading file": (cs, data) => {
      
            var name = data.name;
            if(files[name]) {
                var handler = files[name]["handler"];

                var uint = Uint8Array.from(data.bufferArray);
                var myBuffer = Buffer.from(uint.buffer);
                var start = data.startPosition || 0,
                    end = myBuffer.byteLength + start;

                files[name].downloadedSize += myBuffer.byteLength;

                
                if(files[name].downloadedSize < files[name]["totalSize"]) {
                 
                    cs.send({"ok dude I need more": {
                        name:name,
                        bytePositionToWriteTo:files[name].downloadedSize,
                        totalLength:files[name]["totalSize"]
                    }});
                    try {
                        handler.write(myBuffer);
                    } catch(e) {
                        console.log("writing error: ", e);
                    }
                } else {
                    end = files[name]["totalSize"];
                    handler.write(myBuffer);
                    console.log("finished, I think?");
                    console.log(files[name].downloadedSize, "total: ", files[name]["totalSize"]);
                    console.log("   start: ", start, "end: ", end);
                }
                
                
            }
        }
    },
    intervalLength:1000
});

function startUnity() {
    coby.cmd(`./MyUnity/Editor/Unity.exe -batchmode -quit -projectPath "./MyUnity/totally empty" -executeMethod COBY.Start -logfile ./new123folder/wow.txt`, {
        onData:(data) => {
            console.log(data);
        },
        onError:(data) => {
            console.log(data);
        },
        onExit:(exitCode) => {
            console.log("exitted with code: " + exitCode);
        },
        onFail:(msg) => {
            console.log(msg);
        }
    });  
}

So far this actualy uploads a file, you can test it with npm install coby-node, but its taking a lot more time because I'm JSON.stringifing an Array.from(new Uint8Array(/* the ArrayBuffer result */)) and then on the server side I'm re-JSON parsing it, but how do I just send the actual ArrayBuffer to the websocket? I want to send the arraybuffer along with the name of the file and other data, so I want to include it in a JSON object, but when I JSON.stringify(/an ArrayBuffer/) the result is always [], and IDK how to send an ArrayBuffer with my own data ???

Also it seems to be taking a lot of time with Array.from(new Uint8Array(arrayBufer)) do you think readAsDataURL would be faster?

I AM able to, btw, send an arraybuffer by ITSELF via websocket with binayType="arraybuffer", but how do I include the filename with it??

like image 350
Yaakov5777 Avatar asked Feb 22 '19 05:02

Yaakov5777


People also ask

Can we send JSON data in WebSocket connection?

To create a WebSocket and open the connection to FME Server use getWebSocketConnection method. The result is an open WebSocket you can use in your application to communicate with FME Server. This example sends a basic JSON object as a string to the server, and displays the response from the server.

Can WebSocket send binary data?

WebSockets support sending binary messages, too. To send binary data, one can use either Blob or ArrayBuffer object. Instead of calling the send method with string, you can simply pass an ArrayBuffer or a Blob .

When using WebSocket send() how do you know when the data sent has been transmitted to the network?

The only way to know the client received the webSocket message for sure is to have the client send your own custom message back to the server to indicate you received it and for you to wait for that message on the server.

What is ArrayBuffer in node JS?

The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer. It is an array of bytes, often referred to in other languages as a "byte array".


1 Answers

So you want to send structured binary data. Most generic binary formats use a type-length-value encoding (ASN.1 or Nimn are good examples).

In your case, you might want a simpler scheme because you have fixed fields: "name", "type", "size", "data". You already know their types. So you could got with just length-value. The idea is that each field in your byte stream begins with one or two bytes containing the length of the value. The parser will therefore know how many bytes to read before the next value, removing the need for delimiters.

Let's say you want to encode this:

{
  name: "file.txt",
  type: "text/plain",
  size: 4834,
  data: <an ArrayBuffer of length 4834>
}

The "size" field is actually going to be useful, because all other lengths fit in a single byte but the content length does not.

So you make a new ArrayBuffer with the bytes:

08 (length of the file name)
66 69 6c 65 2e 74 78 74 (the string "file.txt")
0a (length of the content type)
74 65 78 74 2f 70 6c 61 69 6e (the string "text/plain")
02 (you need two bytes to represent the size)
12 e2 (the size, 4834 as an unsigned int16)
... and finally the bytes of the content

To do that with client-side JavaScript is only slightly harder than with node.js Buffers. First, you need to compute the total length of the ArrayBuffer you'll need to send.

// this gives you how many bytes are needed to represent the size
let sizeLength = 1
if (file.size > 0xffff)
  sizeLength = 4
else if (file.size > 0xff)
  sizeLength = 2

const utf8 = new TextEncoder()
const nameBuffer = utf8.encode(file.name)
const typeBuffer = utf8.encode(type)

const length = file.size + sizeLength
  + nameBuffer.length + typeBuffer.length + 3

const buffer = new Uint8Array(length)

Now you just need to fill the buffer.

Let's start with the lengths and copy the strings:

let i = 0
buffer[i] = nameBuffer.length
buffer.set(i += 1, nameBuffer)
buffer[i += nameBuffer.length] = typeBuffer.length
buffer.set(i += 1, typeBuffer)
buffer[i += typeBuffer.length] = sizeLength

Then the file size must be written as the appropriate Int type:

const sizeView = new DataView(buffer)
sizeView[`setUInt${sizeLength*8}`](i += 1, file.size)

Finally, copy the data:

buffer.set(array, i + sizeLength) // array is your data
like image 109
Touffy Avatar answered Sep 30 '22 18:09

Touffy