[PHP/HTTP] Cache und Location

Talion

...
ID: 33759
L
20 April 2006
794
75
Hallo,

ich stehe derzeit vor einem etwas ungewöhnlichen Problem, nämlich dass der IE das tut was ich von ihm erwarte aber Firefox nicht.

Folgende (vereinfachte) Situation:
https://xyz/random.php leitet auf ein zufällig ausgewähltes, php-generiertes Bild weiter:
GET /random.php HTTP/1.1

HTTP/1.x 303 See Other
Pragma: no-cache
Cache-Control: no-cache, must-revalidate
Expires: Thu, 01 Jan 1970 00:00:00
Location: https://xyz/bild.php?id=##zufall##
bild.php generiert nun entsprechend der Id das Bild. Die Anfrage an random.php soll NIE zwischengespeichert werden (damit jedesmal ein neues zufällig gewähltes Ergebnis kommt), aber das Bild an sich soll gecached werden (andernfalls könnte ich es ja auch ohne Weiterleitung direkt ausgeben).
W3 sagt: "The 303 response MUST NOT be cached, but the response to the second (redirected) request might be cacheable" - also genau was ich haben will. Doch weder mit 303 noch mit dem traditionellen 302 funktioniert es im Firefox.


Anfrage bei id=1:
GET /bild.php?id=1 HTTP/1.1

HTTP/1.x 200 OK
Etag: 1

Anfrage bei id=2:
GET /bild.php?id=1 HTTP/1.1

HTTP/1.x 200 OK
Etag: 1

Beide Bilder sind nun im Cache. Nun kommt die nächste Anfrage:

GET /random.php HTTP/1.1

HTTP/1.x 303 See Other
Pragma: no-cache
Cache-Control: no-cache, must-revalidate
Expires: Thu, 01 Jan 1970 00:00:00
Location: https://xyz/bild.php?id=1


GET /bild.php?id=1 HTTP/1.1
If-None-Match: 1

HTTP/1.x 304 Not Modified

Sieht alles richtig aus (für id=2 ebenfalls), aber: Firefox bezieht das 304 not modified nicht auf die bild.php?id=x sondern auf die random.php, so dass immer nur das selbe Bild angezeigt wird. Er fragt also nach bild.php?id=2, bekommt "not modified" und zeigt dann aber nicht bild2 aus dem Cache an, sondern das Bild was eben gerad da ist, egal ob 1 oder 2.
IE und Opera hingegen verstehen was von ihnen gewollt wird und reagieren richtig.
Wenn ich anstatt Etag mit nocache-headern usw. arbeite, komme ich zum selben Ergebnis.


Hat jemand einen Vorschlag bzw. Erfahrung, wie man dieses Problem lösen kann?
Die Bilder bei jeder Anfrage neu zu generieren (also nie 304 zu senden) wäre natürlich möglich, aber eigentlich unnützer Rechenaufwand.


Danke,
Talion
 
so ganz 100%ig verstehe ich dein prob nicht.

Was willst du jetzt genau? Dass das Bild gecached wird?
Dass das Bild nicht gecached wird?
Dass das bild bild.php?id=1 einen anderen cache hat wie bild.php?id=2?

falls "Dass das Bild nicht gecached wird?":

hast du den nocache auch in der bild.php? versuch das mal. Also bei der Bildausgabe noch diesen Header dazu, damit der Browser weiss dass das Bild nun nicht zwischengespeichert werden soll.

in der seite mit dem "Location:" ist eine cache direktive soweit ich weis eh sinnlos, die wird nicht gecacht.(glaub ich, testen^^)

(falls ich dein Problem verstanden habe)
 
Ich will, dass das Bild gecached wird, aber dann bitte auch das richtige Bild angezeigt wird ;)

Eine Minimalversion, die den Fehler reproduziert: https://sig.aves-reich.de/test/test.htm
PHP:
<?php

if(isset($_GET['id']))
{
    $id = intval($_GET['id']);
    
      $eTag = $id; // normalerweise wird mehr als nur die id zur berechnung des etag verwendet, aber hier reichts so
      header('ETag: '.$eTag);
      header('Cache-Control: public');
      header('Expires: ');
      
      if(isset($_SERVER['HTTP_IF_NONE_MATCH']) && (strcmp(trim($_SERVER['HTTP_IF_NONE_MATCH']), $eTag)==0))
      {
          header('HTTP/1.1 304 Not Modified');
          exit;
      }
      
    $img = imagecreatetruecolor(25,25);
    $bgcolor = imagecolorallocatealpha($img, 255,255,255, 127);
    imagefill($img, 0, 0, $bgcolor);
    $black = imagecolorallocate($img, 0, 0, 0);
    
    imagettftext($img, 10, 0, 5, 20, $black, '../arial.ttf', $id);
    header('Content-type: image/png');
    imagepng($img);
    exit;
}

if(isset($_GET['random']))
{
    $r = mt_rand(1,3);
    header('Location: https://sig.aves-reich.de/test/index.php?id='.$r, true, 303);
    exit;
}
?>

Dazu nehme man noch eine html-Datei (test.htm) mit folgendem Inhalt:
HTML:
<a href="test.htm"><img src="index.php?random"></a>

Und ein beliebiges Tool mit dem man den HTTP-Traffic beobachten kann. (livehttpheaders für den Firefox, oder allgemein sowas wie wireshark)


Man ruft nun mehrmals die test.htm und beobachtet folgendes (unwichtiges gestrichen):

*** 1. Aufruf von test.htm ***

GET /test/test.htm HTTP/1.1

HTTP/1.x 200 OK
Etag: "67c968-33-4803c912"

----------------------------------------------------------

GET /test/index.php?random HTTP/1.1
Referer: https://sig.aves-reich.de/test/test.htm

HTTP/1.x 303 See Other
Location: https://sig.aves-reich.de/test/index.php?id=2

----------------------------------------------------------

https://sig.aves-reich.de/test/index.php?id=2

GET /test/index.php?id=2 HTTP/1.1

HTTP/1.x 200 OK
Etag: 2
Cache-Control: public
Content-Type: image/png

*** Bild 2 wird angezeigt (korrekt) ***

----------------------------------------------------------

*** 2. Aufruf von test.htm ***

GET /test/test.htm HTTP/1.1
If-None-Match: "67c968-33-4803c912"

HTTP/1.x 304 Not Modified
Etag: "67c968-33-4803c912"

----------------------------------------------------------

GET /test/index.php?random HTTP/1.1

HTTP/1.x 303 See Other
Location: https://sig.aves-reich.de/test/index.php?id=1

----------------------------------------------------------

GET /test/index.php?id=1 HTTP/1.1

HTTP/1.x 200 OK
Etag: 1
Cache-Control: public
Content-Type: image/png

*** Bild 1 wird angezeigt (korrekt) ***

----------------------------------------------------------

*** 3. Aufruf von test.htm ***

GET /test/test.htm HTTP/1.1
If-None-Match: "67c968-33-4803c912"

HTTP/1.x 304 Not Modified
Etag: "67c968-33-4803c912"

----------------------------------------------------------

GET /test/index.php?random HTTP/1.1

HTTP/1.x 303 See Other
Location: https://sig.aves-reich.de/test/index.php?id=2

----------------------------------------------------------

GET /test/index.php?id=2 HTTP/1.1
If-None-Match: 2

HTTP/1.x 304 Not Modified
Etag: 2
Cache-Control: public

*** Es wird weiterhin Bild 1 angezeigt (nicht korrekt!) ***

Ganz am Ende wurde also (korrekt) abgefragt, ob das zu index.php?id=2 gehörende Dokument mit dem Etag 2 (also Bild2) noch gültig ist. Antwort vom Server: "Ja, es ist gültig (304)", also sollte der Browser es nun auch anzeigen (wie es IE und Opera auch machen).
Stattdessen zeigt Firefox aber das Bild vom vorigen Aufruf an (also Bild1), vermutlich weil Firefox die Antwort 304 auf die index.php?random bezieht, von der aus ja auf index.php?id=2 weitergeleitet wurde.

Das Problem ist also kurz gesagt: Ich möchte, dass wenn der Browser zu Bild-X weitergeleitet wird und erfolgreich überprüft, dass er Bild-X auch noch als gültige Version im Cache hat, dann soll er bitte auch Bild-X anzeigen und nicht irgendeins was zufällig vorher da war.
Ich hoffe, dass ist jetzt etwas deutlicher geworden ;)
Danke schonmal für deine Mühe. (Und du hast natürlich recht, die Cachedirektiven bei der Weiterleitung sind überflüssig, da dabei das Cachingverhalten nur davon abgeleitet wird, ob der Code 301,302,303,307 o.ä. ist.)
 
Häng doch an die Url mit der du dein Bild aufrufst einfach nen Timestam dran:

PHP:
 echo '<img src"bild.php?id=%id&anti_chache='.time().'" />';

Dann wird das Bild nicht gecached.

Mfg
Brom
 
Geht leider nicht, da das z.b. als Signatur in Foren eingesetzt werden soll. Dort kann ich ja keinen Zufallswert verwenden, sondern muss die statische URL (also https://irgendwas/random.php) angeben.
 
hmm, ich glaub für diese Situation, kannst du mod_rewrite nutzen...
Ich hab jetzt nicht geguckt, ob mod_rewrite eine Zufallsfunktion hat, aber sie würde mir ohnehin nicht viel nützen. Ich kann ja nicht einfach eine zufällige ID ausgeben, weil 1. nur IDs ausgegeben werden sollen die zu einem bestimmten User gehören, und 2. auch gelöschte IDs dabei sein könnten. Die Auswahl der Zufallszahl muss also mit Zugriff auf die Datenbank geschehen.
Und wenn das noch auf die Idee mit dem Timestamp anhängen bezogen war: Wenn der von mod_rewrite intern angehängt wird, interessiert den Browser das ja nicht besonders, weil er es nichtmal mitbekommt ;)


Andere möglichkeit ist die Anfrage per POST zu senden, denn das wird nicht gecacht.
Wie soll ich die Anfrage denn per POST senden, wenn es als Forensignatur per img-tag eingebunden ist? ^^



Naja, ich verwende nun die Variante, dass die Bilder jedesmal on the fly generiert werden und direkt bei Zugriff auf die random.php ausgegeben werden. Dann werden sie zwar nicht gecached, aber dafür entfällt der Overhead durch die Umleitung.


Solang ich auf diese Art keine Performanceprobleme bekomme, verbleibt dann eigentlich nur noch die Frage, ob man das als Bug im Firefox werten kann, den man melden sollte.

Edit: Hat sich erledigt, gibt schon nen Bugreport dazu: https://bugzilla.mozilla.org/show_bug.cgi?id=89419
 
Solang ich auf diese Art keine Performanceprobleme bekomme, verbleibt dann eigentlich nur noch die Frage, ob man das als Bug im Firefox werten kann, den man melden sollte.

Du kannst die Bilder auch auf dem Server Cache, du sparst zwar kein Traffic ist aber drotzdem Performance schonender als onfly.
 
Ja, das hatte ich auch schon überlegt. Wenn die Performance einbricht, mache ich das in jedem Fall, aber erstmal warte ich noch ab (zumal vermutlich die Nutzermenge recht überschaubar bleiben wird).


Danke für euer Anregungen nochmal.