file_get_contents отримує неправильні результати


10

Оновлення

Я вирішив проблему і опублікував відповідь. Однак моє рішення не є 100% ідеальним. Я волів би тільки прибрати symlinkз cacheз clearstatcache(true, $target)або , clearstatcache(true, $link)але це не працює.

Я також набагато краще запобігти кешування символьних посилань в першу чергу або видалити символьне посилання з кешу відразу після його створення. На жаль, мені не пощастило з цим. Чомусь clearstatcache(true)після створення символьної посилання не працює, вона все одно отримує кешування.

Я із задоволенням нагороду нагороджую всіх, хто може покращити мою відповідь та вирішити ці питання.

Редагувати

Я намагався оптимізувати свій код, генеруючи файл щоразу, коли clearstatcacheзапускається, так що мені потрібно очистити кеш лише один раз для кожного символьного посилання. Чомусь це не працює. clearstatcacheпотрібно викликати кожного разу, коли 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, якщо це так.
Jannes

Відповіді:


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 doc.


2

Є два кеша.

Спочатку кеш ОС, а потім кеш PHP.

У більшості випадків clearstatcache(true)раніше file_get_contents(...)це робиться.

Але іноді також потрібно очистити кеш ОС. У випадку Linux, я можу придумати два місця, які слід очистити. PageCache (1) та зубні сторони / вставки (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 як в цій проблемі ?

Спробуйте записати всі операції у файл журналу, щоб побачити, що насправді відбувається.

або можливо...

... Ви могли б взагалі обійтися без посилань ? Наприклад, зберігайте у базі даних, пам'яті, файлі SQLite або навіть у файлі JSON, зіставлення між "ім'ям файлу" та "фактичною ціллю посилання". Використовуючи, наприклад, 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тоді, коли це абсолютно необхідно, тому я написав функцію, яка очищає кеш лише тоді, коли каталог включає в себе asymlink .

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 (правда, $ 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")повертає null.
Ден Брей

Це може знадобитися змінити на 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.