file_get_contents得到错误的结果


10

更新资料

我解决了问题并发布了答案。但是,我的解决方案并非100%理想。我宁愿只symlinkcachewith clearstatcache(true, $target)或中删除clearstatcache(true, $link)但那不起作用。

我还宁愿首先避免对符号链接进行缓存,也可以在生成符号链接后立即将其从缓存中删除。不幸的是,我没有运气。由于某些原因clearstatcache(true),创建符号链接后无法正常工作,但仍会对其进行缓存。

我会很高兴地将悬赏奖励给任何能够改善我的答案并解决这些问题的人。

编辑

我试图通过每次clearstatcache运行时生成一个文件来优化我的代码,因此我只需要为每个符号链接清除一次缓存。由于某些原因,这不起作用。clearstatcache每次symlink在路径中包含a时都需要调用它,但是为什么呢?必须有一种方法可以优化我拥有的解决方案。


我使用PHP 7.3.5nginx/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我尝试使用realpathfile_get_conents和没有运气。有时仍会从缓存中加载。
丹·布雷

2
我的意思不仅是realpath,而且是clearstatcache(true); file_get_conents(realpath($fileName));
marv255 '19

尝试 在连续调用之间运行linux.die.net/man/8/updatedb命令。虽然我不确定在这种情况下如何解决php中的问题。
Jannes Botis

Answers:


3

太多取决于操作系统级别。那么如何尝试跳出框框。尝试通过读取文件的真实位置readlink并使用该真实位置路径怎么样?

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

我认为开箱即用还不够,毕竟,readlink也取决于操作系统级别的调用,并受缓存影响。
Bahram Ardalan

3

这是PHP的理想行为,您可以在这里看到它因为realpath_cache由于性能提高,PHP 用来存储文件路径,因此可以减少磁盘操作。

为了避免这种现象,也许您可​​以realpath_cache在使用该get_file_contents功能之前尝试清除

您可以尝试如下操作:


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

您可以阅读更多有关 在PHP文档上 clearstatcache的


2

有两个缓存。

首先是OS缓存,然后是PHP缓存。

在大多数情况下,clearstatcache(true)file_get_contents(...)这项工作是可以做的。

但是有时您还需要清除操作系统缓存。对于Linux,我可以想到两个地方。PageCache(1)和牙科/ inodes(2)。

这将清除这两个:

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

注意:这对于故障排除很有用,但不适用于生产中的频繁调用,因为它会清除整个OS缓存,并花一些时间重新填充缓存。


这是行不通的,它有时仍会加载缓存的值,并且我需要一个对生产中频繁调用有用的解决方案。
丹·布雷

2
@DanBray,您是否可以记录一下事情,以了解有关有时的性质的更多信息?
Bahram Ardalan

1
@DanBray,您如何检测旧值的外观?可能是因为其他测试条件导致您的测试返回了旧值,而实际值却发生了变化?
Bahram Ardalan

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或其他密钥库,可以将“文件名”与实际的符号链接目标相关联,并完全绕开OS分辨率。

根据使用情况,这甚至可能比使用符号链接更快。


我看不到这可能与nginx有什么关系,因为在php进程和本地文件系统之间似乎没有http事物。作为父进程,nginx是否具有某种意义?
Bahram Ardalan

@BahramArdalan事实是,我们不知道如何诊断问题,什么是符号链接或如何使用它们。因此可以想象,在nginx的下游检测到了内容不匹配,并且实际上可能与PHP无关。SCCCE将有很大帮助。
LSerni

是。我们必须对“如何”做一些研究。
Bahram Ardalan

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

我仍然要回答第一个答案,因为它仍然是有效的答案。我通过实现clearstatcache(true,$ filename)来改善@DanBray答案。

导致此问题的原因有两个。

首要问题

我已经发布为并在问题中进行编辑。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;

第二期

第二个问题是我需要在调用file_get_contents之前先调用clearstatcache。我只想在绝对必要时调用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
不幸的是,它对我的​​Web服务器不起作用。
丹·布雷

好吧,我将回到绘图板。@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 );
 ?>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.