Caching images in browser with URL parameters and XSendFile

Refresh

March 2019

Views

147 time

8

I am trying to set up a site so that users only have access to their own images and audio files. To to this I am using variables in the URL such as:

 <img src="/public/get_file.php?type=image&file=pic001.png" />

On the front-end I am using React JS (not sure if that is important to this issue). But on the backend, the PHP script will validate that the user is logged in (just by checking a simple SESSION variable) and look for that file in the user's data directory (based on the user id in their SESSION variable). If it exists it will return it to the user using XSendFile.

The problem I am having is that every time that the user tries to access the files there is a bit of a delay before they load. This is telling me that they probably are not being cached by the browser.

Why are the files not getting cached? Does it have to do with the URL parameter or the use of PHP/XSendFile?

What can I do to allow my files (images/audio) to be cached?

Update

As requested here is my get_file.php:

<?php

        session_start();

        if (empty($_SESSION['auth']['id'])){
                exit;
        }

        require_once("../../api_modules/settings.php");

        $developer_id = $_SESSION['auth']['id'];

        $type = preg_replace('/[^-a-zA-Z0-9_]/', '', $_GET['type']);
        $file = preg_replace('/[^-a-zA-Z0-9_\.]/', '', $_GET['file']);

        $file = preg_replace('/[\.]{2,}/', '', $file);

        $project_id = preg_replace('/[^0-9]/', '', $_GET['project_id']);
        $developer_id = preg_replace('/[^0-9]/', '', $developer_id);

        if ($type == "image")
                $file = FILEPATH ."files/$developer_id/$project_id/images/thumbs/88x88/$file";
        else if ($type == "full_image")
                $file = FILEPATH ."files/$developer_id/$project_id/images/originals/$file";
        else if ($type == "audio"){
                $file = FILEPATH ."files/$developer_id/$project_id/audio/$file";
        }
        else {
                exit;
        }


        header("X-Sendfile: $file");
        header("Content-type: application/octet-stream");
        header('Content-Disposition: attachment; filename="' . basename($file) . '"');

4 answers

0

For the record, this is mostly due to the fact the default value of session.cache_limiter is nocache.

See http://php.net/manual/en/function.session-cache-limiter.php on how to set a different value that fits your needs.

0

Попробуйте использовать этот вид заголовка (PHP кода) с, например , header('Cache-Control: must-revalidate');для скачивания личных файлов

$mime_types = array(
        'pdf' => 'application/pdf',
        'txt' => 'text/plain',
        'html' => 'text/html',
        'exe' => 'application/octet-stream',
        'zip' => 'application/zip',
        'doc' => 'application/msword',
        'xls' => 'application/vnd.ms-excel',
        'ppt' => 'application/vnd.ms-powerpoint',
        'gif' => 'image/gif',
        'png' => 'image/png',
        'jpeg' => 'image/jpg',
        'jpg' => 'image/jpg',
        'php' => 'text/plain'
        );

if ($mimeType == '') {
    $file_ext = strtolower(substr(strrchr($file_path.$file_name_gen, '.'), 1));
    if(array_key_exists($file_ext, $mime_types)) $mime_type = $mime_types[$file_ext];
    else $mime_type = 'application/force-download';
    }


ob_start();
header('Content-Disposition: attachment; filename="' . $file_name_gen . '"');
header('Content-Type: '.$mime_type);
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($file_path.$file_name));
ob_clean();

flush();
readfile($file_path.$file_name);

И использовать контроль кэша с самым лучшим вариантом для решения как

Cache-Control: max-age=<seconds>
Cache-Control: max-stale[=<seconds>]
Cache-Control: min-fresh=<seconds>
Cache-Control: no-cache 
Cache-Control: no-store
Cache-Control: no-transform
Cache-Control: only-if-cached

КСТАТИ. $file_name_genявляется подлинным именем файла на сервере и $file_nameэто имя файла пользователи видят для более файлов уединенности.

0

I think this is a duplicate.

But since there is a bounty lets try to adapt your code together with the suggested solution there.

You need to check that Apache has mod_expires and mod_headers enabled and working properly.

And then before you start real output check if file was modified:

...
$last_modified_time = filemtime($file); 
$etag = md5_file($file);
// always send headers
header("Last-Modified: ".gmdate("D, d M Y H:i:s", $last_modified_time)." GMT"); 
header("Etag: $etag"); 
// exit if not modified
if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified_time || 
    @trim($_SERVER['HTTP_IF_NONE_MATCH']) == $etag) { 
    header("HTTP/1.1 304 Not Modified"); 
    exit; 
} else {
    header("X-Sendfile: $file");
    header("Content-type: application/octet-stream");
    header('Content-Disposition: attachment; filename="' . basename($file) . '"');
}
0

How do you generate the images? Do you have a public local directory or it is loaded by a CDN (remotely)?

Maybe it can help: HOW TO CACHE IMAGES GENERATED BY PHP