Migrate FileReader ReadAsBinaryString() to ReadAsArrayBuffer() or ReadAsText()

2.2k Views Asked by At

I realize that the new Mozilla Firefox return allocation size overflow (on FileReader.ReadAsBinaryString()) when the file bigger than 200MB (something like that).

Here's some of my code on test for client web browser:

function upload(fileInputId, fileIndex)
{
    var file = document.getElementById(fileInputId).files[fileIndex];
    var blob;
    var reader = new FileReader();
    reader.readAsBinaryString(file); 
    reader.onloadend  = function(evt)
    {
        xhr = new XMLHttpRequest();

        xhr.open("POST", "upload.php", true);

        XMLHttpRequest.prototype.mySendAsBinary = function(text){
            var data = new ArrayBuffer(text.length);
            var ui8a = new Uint8Array(data, 0);
            for (var i = 0; i < text.length; i++){ 
                ui8a[i] = (text.charCodeAt(i) & 0xff);
            }

            if(typeof window.Blob == "function")
            {
                 blob = new Blob([data]);
            }else{
                 var bb = new (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder)();
                 bb.append(data);
                 blob = bb.getBlob();
            }

            this.send(blob);
        }

        var eventSource = xhr.upload || xhr;
        eventSource.addEventListener("progress", function(e) {
            var position = e.position || e.loaded;
            var total = e.totalSize || e.total;
            var percentage = Math.round((position/total)*100);
        });

        xhr.onreadystatechange = function()
        {
            if(xhr.readyState == 4)
            {
                if(xhr.status == 200)
                {
                    console.log("Done");
                }else{
                    console.log("Fail");
                }
            }
        };
        xhr.mySendAsBinary(evt.target.result);
    };
}

So I tried change it to FileReader.ReadAsArrayBuffer(), the error has not shown up but the data are not the same (as it's not read as binary string).

Did anyone has any solution to solve this problem? Is there any way that we can upload bigger file from JS to Web Server in raw/string other than FileReader implementation?

I read on Mozilla JS Documentation that said:

This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future. - Mozilla

If not ReadAsBinaryString, the how to implement ReadAsArrayBuffer or ReadAsText

3

There are 3 best solutions below

0
On BEST ANSWER

Kaiido statement is correct

To send Files to a web-server, you simply don't need js

But that doesn't answer my question. Using the Simple XMLHttpRequest() can upload the file and track those progress as well. But still, it's not it. The direct upload, either from the <form> or using XMLHttpRequest() will need to increase your upload limit in php setting. This method is not convenience for me. How if the client upload file as 4GB? So I need to increase to 4GB. Then next time, client upload file as 6GB, then I have to increase to 6GB.

Using the slice() method is make sense for bigger file as we can send it part by part to server. But this time I am not using it yet.

Here's some of my test the worked as I want. I hope some expert could correct me if I am wrong. My Upload.js

function upload(fileInputId, fileIndex)
{
    var file = document.getElementById(fileInputId).files[fileIndex];
    var blob;
    var reader = new FileReader();

    reader.readAsArrayBuffer(file);
    reader.onloadend  = function(evt)
    {
        xhr = new XMLHttpRequest();
        xhr.open("POST", "upload.php?name=" + base64_encode(file.name), true);

        XMLHttpRequest.prototype.mySendAsBinary = function(text){
            var ui8a = new Uint8Array(new Int8Array(text));

            if(typeof window.Blob == "function")
            {
                 blob = new Blob([ui8a]);
            }else{

                var bb = new (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder)();
                bb.append(ui8a);
                blob = bb.getBlob();
            }

            this.send(blob);
        }

        var eventSource = xhr.upload || xhr;
        eventSource.addEventListener("progress", function(e) {
            var position = e.position || e.loaded;
            var total = e.totalSize || e.total;
            var percentage = Math.round((position/total)*100);
            console.log(percentage);
        });

        xhr.onreadystatechange = function()
        {
            if(xhr.readyState == 4)
            {
                if(xhr.status == 200)
                {
                    console.log("Done");
                }else{
                    console.log("Fail");
                }
            }
        };

        xhr.mySendAsBinary(evt.target.result);
    };
}

Below is how the PHP server listen to the ArrayBuffer from JS

if(isset($_GET["name"])){
    $name = base64_decode($_GET["name"]);
    $loc = $name;
    $inputHandler = fopen('php://input', "r");
    $fileHandler = fopen($loc, "w+");

    while(true) {
        //$buffer = fgets($inputHandler, 1024);
        $buffer = fread($inputHandler, 1000000);

        if (strlen($buffer) == 0) {
            fclose($inputHandler);
            fclose($fileHandler);
            return true;
        }

        //$b = base64_encode($buffer);

        fwrite($fileHandler, $buffer);
    }
}

The above method works well. The FileReader read the file as ArrayBuffer the upload to server. For me, migrating from ReadAsBinaryString() to ReadAsArrayBuffer() is important and ReadAsArrayBuffer() has some better performance rather than ReadAsBinaryString()

Here's some reason, why some developer relies to FileReader API:

  1. Streaming. Using this method, the file will be stream, so we can avoid setting the php multiple time.
  2. Easy Encrypt. As the file is sending via ArrayBuffer, it is easy for developer to Encrypt the file while upload in progress.

This method also support upload any type of file. I ve done some test and I realize that ReadAsArrayBuffer() method are more faster than ReadAsBinaryString() and direct form upload. You may try it.

Security Notice

The above code is only under test code, to use it in production, you have to consider sending the data in GET or POST under HTTPS.

0
On

To send Files to a web-server, you simply don't need js. HTML alone is well able to do this with the <form> element.

Now if you want to go through js, for e.g catch the different ProgressEvents, then you can send directly your File, no need to read it whatsoever on your side.

To do this, you've got two (or three) solutions.

If your server is able to handle PUT requests, you can simply xhr.send(file);.

Otherwise, you'd have to go through a FormData.

// if you really want to go the XHR way
document.forms[0].onsubmit = function handleSubmit(evt) {
  if(!window.FormData) { // old browser use the <form>
    return;
  }
  // now we handle the submit through js
  evt.preventDefault();
  var fD = new FormData(this);
  var xhr = new XMLHttpRequest();
  xhr.onprogress = function handleProgress(evt){};
  xhr.onload = function handleLoad(evt){};
  xhr.onerror = function handleError(evt){};
  xhr.open(this.method, this.action);
//  xhr.send(fD); // won't work in StackSnippet
  log(fD, this.method, this.action); // so we just log its content
};


function log(formData, method, action) {
  console.log('would have sent');
  for(let [key, val] of formData.entries())
    console.log(key, val);
  console.log('through', method);
  console.log('to', action);
}
<!-- this in itself is enough -->
<form method="POST" action="your_server.page">
  <input type="file" name="file_upload">
  <input type="submit">
</form>

Now, you sent a comment saying that you can't upload Files bigger than 1GB to your server. This limitation is only due to your server's config, so the best if you want to accept such big files is to configure it correctly.

But if you really want to send your File by chunks, even then don't get off of the Blob interface.
Indeed Blobs have a slice() method, so use it.

document.forms[0].onsubmit = function handleSubmit(evt) {
  evt.preventDefault();
  var file = this.elements[0].files[0];
  var processed = 0;
  if(file) {
//  var MAX_CHUNK_SIZE = Math.min(file.size, server_max_size);
    // for demo we just split in 10 chunks
    var MAX_CHUNK_SIZE = file.size > 10 ? (file.size / 10) | 0 : 1;
    loadChunk(0);
  }
  function loadChunk(start) {
    var fD = new FormData();
    var sliced = file.slice(start, start+MAX_CHUNK_SIZE);
    processed += sliced.size; // only for demo
    fD.append('file_upload', sliced, file.name);
    fD.append('starting_index', start);
    if(start + MAX_CHUNK_SIZE >= file.size) {
      fD.append('last_chunk', true);
    }
    var xhr = new XMLHttpRequest();
    xhr.open('POST', 'your_server.page');
    xhr.onload = function onchunkposted(evt) {
      if(start + MAX_CHUNK_SIZE >= file.size) {
        console.log('All done. Original file size: %s, total of chunks sizes %s', file.size, processed);
        return;
      }
      loadChunk(start + MAX_CHUNK_SIZE);
    };
//    xhr.send(fD);
    log(fD);
    setTimeout(xhr.onload, 200); // fake XHR onload
  }
};

    function log(formData, method, action) {
      console.log('would have sent');
      for(let [key, val] of formData.entries())
        console.log(key, val);
    }
<form method="POST" action="your_server.page">
  <input type="file" name="file_upload">
  <input type="submit">
</form>

But you absolutely don't need to go through a FileReader for this operation.

Actually the only case where it could make sense to use a FileReader here would be for some Android browsers that don't support passing Blob into a FormData, even though they don't give a single clue about it.
So in this case, you'd have to set up your server to let you know the request was empty, and then only read the File as a dataURI that you would send in-place of the original File.

3
On

after a long week of research and sleepless nights, you can't upload binary strings without breaking it, also base64 doesn't work for all files, only images, the journey from the client-side to the server breaks the bytes being sent