【问题标题】:XMLHttpRequest stream crashing when uploading large files (~1 GB)上传大文件 (~1 GB) 时 XMLHttpRequest 流崩溃
【发布时间】:2021-07-30 18:17:17
【问题描述】:

我正在尝试与朋友一起为另一个项目制作在线文件管理器,当上传大于 1GB 的文件时,该过程要么崩溃(firefox),要么成功但接收到的文件重量为 0 字节(chromium)。

JS:

    function uploadFile(fileInputId, fileIndex) {
        //send file name
        try {
            var fileName = document.getElementById('fileUploader').files[0].name;
        }
        catch {
            document.getElementById('uploadStatus').innerHTML = `<font color="red">Mettre un fichier serait une bonne idée.</font>`;
            return false;
        }
        document.cookie = 'fname=' + fileName;

        //take file from input
        const file = document.getElementById(fileInputId).files[fileIndex];
        const reader = new FileReader();
        reader.readAsBinaryString(file);
        reader.onloadend = function(event) {
            ajax = new XMLHttpRequest();
            //send data
            ajax.open("POST", 'uploader.php', true);

            //all browser supported sendAsBinary
            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") {
                    var blob = new Blob([data]);
                }else {
                    var bb = new (window.MozBlobBuilder || window.WebKitBlobBuilder || window.BlobBuilder)();
                    bb.append(data);
                    var blob = bb.getBlob();
                }

                this.send(blob);
            }
            //track progress
            var eventSource = ajax.upload || ajax;
            eventSource.addEventListener('progress', function(e) {
                //percentage
                var position = e.position || e.loaded;
                var total = e.totalSize || e.total;
                var percentage = Math.round((position/total)*100);

                document.getElementById('uploadStatus').innerHTML = `${percentage}%`;
            });

            ajax.onreadystatechange = function() {
                if(ajax.readyState == 4 && ajax.status == 200) {
                    document.getElementById('uploadStatus').innerHTML = this.responseText;
                }
            }

            ajax.mySendAsBinary(event.target.result);
        }

    }

PHP:

//mysql login
$conn = new PDO([Redacted]);

//file info
$fileName = $_COOKIE['fname'];
$targetDir = "uploads/";
$targetFile = $targetDir.$fileName;
$fileNameRaw = explode('.', $fileName)[0]; //file name with no extension
$tempFilePath = $targetDir.$fileNameRaw.'.tmp';
if (file_exists($targetFile)) {
    echo '<font color="red">Un fichier du même nom existe déjà.</font>';
    exit();
}

//read from stream
$inputHandler = fopen('php://input', 'r');
//create temp file to store data from stream
$fileHandler = fopen($tempFilePath, 'w+');

//store data from stream
while (true) {
    $buffer = fgets($inputHandler, 4096);
    if (strlen($buffer) == 0) {
        fclose($inputHandler);
        fclose($fileHandler);
        break;
    }

    fwrite($fileHandler, $buffer);
}

//when finished
rename($tempFilePath, $targetFile);
chmod($targetFile, 0777);
echo 'Fichier envoyé avec succès !';
$bddInsert = $conn->prepare('INSERT INTO files(nom, chemin) VALUES(?,?)');
$bddInsert->execute(array($fileName, $targetFile));

在我的 php.ini 中,
max_execution_time 设置为 0
最大输入时间为 -1
我的帖子最大和上传最大大小为 4G

我正在使用 apache2

【问题讨论】:

    标签: javascript php ajax stream xmlhttprequest


    【解决方案1】:

    如果您不需要文件,则不应使用 fileReader 读取文件。

    只需将文件(blob)直接发送到您的 ajax 请求并避免使用 FileReader

    function uploadFile (fileInputId, fileIndex) {
      // Send file name
      try {
          var fileName = document.getElementById('fileUploader').files[0].name;
      }
      catch {
          document.getElementById('uploadStatus').innerHTML = `<font color="red">Mettre un fichier serait une bonne idée.</font>`;
          return false;
      }
      document.cookie = 'fname=' + fileName;
    
      // Take file from input
      const file = document.getElementById(fileInputId).files[fileIndex];
      const ajax = new XMLHttpRequest();
      // send data
      ajax.open("POST", 'uploader.php', true);
    
      // track progress
      ajax.upload.addEventListener('progress', function(e) {
        // percentage
        var position = e.position || e.loaded;
        var total = e.totalSize || e.total;
        var percentage = Math.round((position/total)*100);
    
        document.getElementById('uploadStatus').innerHTML = `${percentage}%`;
      });
    
      ajax.onreadystatechange = function() {
        if (ajax.readyState == 4 && ajax.status == 200) {
          document.getElementById('uploadStatus').innerHTML = this.responseText;
        }
      }
    
      ajax.send(file)
    }
    

    【讨论】:

    • 如果我这样做,它不是同时存储在内存中吗?我目前正在尝试另一种方法,涉及带有部分文件的承诺。编辑:它虽然有效,但我只是担心什么时候会出现 10GB 文件......
    • 您不必担心这一点,因为浏览器会负责将文件传输到网络并根据需要一次读取文件内容的一部分。文件和 Blob 只不过是数据所在位置的引用点。当您使用 FileReader 时,您甚至可以在将所有内容发送到服务器之前将其读入内存
    • 即使您将文件分割成file.slice(0, 1024) 这样的部分,您也不会将 1024 字节分配到 RAM 中,您唯一要做的就是将旧 blob 作为参考点并更改应该从中读取内容的开始/结束位置
    猜你喜欢
    • 2021-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-04
    • 2019-04-23
    • 2020-03-02
    • 2012-02-14
    相关资源
    最近更新 更多