Organizing resume in php HTTP_RANGE

There are quite a few implementation examples. Interested in the more detailed part, viz. How do I know if a file has already been downloaded?

Situation: The script generates files on the server into a temporary folder, then the upload script is used (http_range, 206 partial content, etc.). How do I know when to delete it.

Do not offer offers with cron!


Answer 1, authority 100%

Unfortunately, there is no way to determine in PHP that a file has been guaranteed to be downloaded. However, if nginx is used as the frontend, then at least you can be sure that the file (in this case, part of the file) has been served.

In nginx, there is a post_actiondirective that makes a repeated internal request after the response has already been sent to the client. Accordingly, thanks to it, you can find out how much data was actually sent to the client. The config will look something like this:

# ,     
location = /download.php {
    fastcgi_pass localhost:9000;
    fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/$fastcgi_script_name;
    include fastcgi_params;
    post_action /complete;
}
#       
location = /complete {
    internal;
    fastcgi_pass localhost:9000;
    fastcgi_param SCRIPT_FILENAME /var/www/localhost/htdocs/complete.php;
    #    
    fastcgi_param BYTE-SEND $body_bytes_sent;
    #  Range
    fastcgi_param RANGE $http_range;
    include fastcgi_params;
}

After download.php sends the next piece of file, nginx will make an internal request to complete.php, where $_SERVER['BYTE-SEND']will be the number of bytes sent, and $_SERVER['BYTE-SEND']Range header from client.

Well, then it’s a matter of technology. We consider somewhere (for example, in a session) bytes or, for greater reliability, Range intervals are better. And in the code, the number of bytes given will be equal to the size of the file, which means the file was given in its entirety.

PS Unfortunately, the post_action directive is not documented.

UPD.If PHP works through fastcgi or any reverse-proxy stands behind it (again, nginx for example), then it is useless to determine this in PHP itself. After PHP gives the web server a piece of data, its further fate is not known to it.

If PHP is running on apache via mod_php without any reverse-proxy, then it’s probably still a stretch to assume that after flush()the data went to the client.

Another way is to give the file directly through sockets. Those. write a script that would itself act as a web server. However, this is an extremely inefficient solution.

That’s it, I don’t see any other options in PHP.


Answer 2, authority 67%

Unfortunately, I wrote such a script a long time ago, and I don’t really want to remember it. Here he is, I hope you understand.

<?php
set_time_limit(0);
function file_download($filename, $mimetype='audio/mpeg') {
    $download_speed =   51200; // 51200
    $time_discret   =   1;
    if (file_exists($filename)) {
        $f              =   fopen($filename, 'r');
        if (isset($_SERVER['HTTP_RANGE'])) {
            $load_from      =   preg_replace('#[^0-9]#', '', $_SERVER['HTTP_RANGE']);
            fseek($f, $load_from);
            $filesize       =   filesize($filename);
            header('HTTP/1.1 206 Partial Content');
            header('Content-Type: ' . $mimetype);
            header('Content-Range: bytes '.$load_from.'-'.$filesize.'/'.$filesize);
            header('Last-Modified: ' . gmdate('r', filemtime($filename)));
            header('ETag: ' . sprintf('%x-%x-%x', fileinode($filename), filesize($filename), filemtime($filename)));
            header('Accept-Ranges: bytes');
            header('Content-Length: ' . (filesize($filename)));
            header('Connection: close');
            header('Content-Disposition: attachment; filename="' . basename($filename) . '";');
        } else {
            header($_SERVER["SERVER_PROTOCOL"] . ' 200 OK');
            header('Content-Type: ' . $mimetype);
            header('Last-Modified: ' . gmdate('r', filemtime($filename)));
            header('ETag: ' . sprintf('%x-%x-%x', fileinode($filename), filesize($filename), filemtime($filename)));
            header('Content-Length: ' . (filesize($filename)));
            header('Connection: close');
            header('Content-Disposition: attachment; filename="' . basename($filename) . '";');
        }
            if((int) $download_speed > 0) {
                while(!feof($f)) {
                    $time_start = microtime(true);
                    echo fread($f, ceil($download_speed*$time_discret));
                    flush();
                    $time_end = microtime(true);
                    $time = $time_end - $time_start;
                    if($time_discret-$time > 0) usleep(($time_discret-$time)*1000000);
                }
            } else {
                while(!feof($f)) {
                    echo fread($f, 1024);
                    flush();
                }
            }
        fclose($f);
    } else {
        header($_SERVER["SERVER_PROTOCOL"] . ' 404 Not Found');
        header('Status: 404 Not Found');
    }
    exit;
}
#
file_download('./20_dt8_project.mp3', $mimetype='audio/mpeg');
?>

The script sends the file for download to the client, and if you need to download the file, it downloads it). I took parts from sites, added some myself, there is a resume, one of the headers says that it is. But I warn you right away, such a script loads the server very heavily.