【问题标题】:Migrate FileReader ReadAsBinaryString() to ReadAsArrayBuffer() or ReadAsText()将 FileReader ReadAsBinaryString() 迁移到 ReadAsArrayBuffer() 或 ReadAsText()
【发布时间】:2018-09-17 17:24:13
【问题描述】:

我意识到当文件大于 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

【问题讨论】:

  • Is there any way that we can upload bigger file from JS to Web Server in raw/string other than FileReader implementation?。是的,直接发送文件。文件是 Blob。您正在做的事情绝对没有意义:您正在尝试以其他格式读取 Blob,然后从这种其他格式重建 Blob。只需发送文件,如果您不需要读取文件,请忘记 FileReader。
  • 直接通过表单数据发送文件不适用于 1gb 的更大文件。
  • 这仅取决于您的服务器配置。如果您将其设置为接受更大的帖子数据,您将能够发送更大的文件。现在,如果你不能正确设置你的服务器,你也可以slice()原始文件。

标签: javascript arraybuffer binarystream


【解决方案1】:

凯多说法正确

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

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

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

这是我的一些测试,按我的意愿工作。如果我错了,希望有专家能纠正我。 我的上传.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的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 文件。

此方法还支持上传任何类型的文件。我做了一些测试,我意识到ReadAsArrayBuffer() 方法比ReadAsBinaryString() 和直接表单上传更快。你可以试试看。

安全通知

以上代码只是测试代码,要在生产环境中使用,需要考虑在GETPOST下HTTPS下发送数据。

【讨论】:

    【解决方案2】:

    要将文件发送到网络服务器,您根本不需要 js。单独的 HTML 可以通过 &lt;form&gt; 元素很好地做到这一点。

    现在如果你想通过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 可能有意义的唯一情况是某些不支持将 Blob 传递到 FormData 的 Android 浏览器,即使它们没有提供任何线索。
    所以在这种情况下,你必须设置你的服务器来让你知道请求是空的,然后只读取文件作为你将在原始文件的位置发送的 dataURI。

    【讨论】:

      【解决方案3】:

      经过漫长的一周研究和不眠之夜,您无法上传二进制字符串而不破坏它,base64 也不适用于所有文件,仅适用于图像,从客户端到服务器的旅程会破坏字节正在发送

      【讨论】:

      • 除了第一部分(关于失眠之夜)之外,所有这些答案都是由完全错误的陈述组成的。
      • 好吧,我试过了,base64、ArrayBuffer、BinaryString,它们不起作用,至少对于并非所有文件类型,例如,base64 非常适合发送 .jpg,但不是.png 或 pdf,一些数据丢失,其他类型的文件也一样,ArrayBuffer 非常适用于 pdf 和图像,但不适用于其他文件,我说的是分块发送文件
      • base64 适用于所有二进制数据,它只是一种编码方案,并不关心传递给它的内容。你肯定在某个地方做错了什么,但首先,离开 Blob 方式是一个错误的举动。
      猜你喜欢
      • 2012-03-09
      • 2015-01-08
      • 1970-01-01
      • 2020-12-06
      • 1970-01-01
      • 1970-01-01
      • 2019-02-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多