1

I'm having a hard time finding documentation or tutorial in order to send a file throught a websocket. Here is my JS:

['dragleave', 'drop'].forEach(event_name =>
document.addEventListener(event_name, function(e) {
  e.preventDefault();
  let files = e.dataTransfer.files;
  var reader = new FileReader();
  for(let i=0;i<files.length; i++) {
    reader.onload = function(event) {
      socket.send(event.target.result);
    };
    reader.readAsArrayBuffer(files[i]);
  }
  }, false));

My rust code is based on the official examples (https://github.com/actix/examples/tree/master/websockets/websocket):

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for RatioUpWS {
    fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context,) {
        // process websocket messages
        println!("WS: {:?}", msg);
        match msg {
            Ok(ws::Message::Ping(msg)) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            Ok(ws::Message::Pong(_)) => {self.hb = Instant::now();}
            Ok(ws::Message::Text(text)) => {
                ctx.text(text);
            }
            Ok(ws::Message::Binary(bin)) => {
                let mut pos = 0;
                let mut buffer = File::create("foo.txt").unwrap();  // notice the name of the file that will be written
                while pos < bin.len() {
                    let bytes_written = buffer.write(&bin[pos..]).unwrap();
                    pos += bytes_written
                };
                ctx.binary(bin)
            },
            Ok(ws::Message::Close(reason)) => {
                ctx.close(reason);
                ctx.stop();
            }
            _ => ctx.stop(), //if file>64K, it goes here
        }
    }
}

It seems to be recognized as a text message and I don't know how to deserialize it. actix_web::web::Form<FormDataStruct> don't seems to be appropriate.

Any idea?

Athanor
  • 855
  • 1
  • 16
  • 34

1 Answers1

2

Your WebSocket server is recognizing the message as text because your JavaScript code is dispatching text data with FormData() instead of using an ArrayBuffer.

To transfer a file from a browser using a WebSocket, you can use the following example code (notice the WebSocket declaration and sendFile() function):

    var ws;
    function connectToWS() {
      var wsUri = (window.location.protocol=='https:'&&'wss://'||'ws://')+window.location.host + '/ws/';
      console.log(wsUri);
      ws = new WebSocket(wsUri);
      ws.binaryType = "arraybuffer";  // Note the binaryType here
      ws.onopen = function() {alert("Connected.");};
      ws.onmessage = function(evt) {alert(evt.msg);};
      ws.onclose = function() {
        ws = null;
        alert("Connection is closed...");
      };
      ws.onerror = function(e) {alert(e.msg);}
    }

    function sendFile() {
      var file = document.getElementById('filename').files[0];
      var reader = new FileReader();
      var rawData = new ArrayBuffer();            
      reader.loadend = function() {}
      reader.onload = function(e) {
          rawData = e.target.result;
          ws.send(rawData);
          alert("the File has been transferred.")
      }
      reader.readAsArrayBuffer(file);
    }

To process the transfered bytes in the server-side you can use the standard Rust library (std::io::Write). Changing the same example Rust code you provided to process a .txt file with std::io:

impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {
    fn handle(
        &mut self,
        msg: Result<ws::Message, ws::ProtocolError>,
        ctx: &mut Self::Context,
    ) {
        // process websocket messages
        println!("WS: {:?}", msg);
        match msg {
            Ok(ws::Message::Ping(msg)) => {
                self.hb = Instant::now();
                ctx.pong(&msg);
            }
            Ok(ws::Message::Pong(_)) => {
                self.hb = Instant::now();
            }
            Ok(ws::Message::Text(text)) => ctx.text(text),
            Ok(ws::Message::Binary(bin)) => {
                let mut pos = 0;
                let mut buffer = File::create("foo.txt").unwrap();  // notice the name of the file that will be written
                while pos < bin.len() {
                    let bytes_written = buffer.write(&bin[pos..]).unwrap();
                    pos += bytes_written
                };
                ctx.binary(bin)
            },
            Ok(ws::Message::Close(reason)) => {
                ctx.close(reason);
                ctx.stop();
            }
            _ => ctx.stop(),
        }
    }
}

You can view the complete working example in this repo: https://github.com/vnderlev/actix_ws_file_transfer (adapted from the official actix/example repository).

I hope this answer helps you.

  • I'm now able to transfer as binary and write small files (<64K I think). I have to fix this now. My files are mainly around 5K-200K but some are bigger (<10M) – Athanor Mar 13 '21 at 08:32