Upload Progress - Sometimes $_SESSION[$key] is Emp

2019-07-19 23:01发布

问题:

I have Ubuntu 12.04 LTS and using PHP 5.5 with Apache2 to implement the upload progress via the PHP session upload progress.

The issue is that it works sometimes and sometimes it doesn't work. I mean sometimes I get the progress percentage 100% direct at the beginning of the upload without finishing the upload (which means the $_SESSION[$key] is empty in that cases, but why?!)

I tried turning the value of session.upload_progress.cleanup to On and Off, but it didn't change anything.

You can try it yourself on this URL:
http://138.128.124.172/upload_progress

In the php.ini, I have the below settings related to the upload:

;;;;;;;;;;;;;;;;
; File Uploads ;
;;;;;;;;;;;;;;;;

; Whether to allow HTTP file uploads.
; http://php.net/file-uploads
file_uploads = On

; Temporary directory for HTTP uploaded files (will use system default if not
; specified).
; http://php.net/upload-tmp-dir
;upload_tmp_dir =

; Maximum allowed size for uploaded files.
; http://php.net/upload-max-filesize
upload_max_filesize = 100M

; Maximum number of files that can be uploaded via a single request
max_file_uploads = 20


; Enable upload progress tracking in $_SESSION
; Default Value: On
; Development Value: On
; Production Value: On
; http://php.net/session.upload-progress.enabled
session.upload_progress.enabled = On

; Cleanup the progress information as soon as all POST data has been read
; (i.e. upload completed).
; Default Value: On
; Development Value: On
; Production Value: On
; http://php.net/session.upload-progress.cleanup
session.upload_progress.cleanup = Off

; A prefix used for the upload progress key in $_SESSION
; Default Value: "upload_progress_"
; Development Value: "upload_progress_"
; Production Value: "upload_progress_"
; http://php.net/session.upload-progress.prefix


;session.upload_progress.prefix = "upload_progress_"

; The index name (concatenated with the prefix) in $_SESSION
; containing the upload progress information
; Default Value: "PHP_SESSION_UPLOAD_PROGRESS"
; Development Value: "PHP_SESSION_UPLOAD_PROGRESS"
; Production Value: "PHP_SESSION_UPLOAD_PROGRESS"
; http://php.net/session.upload-progress.name
;session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"

; How frequently the upload progress should be updated.
; Given either in percentages (per-file), or in bytes
; Default Value: "1%"
; Development Value: "1%"
; Production Value: "1%"
; http://php.net/session.upload-progress.freq
;session.upload_progress.freq =  "1%"


; The minimum delay between updates, in seconds
; Default Value: 1
; Development Value: 1
; Production Value: 1
; http://php.net/session.upload-progress.min-freq
;session.upload_progress.min_freq = "1"

At the PHP side: I have the below code inside the page: progress.php:

session_start();
$key = ini_get("session.upload_progress.prefix") . "myForm";
if (!empty($_SESSION[$key])) {
    $current = $_SESSION[$key]["bytes_processed"];
    $total = $_SESSION[$key]["content_length"];
    echo $current < $total ? ceil($current / $total * 100) : 100;
}
else {
    echo 100;
}

At the client side, I have the below code in the page index.php

<?php

if ($_SERVER["REQUEST_METHOD"] == "POST" && !empty($_FILES["userfile"])) {
    // move_uploaded_file()
}
?>

<style>
    #bar_blank {
        border: solid 1px #000;
        height: 20px;
        width: 300px;
    }

    #bar_color {
        background-color: #006666;
        height: 20px;
        width: 0px;
    }

    #bar_blank, #hidden_iframe {
        display: none;
    }
</style>


<html>
    <head>
        <title>File Upload Progress Bar</title>
    </head>
    <body>
        <div id="bar_blank">
            <div id="bar_color"></div>
        </div>
        <div id="status"></div>
        <form action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="POST" id="myForm" enctype="multipart/form-data" target="hidden_iframe">
            <input type="hidden" value="myForm" name="<?php echo ini_get("session.upload_progress.name"); ?>">
            <input type="file" name="userfile"><br>
            <input type="submit" value="Start Upload">
        </form>


        <iframe id="hidden_iframe" name="hidden_iframe" src="about:blank"></iframe>
    </body>
</html>


<script>

function toggleBarVisibility() {
    var e = document.getElementById("bar_blank");
    e.style.display = (e.style.display == "block") ? "none" : "block";
}

function createRequestObject() {
    var http;
    if (navigator.appName == "Microsoft Internet Explorer") {
        http = new ActiveXObject("Microsoft.XMLHTTP");
    }
    else {
        http = new XMLHttpRequest();
    }
    return http;
}

function sendRequest() {
    var http = createRequestObject();
    http.open("GET", "progress.php");
    http.onreadystatechange = function () { handleResponse(http) };
    http.send(null);
}

function handleResponse(http) {
    var response;
    if (http.readyState == 4) {
        response = http.responseText;  //alert(response);return;
        document.getElementById("bar_color").style.width = response + "%";
        document.getElementById("status").innerHTML = response + "%";

        if (response < 100) {
            setTimeout("sendRequest()", 1000);
        }
        else {
            toggleBarVisibility();
            document.getElementById("status").innerHTML = "Done.";

            document.getElementById("bar_color").style.width = 0 + "%";

        }
    }
}

function startUpload() {
    toggleBarVisibility();
    setTimeout("sendRequest()", 1000);
}

(function () {
    document.getElementById("myForm").onsubmit = startUpload;
})();

</script>

I am not interested in the HTML5, Jquery or the flash. I would be thankful if you hint me also about better approaches to get a robust way to implement the upload with a progress bar.

Thanks for your help!

回答1:

I use the reply part due to size of the answer. Or, size of some details... In fact I've have the same problem, with PHP 5.5.18 running on Debian Whezzy.

After making a few test and putting a log in the progress.php in order to save the value of the $key, bytes_processed and content_length, here are my conclusions:

Discovery 1: we don't have an empty key. We have a key showing us informations with bytes_processed = content_length

Discovery 2: if you download eg 4 files with different size and then have a look at the log of your progress.php you'll see the value from the session for second file will give you the result for file 1.

Example:

Send test.docx -> 500.000 bytes. $key is empty

Send house.jpg -> 4.000.000 bytes. $key give bytes_processed = content_length = 500.000 so result of previous file

In many case, we use in the form, an hidden field like this:

 echo "<input type=hidden value=\"myForm\" name=\"";  
 echo ini_get("session.upload_progress.name");
 echo "\" />\n";

And we get the data using in progress.php:

 $key = ini_get("session.upload_progress.prefix") . "myForm";

meaning ALL our $key have the same name. I change by:

 $val = time();
 echo "<input type=hidden value=\"".$val."\" name=\"";  
 echo ini_get("session.upload_progress.name");
 echo "\" />\n";

and

 $key = ini_get("session.upload_progress.prefix") . $_POST[ini_get("session.upload_progress.name")];

Now, each time I have an empty key. My conclusion is that we have a cache problem which is what PHP.net say:

Warning The web server's request buffering has to be disabled for this to work properly, else PHP may see the file upload only once fully uploaded. Servers such as Nginx are known to buffer larger requests.



回答2:

Older post, but I'd suggest two things:

  1. Make hidden field dynamic value

$_SESSION['ukey'] = substr(md5(uniqid(rand(), true)),0,6);

<input type="hidden" value="<?php echo $_SESSION['ukey'] ?>" name="<?php echo ini_get("session.upload_progress.name"); ?>">

By this you achieve that you can send same filenames again and it will work, you will get unique session id. Also you can use in php.ini the value session.upload_progress.cleanup = Off so data in session will be there after 100% is reached. In progres.php change to $key = ini_get("session.upload_progress.prefix") . echo $_SESSION['ukey'];

  1. In progress script also this part of the code causing trouble:

else { echo 100; }

The reason is that there might be buffering in some intermediate device or apache or in the transit, so $_SESSION[$key] will be initialised even after browser already send all 100% of POST data. This is my scenario at certain ISPs. I removed this code and it is working fine. By this you achieve that AJAX will be pooling data always and not hang on this. You only need to handle exception when TCP would for some reason dropped and AJAX would be trying pooling endlessly until you close browser. But I don't know how often this would happen / if it happens.