将FileReader ReadAsBinaryString()迁移到ReadAsArrayBuffer()或ReadAsText()

时间:2018-04-08 02:36:54

标签: javascript arraybuffer binarystream

我意识到当文件大于200MB(类似的东西)时,新的Mozilla Firefox会返回allocation size overflow(在FileReader.ReadAsBinaryString()上)。

以下是我测试客户端网络浏览器的一些代码:

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);
    };
}

所以我尝试将其更改为FileReader.ReadAsArrayBuffer(),错误未显示但数据不相同(因为它不是二进制字符串读取)。

有没有人有任何解决方案来解决这个问题?除了FileReader实现之外,有没有什么办法可以在原始/字符串中将更大的文件从JS上传到Web服务器?

我在Mozilla JS文档中读过:

  

此功能不符合标准,不符合标准。不要   在面向Web的生产站点上使用它:它不会适用于所有人   用户。两者之间可能存在很大的不兼容性   实现和行为可能在未来发生变化。 - Mozilla

如果不是ReadAsBinaryString,那么如何实现ReadAsArrayBuffer或ReadAsText

3 个答案:

答案 0 :(得分:0)

要将文件发送到网络服务器,您根本不需要js。单独使用HTML就可以使用<form>元素完成此操作。

现在,如果您想要浏览js,例如捕获不同的ProgressEvents,那么您可以直接发送您的文件,无需随意阅读。

为此,您有两个(或三个)解决方案。

如果您的服务器能够处理PUT requests,您只需xhr.send(file);

否则,您必须通过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>

现在,您发送了一条评论,说您无法将大于1GB的文件上传到您的服务器。 这个限制只是由于您的服务器配置,所以如果您想接受这样大的文件,最好是正确配置它。

但是如果你真的想要通过块发送文件,那么即使这样也不要离开Blob界面。
确实Blob有一个slice()方法,所以使用它。

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>

但是你绝对不需要通过FileReader进行此操作。

<子> 实际上,在这里使用FileReader有意义的唯一情况是某些Android浏览器不支持将Blob传递给FormData,即使它们没有提供任何关于它的线索。 因此,在这种情况下,您必须设置服务器以通知您请求为空,然后仅将文件读取为您将代替原始文件发送的dataURI。

答案 1 :(得分:-1)

经过长达一周的研究和不眠之夜,你无法在不破坏它的情况下上传二进制字符串,而且base64不适用于所有文件,只有图像,从客户端到服务器的旅程会打破字节被发送

答案 2 :(得分:-1)

Kaiido声明是正确的

  

要将文件发送到网络服务器,您根本不需要js

但这并不能回答我的问题。使用简单XMLHttpRequest()可以上传文件并跟踪这些进度。但是,它还不是。来自<form>或使用XMLHttpRequest()的直接上传需要在php设置中增加upload limit。这种方法对我来说不方便。如果客户端上传文件为4GB?所以我需要增加到4GB。然后下次,客户端上传文件为6GB,那么我必须增加到6GB。

使用slice()方法对于更大的文件是有意义的,因为我们可以将它逐个发送到服务器。但是这次我还没有使用它。

我的部分测试按照我的意愿进行。如果我错了,我希望有些专家可以纠正我。 我的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);
    };
}

以下是PHP服务器如何从JS <​​/ p>收听ArrayBuffer

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);
    }
}

上述方法效果很好。 FileReader将文件读取为ArrayBuffer上传到服务器。对我来说,从ReadAsBinaryString()迁移到ReadAsArrayBuffer()非常重要,ReadAsArrayBuffer()有一些更好的表现,而不是ReadAsBinaryString()

以上是某些原因,为什么有些开发人员依赖FileReader API:

  1. 即可。使用此方法,文件将是流,因此我们可以避免多次设置php。
  2. 轻松加密。当文件通过ArrayBuffer发送时,开发人员可以在上传过程中轻松Encrypt该文件。
  3. 此方法还支持上传任何类型的文件。我做了一些测试,我发现ReadAsArrayBuffer()方法ReadAsBinaryString() 更快并直接表单上传。你可以尝试一下。

    安全通知

    以上代码仅在测试代码下,要在生产中使用,您必须考虑在HTTPS下以GETPOST发送数据。