12

I'm developing a Web application to send images, videos, etc. to two monitors from an admin interface. I'm using ws in Node.js for the server side. I've implemented selecting images available on the server and external URLs and sending them to the clients, but I also wanted to be able to directly send images selected from the device with a file input. I managed to do it using base64 but I think it's pretty inefficient.

Currently I send a stringified JSON object containing the client to which the resource has to be sent, the kind of resource and the resource itself, parse it in the server and send it to the appropriate client. I know I can set the Websocket binaryType to blob and just send the File object, but then I'd have no way to tell the server which client it has to send it to. I tried using typeson and BSON to accomplish this, but it didn't work.

Are there any other ways to do it?

user2859982
  • 223
  • 1
  • 2
  • 4
  • Take a look on: https://www.npmjs.com/package/socket.io-file – calbertts May 13 '18 at 12:41
  • 1
    @calbertts Can I use it with [ws](https://github.com/websockets/ws) instead of Socket.io? – user2859982 May 13 '18 at 13:51
  • Not sure about it, but you can check the code to see how it works, remember Socket.IO is built on to of Websockets implementation. – calbertts May 13 '18 at 20:10
  • I took a look at it and I think it won't work for what I'm trying to achieve.I need to transfer the image data directly through the server, but without uploading and storing it. – user2859982 May 13 '18 at 21:33

3 Answers3

13

You can send raw binary data through the WebSocket.

It's quite easy to manage.

One option is to prepend a "magic byte" (an identifier that marks the message as non-JSON). For example, prepend binary messages with the B character.

All the server has to do is test the first character before collecting the binary data (if the magic byte isn't there, it's probably the normal JSON message).

A more serious implementation will attach a header after the magic byte (i.e., file name, total length, position of data being sent etc').

This allows the upload to be resumed on disconnections (send just the parts that weren't acknowledged as received.

Your server will need to split the data into magic byte, header and binary_data before processing. but it's easy enough to accomplish.

Myst
  • 18,516
  • 2
  • 45
  • 67
9

Hope this help someone. According to socket.io document you can send either string, Buffer or mix both of them

On Client side:

function uploadFile(e, socket, to) {
  let file = e.target.files[0];

  if (!file) {
    return
  }
  if (file.size > 10000000) {
    alert('File should be smaller than 1MB')
    return
  }

  var reader = new FileReader();
  var rawData = new ArrayBuffer();

  reader.onload = function (e) {
    rawData = e.target.result;
    socket.emit("send_message", {
      type: 'attachment',
      data: rawData
    } , (result) => {
      alert("Server has received file!")
    });
    alert("the File has been transferred.")
  }

  reader.readAsArrayBuffer(file);
}

on server side:

socket.on('send_message', async (data, cb) => {
  if (data.type == 'attachment') {
      console.log('Found binary data')
      cb("Received file successfully.")
      return
    }
// Process other business...
});
Travis Nguyen
  • 115
  • 1
  • 6
8

I am using pure WebSocket without io, where you cannot mix content - either String or Binary. Then my working solution is like this:

CLIENT:

    import { serialize } from 'bson';
    import { Buffer } from 'buffer';

    const reader = new FileReader();
    let rawData = new ArrayBuffer();
    ws = new WebSocket(...)
    reader.onload = (e) => {
      rawData = e.target.result;
      const bufferData = Buffer.from(rawData);
      const bsonData = serialize({  // whatever js Object you need
        file: bufferData,
        route: 'TRANSFER',
        action: 'FILE_UPLOAD',
      });
      ws.send(bsonData);
    }

Then on Node server side, the message is catched and parsed like this:

        const dataFromClient = deserialize(wsMessage, {promoteBuffers: true}) // edited
        fs.writeFile(
          path.join('../server', 'yourfiles', 'yourfile.txt'),
          dataFromClient.file, // edited
          'binary',
          (err) => {
            console.log('ERROR!!!!', err);
          }
        );

The killer is promoteBuffer option in deserialize function.

Fide
  • 1,127
  • 8
  • 7