file_get_contents nhận kết quả sai


10

Cập nhật

Tôi đã giải quyết vấn đề và đăng một câu trả lời. Tuy nhiên, giải pháp của tôi không lý tưởng 100%. Tôi chỉ muốn loại bỏ symlinkkhỏi cachevới clearstatcache(true, $target)hoặc clearstatcache(true, $link)nhưng điều đó không làm việc.

Tôi cũng muốn ngăn chặn bộ đệm của các liên kết tượng trưng ở vị trí đầu tiên hoặc loại bỏ liên kết tượng trưng khỏi bộ đệm ngay sau khi tạo nó. Thật không may, tôi đã không có may mắn với điều đó. Vì một số lý do clearstatcache(true)sau khi tạo symlink không hoạt động, nó vẫn được lưu trữ.

Tôi sẽ vui vẻ trao tiền thưởng cho bất cứ ai có thể cải thiện câu trả lời của tôi và giải quyết những vấn đề đó.

Biên tập

Tôi đã cố gắng tối ưu hóa mã của mình bằng cách tạo tệp mỗi clearstatcachelần chạy, do đó tôi chỉ cần xóa bộ đệm một lần cho mỗi liên kết tượng trưng. Vì một số lý do, điều này không hoạt động. clearstatcachecần được gọi mỗi khi a symlinkbao gồm trong đường dẫn, nhưng tại sao? Phải có một cách để tối ưu hóa giải pháp tôi có.


Tôi đang sử dụng PHP 7.3.5với nginx/1.16.0. Đôi khi file_get_contentstrả về giá trị sai khi sử dụng a symlink. Vấn đề là sau khi xóa và tạo lại một liên kết tượng trưng, ​​giá trị cũ của nó vẫn nằm trong bộ đệm. Đôi khi giá trị đúng được trả về, đôi khi giá trị cũ. Nó xuất hiện ngẫu nhiên.

Tôi đã cố gắng xóa bộ nhớ cache hoặc ngăn bộ đệm với:

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

Tôi thực sự không muốn tắt bộ nhớ đệm nhưng tôi vẫn cần độ chính xác 100% với file_get_contents.

Biên tập

Tôi không thể đăng mã nguồn của mình, vì nó quá dài và phức tạp, vì vậy tôi đã tạo ra một ví dụ tối thiểu, có thể lặp lại (index.php) để tạo lại vấn đề:

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

Nó dường như rất có thể là một vấn đề với Nginxcấu hình. Không có những dòng này có thể gây ra vấn đề:

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

Đây là Nginxcấu hình của tôi (bạn có thể thấy tôi đã bao gồm các dòng trên):

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;
}

Hiện tại tôi có ví dụ trên trực tiếp tại https://www.websemantica.co.uk .

Hãy thử thêm một vài giá trị trong biểu mẫu. Nó sẽ hiển thị Success!màu xanh mỗi lần. Đôi khi được hiển thị Failure!màu đỏ. Có thể mất khá nhiều lần làm mới trang để thay đổi từ Success!sang Failure!hoặc ngược lại. Cuối cùng, nó sẽ hiển thị Success!mọi lúc, do đó phải có một số loại vấn đề bộ đệm.


Tôi đã tìm kiếm xung quanh trường hợp tương tự và tìm thấy nhận xét rất hữu ích trên realpathtrang chức năng . Có lẽ nó có thể giúp bạn.
marv255

@ marv255 tôi đã cố gắng sử dụng realpathvới file_get_conentsvà không may mắn. Nó vẫn đôi khi tải từ bộ đệm.
Dan Bray

2
Ý tôi không chỉ realpath, mà là một cái gì đó giống nhưclearstatcache(true); file_get_conents(realpath($fileName));
marv255

Hãy thử linux.die.net/man/8/updatedb chạy lệnh giữa các cuộc gọi liên tiếp. Mặc dù tôi không chắc chắn làm thế nào để giải quyết vấn đề trong php nếu đây là trường hợp.
Jannes Botis

Câu trả lời:


3

Nó phụ thuộc quá nhiều vào cấp độ hệ điều hành. Vì vậy, làm thế nào về cố gắng nghĩ ra hộp. Làm thế nào về việc cố gắng đọc vị trí thực của tệp bằng readlinkvà sử dụng đường dẫn vị trí thực đó?

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

Tôi không nghĩ rằng đã đủ (hết hộp), sau tất cả, đường dẫn đọc cũng phụ thuộc vào các cuộc gọi ở cấp độ hệ điều hành và bị ảnh hưởng bởi bộ đệm.
Bahram Ardalan

3

Đây là hành vi mong muốn của PHP bạn có thể thấy điều này ở đây vì PHP sử dụng realpath_cacheđể lưu trữ các đường dẫn tệp do cải tiến hiệu suất để có thể giảm hoạt động của đĩa.

Để tránh hành vi này, có thể bạn có thể cố gắng xóa realpath_cachetrước khi sử dụng get_file_contentschức năng

Bạn có thể thử một cái gì đó như thế này:


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

Bạn có thể đọc thêm cho Clearstatcache trên tài liệu PHP.


2

Có hai bộ đệm.

Đầu tiên là bộ đệm hệ điều hành và sau đó là bộ đệm PHP.

Trong hầu hết các trường hợp clearstatcache(true)trước khi file_get_contents(...)làm công việc.

Nhưng đôi khi bạn cũng cần xóa bộ nhớ cache của hệ điều hành. Trong trường hợp của Linux, tôi có thể nghĩ ra hai nơi để xóa. PageCache (1) và răng / inodes (2).

Điều này xóa cả hai:

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

Lưu ý: Điều này tốt cho việc khắc phục sự cố nhưng không phải cho các cuộc gọi thường xuyên trong sản xuất vì nó xóa toàn bộ bộ đệm của hệ điều hành và khiến hệ thống mất một vài phút để lưu lại bộ đệm.


Điều này không hoạt động, đôi khi nó vẫn tải giá trị được lưu trong bộ nhớ cache và tôi cần một giải pháp tốt cho các cuộc gọi thường xuyên trong sản xuất.
Dan Bray

2
@DanBray, bạn có thể đăng nhập mọi thứ để tìm hiểu thêm về bản chất của đôi khi không?
Bahram Ardalan

1
@DanBray, và làm thế nào để bạn phát hiện sự xuất hiện của giá trị cũ? Có thể là thử nghiệm của bạn trả về giá trị cũ do các điều kiện thử nghiệm khác trong khi giá trị ở đó đã thực sự thay đổi?
Bahram Ardalan

2

"Vấn đề là sau khi xóa và tạo lại một liên kết tượng trưng"

Làm thế nào để bạn xóa symlink? Xóa một tập tin (hoặc một liên kết tượng trưng) sẽ tự động xóa bộ đệm.

Mặt khác, bạn có thể thấy những gì sẽ xảy ra nếu bạn làm:

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

Nếu điều này không giải quyết được vấn đề, có lẽ nó có thể là vấn đề với nginx như trong vấn đề này ?

Hãy thử đăng nhập tất cả các hoạt động vào một tệp nhật ký, để xem những gì thực sự xảy ra.

hoặc có thể...

... bạn có thể làm mà không có liên kết tượng trưng hoàn toàn ? Ví dụ: lưu trữ trong cơ sở dữ liệu, memcache, tệp SQLite hoặc thậm chí là tệp JSON ánh xạ giữa "tên tệp" và "mục tiêu liên kết tượng trưng thực tế". Sử dụng ví dụ redis hoặc các kho khóa khác, bạn có thể liên kết "tên tệp" với mục tiêu liên kết tượng trưng thực sự và bỏ qua hoàn toàn độ phân giải HĐH.

Tùy thuộc vào trường hợp sử dụng, điều này thậm chí có thể nhanh hơn so với sử dụng liên kết tượng trưng.


Tôi không thể thấy làm thế nào điều này có thể liên quan đến nginx vì dường như không có điều http giữa quy trình php và hệ thống tệp cục bộ. Là quá trình cha mẹ làm cho nginx bằng cách nào đó có liên quan?
Bahram Ardalan

@BahramArdalan thực tế là, chúng tôi không biết vấn đề được chẩn đoán như thế nào hoặc các liên kết tượng trưng là gì hoặc chúng được sử dụng như thế nào. Vì vậy, có thể hình dung rằng sự không phù hợp nội dung được phát hiện xuôi dòng từ nginx và thực sự có thể không liên quan đến PHP. Một SCCCE sẽ giúp đỡ rất nhiều.
LSerni

Đúng. Chúng ta phải đào sâu một chút vào thứ "thế nào".
Bahram Ardalan

1

Có hai vấn đề gây ra vấn đề.

Vấn đề đầu tiên

Tôi đã đăng và chỉnh sửa trong câu hỏi. Đó là một vấn đề với cấu hình Nginx.

Những dòng này:

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

cần thay thế bằng:

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

Vấn đề thứ hai

Vấn đề thứ hai là tôi cần gọi clearstatcachetrước khi gọi file_get_contents. Tôi chỉ muốn gọi clearstatcachekhi thật cần thiết, vì vậy tôi đã viết một hàm chỉ xóa bộ đệm khi thư mục chứa a symlink.

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

1

Tôi đang để lại câu trả lời đầu tiên của mình vì nó vẫn là một câu trả lời hợp lệ. Tôi đang cải thiện câu trả lời @DanBray bằng cách triển khai Clearstatcache (tên thật, $ filename).

Có hai vấn đề gây ra vấn đề.

Vấn đề đầu tiên

Tôi đã đăng và chỉnh sửa trong câu hỏi. Đó là một vấn đề với cấu hình Nginx.

Những dòng này:

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

cần thay thế bằng:

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

Vấn đề thứ hai

Vấn đề thứ hai là tôi cần phải gọi Clearstatcache trước khi gọi file_get_contents. Tôi chỉ muốn gọi Clearstatcache khi thật cần thiết, vì vậy tôi đã viết một chức năng chỉ xóa bộ nhớ cache khi thư mục có chứa một liên kết tượng trưng.

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);
}

Tôi đã thử nghiệm mã trên với máy chủ web của mình và nó đã hoạt động.


1
Thật không may, nó không hoạt động với tôi trên máy chủ web của tôi.
Dan Bray

Vâng, tôi sẽ trở lại bảng vẽ. @DanBray
JTS

1
Cảm ơn bạn rất nhiều, nhưng thật không may, có rất ít thời gian trước khi thời hạn tiền thưởng hết hạn. Tuy nhiên, nếu bạn nghĩ đến giải pháp tôi hài lòng 100%, tôi sẽ thưởng thêm một khoản tiền thưởng. Ngoài ra, file_get_contents1là một phần của khung tôi đã thực hiện, vì vậy nó được sử dụng rất nhiều, điều này làm cho việc tối ưu hóa trở nên quan trọng.
Dan Bray

$dir_go=readdir("$realPath")trả về null.
Dan Bray

Điều đó có thể cần thay đổi thành While($dir_go!==null)@DanBray
JTS

0

Hãy thử đặt mã bên trong một phần tử được làm mới liên tục bằng cách sử dụng Jquery cũng như buộc xác nhận lại và xóa bắt tĩnh. Mã này đã được sửa đổi từ câu trả lời ban đầu @naveed .

mẫu.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>

hồ sơ.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 );
 ?>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.