file_get_contents erhalten falsche Ergebnisse

10

Aktualisieren

Ich habe das Problem gelöst und eine Antwort gepostet. Meine Lösung ist jedoch nicht 100% ideal. Ich würde viel lieber nur symlinkdas cachemit clearstatcache(true, $target)oder entfernen, clearstatcache(true, $link)aber das funktioniert nicht.

Ich würde auch viel lieber das Zwischenspeichern von Symlinks verhindern oder den Symlink sofort nach dem Generieren aus dem Cache entfernen. Leider hatte ich damit kein Glück. Aus irgendeinem Grund clearstatcache(true)funktioniert das Erstellen eines Symlinks nach dem Erstellen nicht.

Ich werde das Kopfgeld gerne an jeden vergeben, der meine Antwort verbessern und diese Probleme lösen kann.

Bearbeiten

Ich habe versucht, meinen Code zu optimieren clearstatcache, indem ich bei jeder Ausführung eine Datei generiere , sodass ich den Cache für jeden Symlink nur einmal leeren muss. Aus irgendeinem Grund funktioniert dies nicht. clearstatcachemuss jedes Mal aufgerufen werden, wenn a symlinkin den Pfad aufgenommen wird, aber warum? Es muss eine Möglichkeit geben, meine Lösung zu optimieren.


Ich benutze PHP 7.3.5mit nginx/1.16.0. Gibt file_get_contentsbei Verwendung von a manchmal den falschen Wert zurück symlink. Das Problem ist, dass nach dem Löschen und Neuerstellen eines Symlinks sein alter Wert im Cache verbleibt. Manchmal wird der richtige Wert zurückgegeben, manchmal der alte Wert. Es erscheint zufällig.

Ich habe versucht, den Cache zu leeren oder das Caching zu verhindern mit:

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

Ich möchte das Caching nicht wirklich deaktivieren, benötige aber dennoch eine 100% ige Genauigkeit mit file_get_contents.

Bearbeiten

Ich kann meinen Quellcode nicht veröffentlichen, da er viel zu lang und komplex ist. Daher habe ich ein minimales, reproduzierbares Beispiel (index.php) erstellt, das das Problem neu erstellt:

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

Es schien sehr wahrscheinlich ein Problem mit der NginxKonfiguration zu sein. Das Fehlen dieser Leitungen kann das Problem verursachen:

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

Hier ist meine NginxKonfiguration (Sie können sehen, dass ich die obigen Zeilen eingefügt habe):

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

Derzeit habe ich das obige Beispiel live unter https://www.websemantica.co.uk .

Versuchen Sie, dem Formular einige Werte hinzuzufügen. Es sollte Success!jedes Mal blau angezeigt werden. Manchmal sind Shows Failure!in rot. Es kann einige Seitenaktualisierungen dauern, um von Success!zu Failure!oder umgekehrt zu wechseln . Irgendwann wird es Success!jedes Mal angezeigt, daher muss es eine Art Caching-Problem geben.

Dan Bray
quelle
Ich habe mich im selben Fall umgesehen und einen sehr nützlichen Kommentar auf der realpathFunktionsseite gefunden . Vielleicht könnte es dir helfen.
März 255
@ marv255 Ich habe versucht realpathmit file_get_conentsund kein Glück. Es wird immer noch manchmal aus dem Cache geladen.
Dan Bray
2
Ich meine nicht nur realpath, sondern so etwas wieclearstatcache(true); file_get_conents(realpath($fileName));
marv255
Versuchen Sie , den Befehl zwischen aufeinanderfolgenden Aufrufen unter linux.die.net/man/8/updatedb auszuführen. Obwohl ich nicht sicher bin, wie ich das Problem in PHP lösen soll, wenn dies der Fall ist.
Jannes Botis

Antworten:

3

Es hängt zu sehr vom Betriebssystem ab. Wie wäre es also mit einem Versuch, die Schachtel zu überdenken? Wie wäre es, wenn Sie versuchen, den tatsächlichen Speicherort der Datei zu lesen readlinkund diesen Pfad für den tatsächlichen Speicherort zu verwenden?

$realPath = shell_exec("readlink " . $yourSymlink);
$fileContent = file_get_contents($realPath);
Vo Kim Nguyen
quelle
Ich denke nicht, dass das sofort ausreicht, schließlich hängt Readlink auch von Aufrufen auf Betriebssystemebene ab und wird vom Cache beeinflusst.
Bahram Ardalan
3

Dies ist das gewünschte Verhalten von PHP. Sie können dies hier sehen, da PHP realpath_cachedie Dateipfade aufgrund von Leistungsverbesserungen speichert , um die Festplattenoperationen zu reduzieren.

Um dieses Verhalten zu vermeiden, können Sie möglicherweise versuchen, das zu löschen, realpath_cachebevor Sie die get_file_contentsFunktion verwenden

Sie können so etwas ausprobieren:


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

Sie können mehr für clearstatcache auf PHP doc lesen .

Touqeer Shafi
quelle
2

Es gibt zwei Caches.

Zuerst der Betriebssystem-Cache und dann der PHP-Cache.

In den meisten Fällen erledigt clearstatcache(true)vorher file_get_contents(...)die Arbeit.

Manchmal müssen Sie aber auch den Betriebssystem-Cache leeren. Im Falle von Linux kann ich mir zwei Stellen zum Löschen vorstellen. PageCache (1) und Einträge / Inodes (2).

Dies löscht beide:

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

Hinweis: Dies ist gut für die Fehlerbehebung, jedoch nicht für häufige Aufrufe in der Produktion, da der gesamte Betriebssystem-Cache geleert wird und das System einige Momente der Cache-Neupopulation kostet.

Bahram Ardalan
quelle
Dies funktioniert nicht, es lädt immer noch manchmal den zwischengespeicherten Wert und ich brauche eine Lösung, die für häufige Aufrufe in der Produktion gut ist.
Dan Bray
2
@DanBray, könnten Sie Dinge protokollieren, um mehr über die Natur von manchmal herauszufinden ?
Bahram Ardalan
1
@ DanBray, und wie erkennt man das Aussehen des alten Wertes? Könnte es sein, dass Ihr Test aufgrund anderer Testbedingungen den alten Wert zurückgibt, während sich der Wert dort wirklich geändert hat?
Bahram Ardalan
2

"Das Problem ist nach dem Löschen und Neuerstellen eines Symlinks"

Wie löscht man den Symlink? Das Löschen einer Datei (oder eines Symlinks) sollte den Cache automatisch leeren.

Andernfalls könnten Sie sehen, was passiert, wenn Sie Folgendes tun:

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

Wenn dies das Problem nicht löst, könnte es möglicherweise ein Problem mit Nginx wie in dieser Ausgabe sein ?

Versuchen Sie, alle Vorgänge in einer Protokolldatei zu protokollieren, um zu sehen, was tatsächlich passiert.

oder vielleicht...

... könnten Sie ganz auf Symlinks verzichten ? Speichern Sie beispielsweise die Zuordnung zwischen "Dateiname" und "tatsächlichem Symlink-Ziel" in einer Datenbank, einem Memcache, einer SQLite-Datei oder sogar einer JSON-Datei. Wenn Sie beispielsweise Redis oder andere Schlüsselspeicher verwenden, können Sie den "Dateinamen" dem tatsächlichen Symlink-Ziel zuordnen und die Betriebssystemauflösung vollständig umgehen.

Je nach Anwendungsfall ist dies möglicherweise sogar schneller als die Verwendung von Symlinks.

LSerni
quelle
Ich konnte nicht sehen, wie dies mit Nginx zusammenhängt, da es zwischen dem PHP-Prozess und dem lokalen Dateisystem keine http-Sache zu geben scheint. Ist es irgendwie relevant, Nginx als übergeordneter Prozess zu haben?
Bahram Ardalan
@BahramArdalan Tatsache ist, wir wissen nicht, wie das Problem diagnostiziert wurde oder was die Symlinks sind oder wie sie verwendet werden. Es ist daher denkbar, dass die Inhaltsinkongruenz stromabwärts von Nginx erkannt wurde und tatsächlich nicht mit PHP zusammenhängt. Ein SCCCE wäre eine große Hilfe.
LSerni
Ja. Wir müssen uns ein wenig mit diesem "Wie" beschäftigen.
Bahram Ardalan
1

Es gab zwei Probleme, die das Problem verursachten.

Erste Ausgabe

Ich habe bereits als gepostet und in der Frage bearbeitet. Es ist ein Problem mit der Nginx-Konfiguration.

Diese Zeilen:

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

benötigt ersetzt durch:

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

Zweite Ausgabe

Das zweite Problem war, dass ich anrufen musste, clearstatcachebevor ich anrief file_get_contents. Ich möchte nur aufrufen, clearstatcachewenn es absolut notwendig ist, deshalb habe ich eine Funktion geschrieben, die den Cache nur löscht, wenn das Verzeichnis a enthält symlink.

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

Ich hinterlasse meine erste Antwort, da es sich immer noch um eine gültige Antwort handelt. Ich verbessere die @ DanBray-Antwort durch Implementierung von clearstatcache (true, $ filename).

Es gab zwei Probleme, die das Problem verursachten.

Erste Ausgabe

Ich habe bereits als gepostet und in der Frage bearbeitet. Es ist ein Problem mit der Nginx-Konfiguration.

Diese Zeilen:

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

benötigt ersetzt durch:

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

Zweite Ausgabe

Das zweite Problem war, dass ich clearstatcache aufrufen musste, bevor ich file_get_contents aufrief. Ich möchte clearstatcache nur aufrufen, wenn dies unbedingt erforderlich ist. Daher habe ich eine Funktion geschrieben, die den Cache nur löscht, wenn das Verzeichnis einen Symlink enthält.

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

Ich habe den obigen Code mit meinem Webserver getestet und es hat funktioniert.

JTS
quelle
1
Leider funktioniert es bei mir auf meinem Webserver nicht.
Dan Bray
Nun, ich werde zum Zeichenbrett zurückkehren. @ DanBray
JTS
1
Vielen Dank, aber leider bleibt nur sehr wenig Zeit, bis die Kopfgeldfrist abläuft. Wenn Sie jedoch an eine Lösung denken, mit der ich zu 100% zufrieden bin, werde ich ein zusätzliches Kopfgeld vergeben. Ist auch file_get_contents1Teil des Frameworks, das ich erstellt habe, daher wird es häufig verwendet, was die Optimierung wichtig macht.
Dan Bray
$dir_go=readdir("$realPath")gibt null zurück.
Dan Bray
Das muss möglicherweise in While($dir_go!==null)@DanBray
JTS
0

Versuchen Sie, den Code in ein Element einzufügen, das mit Jquery ständig aktualisiert wird, und erzwingen Sie eine erneute Validierung und löschen Sie den statischen Fang. Dieser Code wurde gegenüber der ursprünglichen Antwort von @naveed geändert .

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 );
 ?>
JTS
quelle