Search code examples
javascriptphpajaxstreamxmlhttprequest

XMLHttpRequest stream crashing when uploading large files (~1 GB)


I'm trying to make an online file manager for another project with friends, and when uploading files bigger than 1GB, the process either crashes (firefox), or succeeds but the received file weighs 0 bytes (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));

in my php.ini,
max_execution_time is set to 0
max_input_time to -1
and my post max and upload max sizes are at 4G

I'm using apache2


Solution

  • You should not be reading the file with the fileReader if you don't need it.

    Just send the file (blob) directly to your ajax request and avoid the 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)
    }