10

Issue

I have a page where users can upload files with the help of FormData and an XMLHttpRequest. Uploading the file works fine. But the upload.onprogress is only working when uploading from an HTTP connection.

HTTPS

HTTPS

HTTP

HTTP

I've tested this on Heroku and on an Amazon EC2 instance. But it's always the same:

  • Progress is shown when uploading via HTTP
  • Progress event is never triggered when uploading via HTTPS

Javascript (Angular 7)

const xhr = new XMLHttpRequest();
let progress = 0;


/** THIS EVENT IS NOT WORKING WITH HTTPS */
xhr.upload.onprogress = (event: ProgressEvent) => {
    if (event.lengthComputable) {
        progress = 100 * (event.loaded / event.total);
    }
};


xhr.responseType = 'json';
xhr.open('POST', `${API_URL}/${this.API_PATH}/upload`, true);
xhr.setRequestHeader('authorization', this.authService.getAuthToken());
xhr.send(payload);
xhr.onload = () => {
    observer.next(xhr.response);
    observer.complete();
};

Node.Js

const busboyBodyParser = require('busboy-body-parser');
app.use(busboyBodyParser())

const busboy = new Busboy({ headers: req.headers })
busboy.on('finish', async () => {

    const fileData = req.files.file
    const fileId = req.body.fileId
    const params = {
        Body: fileData.data,
        Bucket: awsConfig.bucket,
        ContentType: fileData.mimetype,
        Key: fileId,
        StorageClass: 'ONEZONE_IA',
    }
    awsConfig.s3.upload(params, (err, data) => { /* ... */ }

})
req.pipe(busboy)

What I've also tried

I also tried to use the .addEventListener syntax for listening for progress:

xhr.upload.addEventListener("progress", uploadProgress, false);

But this didn't work, either.

Source Code

Node.Js (server.js)

Node.Js (upload-file.js)

Angular Service (editor-file.service.ts)

Notes

Please note, that I have already asked a question about this topic. But I got no working answer and I really need this to work.

Old question: XHR upload onprogress Event not Working on HTTPS Connection

Florian Ludewig
  • 4,338
  • 11
  • 71
  • 137

4 Answers4

7

It's important to set Listener between:

xhr.open('POST'...);

...put you listener here....

xhr.send(data)

In this case it gonna work!

DmitryBara
  • 391
  • 3
  • 3
  • No way, all those "super complex" answers I've found all day; but it was this, I wish I could upvote twice. – Onza Aug 09 '21 at 11:23
  • Annoyingly odd thing. But seems to correct nonetheless. What if SO never existed... – Gherman Jul 15 '22 at 23:40
4

I'm doing just the same with one of my webapps but without any angular, just JS and PHP. My xhr works like a charm and is looking like this:

var totalSize = 0;
var xhr = new XMLHttpRequest();    // den AJAX Request anlegen
xhr.open('POST', 'data/upload.php');    // Angeben der URL und des Requesttyps
xhr.upload.addEventListener("progress", handleProgress);
xhr.addEventListener("load", handleComplete);

and this my handleProgess method:

handleProgress = function(event){
    var progress = totalProgress + event.loaded;
    document.getElementById('progress').innerHTML = 'Aktueller Fortschritt: ' + ((progress - totalSize < 0) ? Math.floor(progress / totalSize * 10000) / 100 : 100)  + '%';
}
1

As I've tried to reproduce this problem, I didn't face the same issue. Could you please check below simple Heroku app that I've created to test this specific problem? Also, if there is any missing part that I am not seeing, please inform me.

Heroku Test-Purpose App: https://erdsav-test-app.herokuapp.com/

Below is the JS code that I am tried to build on top of your code and it simply uploads the zip data and downloads it afterwards (cannot store on Heroku because of having Ephemeral filesystem) to ensure that the file is uploaded successfully;

import { Observable } from "../js/Observable.js";

document.addEventListener("DOMContentLoaded", function(event) {
    var progressBar = document.getElementById("progress"),
    fileNameSpan = document.getElementById("file_name"),
    fileSizeSpan = document.getElementById("file_size"),
    fileUploadComp = document.getElementById("file_upload"),
    loadButton = document.getElementById("upload_button"),
    displaySpan = document.getElementById("progress_display"),
    fileDetails = document.getElementById("file_details"),
    selectButton = document.getElementById("select_button"),
    formData = null;

    function hideElements(){
        fileDetails.style.display = "none";
    }

    function showElements(){
        fileDetails.style.display = "block";
    }

    function upload(payload, fileName){
        return new Observable(observer => {
            const xhr = new XMLHttpRequest();
            let progress = 0;

            /** THIS EVENT IS NOT WORKING WITH HTTPS */
            xhr.upload.onprogress = (event => {
                if (event.lengthComputable) {
                    progressBar.max = event.total;
                    progressBar.value = event.loaded;
                    progress = Math.floor((event.loaded / event.total) * 100);
                    displaySpan.innerText = progress + '%';
                    observer.next(progress);
                }
            });
            xhr.upload.onloadstart = function(e) {
              progressBar.value = 0;
              displaySpan.innerText = '0%';
            }
            xhr.upload.onloadend = function(e) {
              progressBar.value = e.loaded;
              loadButton.disabled = false;
              loadButton.innerHTML = 'Start Upload Process';
            }

            xhr.responseType = 'blob';
            xhr.open('POST', "https://erdsav-test-app.herokuapp.com/upload.php", true);  
            xhr.send(payload);
            xhr.returnedFileName = fileName;
            xhr.onload = () => {
                download(xhr.response, xhr.returnedFileName, "application/zip");
                observer.next(100);
                observer.complete();
            };
        });
    }

    function showUploadedFile(file){
        var fileName = file.name;
        var fileSize = file.size;

        fileNameSpan.innerText = fileName;
        fileSizeSpan.innerText = Math.floor(fileSize / 1000) + ' KB';
    }

    function buildFormData(file) {      
        if (formData) { 
            formData.append("file", file);
        }     

        return formData;  
    }

    hideElements(); 
    if (window.FormData) {
        formData = new FormData();
    }
    else{
        alert("FormData is not supported in this browser!");
    }

    fileUploadComp.onchange = function(){
        var file = fileUploadComp.files[0];

        if(file){
            showElements();
            showUploadedFile(file);
        }
        else{
            hideElements();
        }
    }

    selectButton.addEventListener("click", function(e){
       fileUploadComp.value = ""; 
       hideElements();    

       fileUploadComp.click();

       e.preventDefault(); 
    });

    loadButton.addEventListener("click", function(e) {
       if(fileUploadComp.files !== undefined && fileUploadComp.files.length > 0){
           this.disabled = true;
           this.innerHTML = "Uploading. Please wait...";

           var obs = upload(buildFormData(fileUploadComp.files[0]), fileUploadComp.files[0].name);
           obs.subscribe(
            function valueHandler(value){
              console.log("UPLOADING");
              if(value){
                  console.log(value);
              }
            },
            function errorHandler(err){
              console.log("THERE IS AN ERROR");
            },
            function completeHandler(){
              console.log("COMPLETE");
            }
            );
        }
        else{
            alert("No file is selected");
        }

        e.preventDefault();
    });
});

PHP side

<?php

if ($_FILES['file']['error'] != $UPLOAD_ERR_OK) {
    //writeLog($_FILES['file']['error']);
    echo 'An error occurred!'; 
    exit();
} 
else { 
   $filePath = $_FILES['file']['tmp_name'];
   $fileName = $_FILES['file']['name']; 

   if (file_exists($filePath)) {
        ob_start();
        $fileSize = readfile($filePath);
        $content = ob_get_clean();

        header('Content-Type: application/octet-stream;');
        header("Content-Disposition: attachment; filename=\"" . $fileName . "\"");
        header('Expires: 0');
        header('Pragma: no cache');
        header('Content-Length: ' . $fileSize);

        echo $content;
   }
   else{
       echo 'File is not found';
       exit();
   }
}

?>

HTML page source

<!DOCTYPE html>
<html lang="en">
    <title>ProgressBar Progress Test</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <meta name="description" content="ProgressBar Progress Test">
    <body>          
            <form method="post" enctype="multipart/form-data">
                <input type="file" id="file_upload" accept="application/zip" style="width:0px" />
                <button id="select_button">Choose File to Upload</button>
                <progress id="progress" value="0"></progress>
                <span id="progress_display"></span>
                <button type="submit" id="upload_button">Start Upload Process</button>
            </form>
            <div id="file_details" style="display: none">
                <h3>Selected File Details</h3>
                <span id="file_name"></span><br>
                <span id="file_size"></span>
            </div>
            <script type="module" src="js/Observable.js"></script>  
            <script src="js/download.js"></script>  
            <script type="module" src="js/main.js"></script>                                
    </body>
</html>

Below image is captured by slowing the network to see the current percentage while uploading process continues;

File Upload Test

Browsers used in testing;

Firefox Developer Edition 67.0b13 (64-bit/Up-to-date)
Google Chrome 74.0.3729.108 (64-bit/Up-to-date)

Erdem Savasci
  • 697
  • 5
  • 12
  • Your heroku app works for me, too. The only difference I can see, is that I am using Node.Js instead of PHP. So, I've added links to my source code - maybe you can spot somehting that could cause the issue :) – Florian Ludewig Apr 25 '19 at 08:47
  • 1
    Hi Florian, thank you for your response. I've investigated your code a bit, not in detail for now but something attracted my attention. There is no observer.next(progress); line in your xhr.upload.onprogress event. Can you check there first? – Erdem Savasci Apr 25 '19 at 10:56
  • I checked it, and the issue remains. (The `xhr.upload.onprogress` is called only once) – Florian Ludewig Apr 25 '19 at 11:06
  • Okay, can you try again with throttling the network connection? (ex. Slow 3G turned on) Maybe progress finishes so fast and onprogress is being called once? I am asking this to ensure that method is called only once. – Erdem Savasci Apr 25 '19 at 12:11
  • It's called only once, also with network throttling – Florian Ludewig Apr 25 '19 at 12:13
  • Not trying to annoy you, but did you find anything that could potentially be the cause? – Florian Ludewig Apr 26 '19 at 19:30
0

Now in 2022

The 2016's specifications are outdated, here is the new standard for XMLHttpRequest.

The upload is a getter that returns an XMLHttpRequestUpload object

The XMLHttpRequestUpload object implements the XMLHttpRequestEventTarget interface, which handles the onprogress event.

Now here's what MDN says about this:

Note: The spec also seems to indicate that event listeners should be attached after open(). However, browsers are buggy on this matter, and often need the listeners to be registered before open() to work.

migli
  • 2,692
  • 27
  • 32