file_get_contents ottiene risultati errati


10

Aggiornare

Ho risolto il problema e pubblicato una risposta. Tuttavia, la mia soluzione non è l'ideale al 100%. Preferirei di gran lunga rimuovere solo symlinkil cachecon clearstatcache(true, $target)o clearstatcache(true, $link)ma non funziona.

Preferirei molto di più impedire la memorizzazione nella cache dei collegamenti simbolici in primo luogo o rimuovere il collegamento simbolico dalla cache immediatamente dopo averlo generato. Sfortunatamente, non ho avuto fortuna con quello. Per qualche motivo, clearstatcache(true)dopo aver creato un collegamento simbolico non funziona, viene comunque memorizzato nella cache.

Felicemente assegnerò la generosità a chiunque possa migliorare la mia risposta e risolvere quei problemi.

modificare

Ho tentato di ottimizzare il mio codice generando un file ogni volta che clearstatcacheviene eseguito, quindi ho solo bisogno di cancellare la cache una volta per ogni collegamento simbolico. Per qualche motivo, questo non funziona. clearstatcachedeve essere chiamato ogni volta che un symlinkè incluso nel percorso, ma perché? Ci deve essere un modo per ottimizzare la soluzione che ho.


Sto usando PHP 7.3.5con nginx/1.16.0. A volte file_get_contentsrestituisce un valore errato quando si utilizza a symlink. Il problema è dopo aver eliminato e ricreato un collegamento simbolico, il suo vecchio valore rimane nella cache. A volte viene restituito il valore corretto, a volte il valore precedente. Sembra casuale.

Ho provato a cancellare la cache o impedire la memorizzazione nella cache con:

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

Non voglio davvero disabilitare la memorizzazione nella cache ma ho ancora bisogno del 100% di precisione con file_get_contents.

modificare

Non riesco a pubblicare il mio codice sorgente, poiché è troppo lungo e complesso, quindi ho creato un esempio minimale e riproducibile (index.php) che ricrea il problema:

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

Sembrava molto probabile che fosse un problema con la Nginxconfigurazione. Non avere queste righe può causare il problema:

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

Ecco la mia Nginxconfigurazione (puoi vedere che ho incluso le righe sopra):

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

Attualmente ho l'esempio sopra in diretta su https://www.websemantica.co.uk .

Prova ad aggiungere alcuni valori nel modulo. Dovrebbe apparire Success!in blu ogni volta. A volte viene mostrato Failure!in rosso. Si può prendere parecchi aggiornamenti di pagina per passare da Success!a Failure!o viceversa. Alla fine, verrà visualizzato Success!ogni volta, quindi ci deve essere una sorta di problema di memorizzazione nella cache.


Stavo guardando nello stesso caso e ho trovato commenti molto utili sulla realpathpagina delle funzioni . Forse potrebbe aiutarti.
marv255,

@ marv255 Ho provato a usare realpathcon file_get_conentse senza fortuna. A volte si carica ancora dalla cache.
Dan Bray,

2
Intendo non solo realpath, ma qualcosa del genereclearstatcache(true); file_get_conents(realpath($fileName));
marv255

Prova linux.die.net/man/8/updatedb esegui il comando tra chiamate consecutive. Anche se non sono sicuro di come risolvere il problema in php se questo è il caso.
Jannes Botis,

Risposte:


3

Dipende troppo dal livello del sistema operativo. Che ne dici di provare a pensare fuori dagli schemi. Che ne dici di provare a leggere la posizione reale del file readlinke di usare quel percorso di posizione reale?

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

Non penso che sia abbastanza (fuori dalla scatola), dopo tutto, readlink dipende anche dalle chiamate a livello di sistema operativo ed è influenzato dalla cache.
Bahram Ardalan,

3

Questo è il comportamento desiderato di PHP, puoi vederlo qui perché PHP usa realpath_cacheper archiviare i percorsi dei file a causa di miglioramenti delle prestazioni in modo che possa ridurre le operazioni del disco.

Al fine di evitare questo comportamento, forse è possibile provare a cancellare il realpath_cacheprima di utilizzare la get_file_contentsfunzione

Puoi provare qualcosa del genere:


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

Puoi leggere di più per clearstatcache sul documento PHP.


2

Ci sono due cache.

Prima la cache del sistema operativo e poi la cache PHP.

Nella maggior parte dei casi clearstatcache(true)prima file_get_contents(...)fa il lavoro.

Ma a volte è anche necessario svuotare la cache del sistema operativo. Nel caso di Linux, mi vengono in mente due posti da cancellare. PageCache (1) e dentries / inodes (2).

Questo cancella entrambi:

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

Nota: è utile per la risoluzione dei problemi, ma non per le chiamate frequenti in produzione poiché cancella l'intera cache del sistema operativo e costa al sistema alcuni momenti di ripopolamento della cache.


Questo non funziona, a volte carica ancora il valore memorizzato nella cache e ho bisogno di una soluzione adatta alle chiamate frequenti in produzione.
Dan Bray,

2
@DanBray, potresti registrare le cose per scoprire di più sulla natura di a volte ?
Bahram Ardalan,

1
@DanBray, e come si rileva l' aspetto del vecchio valore? Potrebbe essere che il tuo test restituisca il vecchio valore a causa di altre condizioni del test mentre il valore lì è davvero cambiato?
Bahram Ardalan,

2

"Il problema è dopo aver eliminato e ricreato un collegamento simbolico"

Come si elimina il collegamento simbolico? L'eliminazione di un file (o di un collegamento simbolico) dovrebbe cancellare automaticamente la cache.

Altrimenti, potresti vedere cosa succede se lo fai:

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

Se questo non risolve il problema, potrebbe essere forse un problema con nginx come in questo numero ?

Prova a registrare tutte le operazioni in un file di registro, per vedere cosa succede realmente .

o forse...

... potresti fare a meno dei symlink ? Ad esempio, archiviare in un database, memcache, file SQLite o persino un file JSON la mappatura tra "nome file" e "destinazione link simbolico". Usando ad esempio redis o altri keystore, è possibile associare il "nome file" al target reale del collegamento simbolico e bypassare completamente la risoluzione del sistema operativo.

A seconda del caso d'uso, questo potrebbe persino rivelarsi più veloce dell'uso dei collegamenti simbolici.


Non riuscivo a vedere come questo potesse essere correlato a nginx poiché non sembra esserci nulla http tra il processo php e il file system locale. Essere il processo genitore rende nginx in qualche modo rilevante?
Bahram Ardalan,

@BahramArdalan il fatto è che non sappiamo come sia stato diagnosticato il problema o quali siano i collegamenti simbolici o come vengano utilizzati. Quindi è concepibile che la mancata corrispondenza del contenuto sia stata rilevata a valle di nginx e potrebbe effettivamente non essere correlata a PHP. Un SCCCE sarebbe di grande aiuto.
LSerni il

Sì. Dobbiamo scavare un po 'in quella cosa "come".
Bahram Ardalan,

1

Ci sono stati due problemi che hanno causato il problema.

Prima edizione

Ho già pubblicato come e modifica nella domanda. È un problema con la configurazione di Nginx.

Queste linee:

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

necessario sostituito con:

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

Secondo numero

Il secondo problema era che dovevo chiamare clearstatcacheprima di chiamare file_get_contents. Voglio chiamare solo clearstatcachequando è assolutamente necessario, quindi ho scritto una funzione che cancella la cache solo quando la directory include 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

Lascio la mia prima risposta poiché è ancora una risposta valida. Sto migliorando la risposta di @DanBray implementando clearstatcache (true, $ nomefile).

Ci sono stati due problemi che hanno causato il problema.

Prima edizione

Ho già pubblicato come e modifica nella domanda. È un problema con la configurazione di Nginx.

Queste linee:

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

necessario sostituito con:

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

Secondo numero

Il secondo problema era che dovevo chiamare clearstatcache prima di chiamare file_get_contents. Voglio chiamare clearstatcache solo quando è assolutamente necessario, quindi ho scritto una funzione che cancella la cache solo quando la directory include un collegamento simbolico.

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

Ho testato il codice sopra con il mio web-server e ha funzionato.


1
Sfortunatamente, non funziona per me sul mio server web.
Dan Bray,

Bene, tornerò al tavolo da disegno. @DanBray
JTS,

1
Grazie mille, ma sfortunatamente, c'è poco tempo prima che scada il periodo di ricompensa. Tuttavia, se pensi alla soluzione di cui sono soddisfatto al 100%, assegnerò un premio extra. Inoltre, file_get_contents1fa parte del framework che ho creato, quindi è molto utilizzato, il che rende importante l'ottimizzazione.
Dan Bray,

$dir_go=readdir("$realPath")restituisce null.
Dan Bray,

Potrebbe essere necessario modificarlo in While($dir_go!==null)@DanBray
JTS il

0

Prova a posizionare il codice all'interno di un elemento che si aggiorna continuamente usando Jquery, oltre a forzare la riconvalida e cancellare il fermo statico. Questo codice è stato modificato dalla risposta originale di @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 );
 ?>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.