0

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

这是我测试客户端 Web 浏览器的一些代码:

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 Documentation 上读到:

此功能是非标准的,不在标准轨道上。不要在面向 Web 的生产站点上使用它:它不适用于每个用户。实现之间也可能存在很大的不兼容性,并且行为可能会在未来发生变化。- Mozilla

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

4

3 回答 3

0

要将文件发送到网络服务器,您根本不需要 js。单独的 HTML 可以很好地处理<form>元素。

现在,如果您想通过 js,例如捕获不同的 ProgressEvents,那么您可以直接发送您的文件,无需在您身边阅读它。

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

如果您的服务器能够处理PUT 请求,您可以简单地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。

于 2018-04-08T06:19:05.630 回答
-1

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

于 2018-04-08T03:11:57.420 回答
-1

凯多的说法是正确的

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

但这并不能回答我的问题。使用 SimpleXMLHttpRequest()可以上传文件并跟踪这些进度。但是,它仍然不是。直接上传,无论是从<form>还是使用XMLHttpRequest()都需要增加你upload limit的 php 设置。这种方法对我来说不方便。如果客户端上传文件为 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服务器如何监听ArrayBuffer来自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);
    }
}

上述方法效果很好。FileReader 读取文件作为ArrayBuffer上传到服务器。对我来说,从ReadAsBinaryString()to迁移ReadAsArrayBuffer()很重要,并且ReadAsArrayBuffer()具有更好的性能,而不是ReadAsBinaryString()

以下是一些开发人员依赖FileReaderAPI 的原因:

  1. 流媒体。使用这种方法,文件将被流式传输,因此我们可以避免多次设置 php。
  2. 轻松加密。由于文件是通过 发送的,因此开发人员可以在上传过程中ArrayBuffer轻松访问文件。Encrypt

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

安全通知

上面的代码只是测试代码,要在生产中使用它,你必须考虑在 HTTPS 中GETPOST在 HTTPS 下发送数据。

于 2018-04-13T02:34:10.487 回答