file_get_contents получая неправильные результаты


10

Обновить

Я решил проблему и отправил ответ. Тем не менее, мое решение не на 100% идеально. Я бы предпочел только удалить symlinkиз cacheс clearstatcache(true, $target)или, clearstatcache(true, $link)но это не работает.

Я также предпочел бы предотвратить кеширование символических ссылок в первую очередь или удалить символическую ссылку из кэша сразу после ее генерации. К сожалению, мне не повезло с этим. По какой-то причине clearstatcache(true)после создания символическая ссылка не работает, она все еще кэшируется.

Я с радостью присужду награду любому, кто сможет улучшить мой ответ и решить эти проблемы.

редактировать

Я пытался оптимизировать свой код, генерируя файл при каждом clearstatcacheзапуске, поэтому мне нужно очищать кэш только один раз для каждой символической ссылки. По какой-то причине это не работает. clearstatcacheнужно вызывать каждый раз, когда a symlinkвключается в путь, но почему? Должен быть способ оптимизировать решение, которое у меня есть.


Я использую PHP 7.3.5с nginx/1.16.0. Иногда file_get_contentsвозвращает неправильное значение при использовании symlink. Проблема в том, что после удаления и повторного создания символической ссылки ее старое значение остается в кэше. Иногда возвращается правильное значение, иногда старое значение. Это кажется случайным.

Я пытался очистить кеш или предотвратить кеширование с помощью:

function symlink1($target, $link)
{
    realpath_cache_size(0);
    symlink($target, $link);
    //clearstatcache(true);
}

Я действительно не хочу отключать кеширование, но мне все еще нужна 100% точность с file_get_contents.

редактировать

Я не могу опубликовать свой исходный код, так как он слишком длинный и сложный, поэтому я создал минимальный воспроизводимый пример (index.php), который воссоздает проблему:

<h1>Symlink Problem</h1>
<?php
    $dir = getcwd();
    if (isset($_POST['clear-all']))
    {
        $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
        foreach ($nos as $no)
        {
            unlink($dir.'/nos/'.$no.'/id.txt');
            rmdir($dir.'/nos/'.$no);
        }
        foreach (array_values(array_diff(scandir($dir.'/ids'), array('..', '.'))) as $id)
            unlink($dir.'/ids/'.$id);
    }
    if (!is_dir($dir.'/nos'))
        mkdir($dir.'/nos');
    if (!is_dir($dir.'/ids'))
        mkdir($dir.'/ids');
    if (isset($_POST['submit']) && !empty($_POST['id']) && ctype_digit($_POST['insert-after']) && ctype_alnum($_POST['id']))
    {
        $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
        $total = count($nos);
        if ($total <= 100)
        {
            for ($i = $total; $i >= $_POST['insert-after']; $i--)
            {
                $id = file_get_contents($dir.'/nos/'.$i.'/id.txt');
                unlink($dir.'/ids/'.$id);
                symlink($dir.'/nos/'.($i + 1), $dir.'/ids/'.$id);
                rename($dir.'/nos/'.$i, $dir.'/nos/'.($i + 1));
            }
            echo '<br>';
            mkdir($dir.'/nos/'.$_POST['insert-after']);
            file_put_contents($dir.'/nos/'.$_POST['insert-after'].'/id.txt', $_POST['id']);
            symlink($dir.'/nos/'.$_POST['insert-after'], $dir.'/ids/'.$_POST['id']);
        }
    }
    $nos = array_values(array_diff(scandir($dir.'/nos'), array('..', '.')));
    $total = count($nos) + 1;
    echo '<h2>Ids from nos directory</h2>';
    foreach ($nos as $no)
    {
        echo ($no + 1).':'.file_get_contents("$dir/nos/$no/id.txt").'<br>';
    }
    echo '<h2>Ids from using symlinks</h2>';
    $ids = array_values(array_diff(scandir($dir.'/ids'), array('..', '.')));
    if (count($ids) > 0)
    {
        $success = true;
        foreach ($ids as $id)
        {
            $id1 = file_get_contents("$dir/ids/$id/id.txt");
            echo $id.':'.$id1.'<br>';
            if ($id !== $id1)
                $success = false;
        }
        if ($success)
            echo '<b><font color="blue">Success!</font></b><br>';
        else
            echo '<b><font color="red">Failure!</font></b><br>';
    }
?>
<br>
<h2>Insert ID after</h2>
<form method="post" action="/">
    <select name="insert-after">
        <?php
            for ($i = 0; $i < $total; $i++)
                echo '<option value="'.$i.'">'.$i.'</option>';
        ?>
    </select>
    <input type="text" placeholder="ID" name="id"><br>
    <input type="submit" name="submit" value="Insert"><br>
</form>
<h2>Clear all</h2>
<form method="post" action="/">
    <input type="submit" name="clear-all" value="Clear All"><br>
</form>
<script>
    if (window.history.replaceState)
    {
        window.history.replaceState( null, null, window.location.href );
    }
</script>

Казалось, очень вероятно, что проблема с Nginxконфигурацией. Отсутствие этих строк может вызвать проблему:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

Вот моя Nginxконфигурация (вы можете видеть, что я включил вышеупомянутые строки):

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.websemantica.co.uk;
    root "/path/to/site/root";
    index index.php;

    location / {
        try_files $uri $uri/ $uri.php$is_args$query_string;
    }

    location ~* \.php$ {
        try_files $uri =404;
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_param   QUERY_STRING            $query_string;
        fastcgi_param   REQUEST_METHOD          $request_method;
        fastcgi_param   CONTENT_TYPE            $content_type;
        fastcgi_param   CONTENT_LENGTH          $content_length;

        fastcgi_param   SCRIPT_FILENAME         $realpath_root$fastcgi_script_name;
        fastcgi_param   SCRIPT_NAME             $fastcgi_script_name;
        fastcgi_param   PATH_INFO               $fastcgi_path_info;
        fastcgi_param   PATH_TRANSLATED         $realpath_root$fastcgi_path_info;
        fastcgi_param   REQUEST_URI             $request_uri;
        fastcgi_param   DOCUMENT_URI            $document_uri;
        fastcgi_param   DOCUMENT_ROOT           $realpath_root;
        fastcgi_param   SERVER_PROTOCOL         $server_protocol;

        fastcgi_param   GATEWAY_INTERFACE       CGI/1.1;
        fastcgi_param   SERVER_SOFTWARE         nginx/$nginx_version;

        fastcgi_param   REMOTE_ADDR             $remote_addr;
        fastcgi_param   REMOTE_PORT             $remote_port;
        fastcgi_param   SERVER_ADDR             $server_addr;
        fastcgi_param   SERVER_PORT             $server_port;
        fastcgi_param   SERVER_NAME             $server_name;

        fastcgi_param   HTTPS                   $https;

        # PHP only, required if PHP was built with --enable-force-cgi-redirect
        fastcgi_param   REDIRECT_STATUS         200;

        fastcgi_index index.php;
        fastcgi_read_timeout 3000;
    }

    if ($request_uri ~ (?i)^/([^?]*)\.php($|\?)) {
        return 301 /$1$is_args$args;
    }
    rewrite ^/index$ / permanent;
    rewrite ^/(.*)/$ /$1 permanent;
}

В настоящее время у меня есть приведенный выше пример в прямом эфире на https://www.websemantica.co.uk .

Попробуйте добавить несколько значений в форму. Он должен отображаться Success!синим цветом каждый раз. Иногда это показывает Failure!в красном. Это может занять довольно много обновления страницы , чтобы перейти от Success!к Failure!или наоборот. В конце концов, это будет показываться Success!каждый раз, поэтому должна быть какая-то проблема с кэшированием.


Я осматривал тот же случай и нашел очень полезный комментарий на realpathстранице функций . Может быть, это может помочь вам.
marv255

@ marv255 Я пытался использовать realpathс file_get_conentsи не повезло. Он все еще иногда загружается из кеша.
Дэн Брей

2
Я имею в виду не только realpath, но что-то вродеclearstatcache(true); file_get_conents(realpath($fileName));
marv255

Попробуйте linux.die.net/man/8/updatedb запустить команду между последовательными вызовами. Хотя я не уверен, как решить проблему в php, если это так.
Яннес Ботис

Ответы:


3

Это слишком сильно зависит от уровня ОС. Так как насчет того, чтобы попытаться придумать коробку. Как насчет того, чтобы попытаться прочитать реальное местоположение файла readlinkи использовать этот реальный путь к файлу?

$realPath = shell_exec("readlink " . $yourSymlink);
$fileContent = file_get_contents($realPath);

Я не думаю, что этого достаточно (из коробки), в конце концов, readlink также зависит от вызовов уровня ОС и зависит от кеша.
Бахрам Ардалан

3

Это желаемое поведение PHP, вы можете увидеть это здесь, потому что PHP использует realpath_cacheдля хранения путей к файлам из-за повышения производительности, чтобы он мог уменьшить операции на диске.

Чтобы избежать такого поведения, возможно, вы можете попытаться очистить realpath_cacheперед использованием get_file_contentsфункции

Вы можете попробовать что-то вроде этого:


clearstatcache();
$data = file_get_contents("Your File");

Вы можете прочитать больше о clearstatcache на PHP документ.


2

Есть два кеша.

Сначала кеш ОС, а затем кеш PHP.

В большинстве случаев clearstatcache(true)раньше file_get_contents(...)делает работу.

Но иногда вам также необходимо очистить кэш ОС. В случае с Linux я могу подумать о двух местах, которые нужно очистить. PageCache (1) и dentries / inode (2).

Это очищает оба:

shell_exec('echo 3 > /proc/sys/vm/drop_caches')

Примечание. Это полезно для устранения неполадок, но не для частых вызовов в производственном процессе, поскольку оно очищает весь кэш ОС и обходится системе в несколько секунд для повторного заполнения кеша.


Это не работает, все равно иногда загружает кэшированное значение, и мне нужно решение, подходящее для частых вызовов в работе.
Дэн Брей

2
@DanBray, не могли бы вы регистрировать вещи, чтобы узнать больше о природе иногда ?
Бахрам Ардалан

1
@DanBray, а как вы определяете появление старого значения? Может ли быть так, что ваш тест возвращает старое значение из-за других условий теста, в то время как значение там действительно изменилось?
Бахрам Ардалан

2

«Проблема после удаления и воссоздания символической ссылки»

Как удалить символическую ссылку? Удаление файла (или символической ссылки) должно автоматически очистить кеш.

В противном случае вы можете увидеть, что произойдет, если вы сделаете:

// This has "race condition" written all around it
unlink($link);
touch($link);
unlink($link); // Remove the empty file
symlink($target, $link);

Если это не решит проблему, возможно, это проблема с nginx, как в этом выпуске ?

Попробуйте записать все операции в файл журнала, чтобы увидеть, что на самом деле происходит.

или, может быть...

... не могли бы вы вообще обойтись без символических ссылок ? Например, сохраните в базе данных, memcache, файле SQLite или даже в файле JSON соответствие между «filename» и «фактической целью символической ссылки». Используя, например, redis или другие хранилища ключей, вы можете связать «имя файла» с реальной целью символической ссылки и полностью обойти разрешение ОС.

В зависимости от варианта использования это может даже оказаться быстрее, чем использование символических ссылок.


Я не мог понять, как это может относиться к nginx, так как между процессом php и локальной файловой системой, похоже, нет http. Является ли родительский процесс делает nginx как-то актуальным?
Бахрам Ардалан

@BahramArdalan на самом деле, мы не знаем, как проблема была диагностирована или что символические ссылки или как они используются. Таким образом, возможно, что несоответствие контента было обнаружено вниз по течению от nginx, и на самом деле может не иметь отношения к PHP. SCCCE был бы очень полезен.
LSerni

Да. Нам нужно немного покопаться в этом "как".
Бахрам Ардалан

1

Были две проблемы, которые вызвали проблему.

Первый выпуск

Я уже выкладывал как и редактирую в вопросе. Это проблема с конфигурацией Nginx.

Эти строки:

fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;

необходимо заменить на:

fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $realpath_root;

Второй выпуск

Второй вопрос - мне нужно было позвонить clearstatcacheперед звонком file_get_contents. Я хочу вызывать только clearstatcacheтогда, когда это абсолютно необходимо, поэтому я написал функцию, которая очищает кэш только тогда, когда каталог включает в себя symlink.

function file_get_contents1($dir)
{
    $realPath = realpath($dir);
    if ($realPath === false)
        return '';
    if ($dir !== $realPath)
    {
        clearstatcache(true);
    }
    return file_get_contents($dir);
}

1

Я оставляю свой первый ответ, так как это все еще правильный ответ. Я улучшаю ответ @DanBray путем реализации clearstatcache (true, $ filename).

Были две проблемы, которые вызвали проблему.

Первый выпуск

Я уже выкладывал как и редактирую в вопросе. Это проблема с конфигурацией Nginx.

Эти строки:

fastcgi_param SCRIPT_FILENAME $ document_root $ fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $ document_root;

необходимо заменить на:

fastcgi_param SCRIPT_FILENAME $ realpath_root $ fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $ realpath_root;

Второй выпуск

Во-вторых, мне нужно было вызвать clearstatcache перед вызовом file_get_contents. Я хочу вызывать clearstatcache только тогда, когда это абсолютно необходимо, поэтому я написал функцию, которая очищает кэш только тогда, когда каталог содержит символическую ссылку.

function file_get_contents1234_hard_drives($dir_go_1){
    $realPath = realpath($dir_go_1);
        $myDirectory=opendir(dirname($realPath));        
        while($entryName=readdir($myDirectory)) {
          $dirArray[]=$entryName;
        }

        /* Finds extensions of files used for my site theelectronichandbook.tech
        function findexts ($filename) {
          $filename=strtolower($filename);
          $exts=split("[/\\.]", $filename);
          $n=count($exts)-1;
          $exts=$exts[$n];
          return $exts;
        }*/

        // Closes directory
        closedir($myDirectory);

        // Counts elements in array
        $indexCount=count($dirArray);
        for($ArPos=1;$ArPos<=$indexCount;$ArPos++){
            /*used for my site theelectronichandbook.tech
            if($_SERVER['QUERY_STRING']=="hidden"){
                $H="";
                $af="./";
                $atext="Hide";
            }else{
                $H=".";
                $af="./?hidden";
                $at="Show";
            }*/
            if(strpos($dirArray[$ArPos], "Symlink") !== false){
                clearstatcache(true,$dir_go_1);
            }
        }
    return file_get_contents($dir_go_1);
}

Я протестировал приведенный выше код на своем веб-сервере, и он заработал.


1
К сожалению, у меня не работает на моем веб-сервере.
Дэн Брей

Хорошо, я вернусь к доске для рисования. @DanBray
JTS

1
Большое спасибо, но, к сожалению, до истечения периода вознаграждения осталось совсем немного времени. Однако, если вы подумаете о решении, которым я доволен на 100%, я буду награждать дополнительную награду. Кроме того, file_get_contents1это часть фреймворка, который я сделал, поэтому он часто используется, что делает оптимизацию важной.
Дэн Брей

$dir_go=readdir("$realPath")возвращает ноль
Дэн Брей

Это может потребоваться изменить на While($dir_go!==null)@DanBray
JTS

0

Попробуйте поместить код в элемент, который постоянно обновляется с помощью Jquery, а также принудительно выполнить повторную проверку и очистить статический улов. Этот код был изменен с оригинального ответа @naveed .

form.php:

 <meta http-equiv="Cache-Control" content="no-store, must-revalidate" />
 <meta http-equiv="Expires" content="0"/>
 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
 <script> 
 jQuery(document).ready(function(){
    jQuery('.ajaxform').submit( function() {
        $.ajax({
            url     : $(this).attr('action'),
            type    : $(this).attr('method'),
            dataType: 'json',
            data    : $(this).serialize(),
            success : function( data ) {
                        // loop to set the result(value)
                        // in required div(key)
                        for(var id in data) {
                            jQuery('#' + id).html( data[id] );
                        }
                      }
        });
        return false;
    });
});
var timer, delay = 30;
timer = setInterval(function(){
    $.ajax({
      type    : 'POST',
      url     : 'profile.php',
      dataType: 'json',
      data    : $('.ajaxform').serialize(),
      success : function(data){
                  for(var id in data) {
                    jQuery('#' + id).html( data[id] );
                  }
                }
    }); }, delay);
 </script>
 <form action='profile.php' method='post' class='ajaxform'></form>
 <div id='result'></div>

profile.php:

 <?php
       // All form data is in $_POST
       // Now perform actions on form data here and create an result array something like this
       clearstatcache();
       $arr = array( 'result' => file_get_contents("./myfile.text") );
       echo json_encode( $arr );
 ?>
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.