[php] Caching van dynamische afbeeldingen

Pagina: 1
Acties:
  • 600 views sinds 30-01-2008
  • Reageer

Onderwerpen


Acties:
  • 0 Henk 'm!

  • emkedouwe
  • Registratie: September 2001
  • Laatst online: 26-11-2021
Hallo,

Ik heb een script gemaakt waarmee automatisch thumbnails worden gegenereerd. Wanneer deze al bestaat wordt de thumbnail opgehaald. Voorbeeld:

image/view/id/3/w/500

Deze haalt de afbeelding met id 3 op. Vervolgens wordt er gekeken of er een thumbnail is met een breedte van 500 px. Zo niet wordt deze gegenereerd. De volgende keer wordt direct de thumbnail opgehaald. Tot zover werkt alles.

Het probleem is nu dat de afbeeldingen die geöutput worden naar de browser niet worden gecached. De server geeft elke keer de status 200 terug wat betekend dat de afbeelding elke keer van de server wordt gedownload en niet vanuit de browser cache op de client wordt opgehaald.

Nadat ik de code:

code:
1
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");


heb toegevoegd wordt de afbeelding wel weggeschreven in de temporary internet files alleen evengoed elke keer weer opnieuw gedownload. Ook wanneer ik bijvoorbeeld de last-modified datum of expire datum meegeef.

De code die ik nu heb:
code:
1
2
3
header("Content-type: image/jpeg"); 
header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); 
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 60 * 60 * 24 * 5) . " GMT");

Acties:
  • 0 Henk 'm!

  • Erkens
  • Registratie: December 2001
  • Niet online

Erkens

Fotograaf

Geef ook een Content-Length mee, en ik zou aan de Cache-Control ook een "max-age" mee geven (in seconden) zodat die overeenkomt met je Expiers header.


Handige site: http://www.web-caching.com/

[ Voor 13% gewijzigd door Erkens op 03-04-2007 11:58 ]


Acties:
  • 0 Henk 'm!

  • emkedouwe
  • Registratie: September 2001
  • Laatst online: 26-11-2021
Ik heb de headers nu als volgt gezet:

code:
1
2
3
header("Content-type: image/jpeg"); 
header("Cache-Control: max-age=3600"); 
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 60 * 60 * 3) . " GMT");


Dit zou voldoende moeten zijn toch? Hij geeft nog steeds bij alle browsers een status 200 terug.

Acties:
  • 0 Henk 'm!

  • Erkens
  • Registratie: December 2001
  • Niet online

Erkens

Fotograaf

Mja, je kan ook gewoon even mijn hele advies lezen en proberen ipv slechts de helft (en ook die url zet ik er niet voor niks bij...)

Ten eerste staat je max-age nu op 1 uur terwijl je bij de Expires header uitgaat van 3 uur, waarom dat verschil?

PHP:
1
2
3
4
header("Content-type: image/jpeg"); 
header("Cache-Control: max-age=".(60 * 60 * 3).", must-revalidate");
header("Content-Length: ".filesize($filename));
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 60 * 60 * 3) . " GMT");


Waarbij $filename natuurlijk wijst naar de opgeslagen versie van het plaatje ;)

Acties:
  • 0 Henk 'm!

  • Confusion
  • Registratie: April 2001
  • Laatst online: 01-03-2024

Confusion

Fallen from grace

emkedouwe schreef op dinsdag 03 april 2007 @ 11:49:
Wanneer deze al bestaat wordt de thumbnail opgehaald.
Minor nitpick: je PHP haalt geen thumbnails op. Dat doet de browser.
Het probleem is nu dat de afbeeldingen die ge-output worden naar de browser niet worden gecached. De server geeft elke keer de status 200 terug wat betekend dat de afbeelding elke keer van de server wordt gedownload en niet vanuit de browser cache op de client wordt opgehaald.

Nadat ik de code:
code:
1
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");


heb toegevoegd wordt de afbeelding wel weggeschreven in de temporary internet files alleen evengoed elke keer weer opnieuw gedownload. Ook wanneer ik bijvoorbeeld de last-modified datum of expire datum meegeef.
Geef je die headers mee in de response die de pagina waar de plaatjes opstaan teruggeeft of geef je die mee met de responses op de individuele image requests? Want als je alleen de images gecached wilt hebben, dan moet je eigenlijk aan de laatste headers zitten, niet aan de eerste.

edit:
Zie onder; Erkens let beter op dan ik.

Wie trösten wir uns, die Mörder aller Mörder?


Acties:
  • 0 Henk 'm!

  • Erkens
  • Registratie: December 2001
  • Niet online

Erkens

Fotograaf

Confusion schreef op dinsdag 03 april 2007 @ 16:07:
Minor nitpick: je PHP haalt geen thumbnails op. Dat doet de browser.
nee hij bedoeld dat PHP die thumbnail ophaalt ipv opnieuw genereerd. :P
Geef je die headers mee in de response die de pagina waar de plaatjes opstaan teruggeeft of geef je die mee met de responses op de individuele image requests? Want als je alleen de images gecached wilt hebben, dan moet je eigenlijk aan de laatste headers zitten, niet aan de eerste.
als je goed kijkt zie je een header waarbij de content-type op "image/jpeg" gezet wordt, wat mij eerder op een plaatje lijkt dan een (html) pagina.

Acties:
  • 0 Henk 'm!

  • emkedouwe
  • Registratie: September 2001
  • Laatst online: 26-11-2021
Ik heb de headers nu als volgt gezet:

code:
1
2
3
4
header("Content-type: image/jpeg");
header("Cache-Control: max-age=".(60 * 60 * 3).", must-revalidate"); 
header("Content-Length: ".filesize($file)); 
header("Expires: " . gmdate("D, d M Y H:i:s", time() + 60 * 60 * 3) . " GMT");


Mijn headers zien er nu in de browser zo uit:

Expires 3 hr 1 sec from now (Wed, 04 Apr 2007 16:07:52 GMT)
Cache-Control max-age=10800, must-revalidate
Last-Modified -
ETag -
Content-Length 7.8K (7946)
Server Apache/2.0.54 (Debian GNU/Linux) PHP/5.1.4-1.dotdeb.1

Dit zou toch goed moeten staan zo? De Cachability Checker op ircache.net geeft nu het volgende aan:

This object will be fresh for 3 hr. It doesn't have a validator present. Because of the must-revalidate header, all caches will strictly adhere to any freshness information you set. This object requests that a Cookie be set; this makes it and other pages affected automatically stale; clients must check them upon every request.

Verder zie ik in mijn apache log dat deze niet gecached wordt.

Acties:
  • 0 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
emkedouwe schreef op woensdag 04 april 2007 @ 15:16:
De Cachability Checker op ircache.net geeft nu het volgende aan:
[...]
This object requests that a Cookie be set; this makes it and other pages affected automatically stale; clients must check them upon every request.
[...]
Vanwaar dit verhaal over cookies?

{signature}


Acties:
  • 0 Henk 'm!

  • sonix666
  • Registratie: Maart 2000
  • Laatst online: 16:55
Correct me if I'm wrong. Maar jij zegt in je response dat ie moet revalidaten. De browser zal bij de volgende keer dat ie het plaatje gaat ophalen (indien de expiry date nog niet is verlopen) een header toevoegen die aangeeft dat ie wil valideren of de boel veranderd is. Als jouw pagina dan gewoon doodleuk weer dat plaatje teruggeeft in plaats van de header die aangeeft dat het plaatje nog steeds valide is, ja, dan krijg je een response 200 terug met volledig plaatje.

Gebruik anders een programma als Wireshark om exact je netwerkverkeerd te volgen. Die kan precies tonen wat er op HTTP niveau verstuurd en ontvangen wordt.

Acties:
  • 0 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
http://www.w3.org/Protoco...2616-sec14.html#sec14.9.4
When the must-revalidate directive is present in a response received by a cache, that cache MUST NOT use the entry after it becomes stale to respond to a subsequent request without first revalidating it with the origin server.
Deze header zorgt inderdaad voor verplichte revalidation, maar pas nadat de opgegeven houdbaarheidsdatum verstreken is. :)

{signature}


Acties:
  • 0 Henk 'm!

  • igmar
  • Registratie: April 2000
  • Laatst online: 03-09 22:58

igmar

ISO20022

emkedouwe schreef op woensdag 04 april 2007 @ 15:16:
Verder zie ik in mijn apache log dat deze niet gecached wordt.
En wat zie je precies in je accesslogs ? Lees : een voorbeeldregel van wat volgens jou niet gecached wordt.

Acties:
  • 0 Henk 'm!

  • emkedouwe
  • Registratie: September 2001
  • Laatst online: 26-11-2021
Dit zie ik terug in mijn apache access log:

Gewone afbeelding:

12.160.37.6 - - [05/Apr/2007:09:18:53 +0200] "GET /public_html/_images/logo.gif HTTP/1.0" 200 8897 "-" "CacheabilityEngine/1.30 <http://www.mnot.net/cacheability/>"
12.160.37.6 - - [05/Apr/2007:09:18:54 +0200] "GET /public_html/_images/logo.gif HTTP/1.0" 304 - "-" "CacheabilityEngine/1.30 <http://www.mnot.net/cacheability/>"

Afbeelding via script:

12.160.37.6 - - [05/Apr/2007:09:20:16 +0200] "GET /public_html/media/image/id/7 HTTP/1.0" 200 7946 "-" "CacheabilityEngine/1.30 <http://www.mnot.net/cacheability/>"
12.160.37.6 - - [05/Apr/2007:09:20:18 +0200] "GET /public_html/media/image/id/7 HTTP/1.0" 200 7946 "-" "CacheabilityEngine/1.30 <http://www.mnot.net/cacheability/>"

Hierbij zie je duidelijk dat de gewone afbeelding wordt gecached en die via het script gewoon weer opnieuw opgehaald moet worden.
Zou het iets kunnen zijn dat ik in mijn script moet checken of ik een afbeelding of een 304 terug moet geven?

Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 21:18

crisp

Devver

Pixelated

Er zijn meerdere situaties waarbij een browser toch weer een request doet naar de server, bijvoorbeeld als je F5'ed op het URL van het plaatje. Wat je moet doen is serverside checken op de If-Modified-Since request-header en indien het plaatje niet na dat tijdstip is veranderd een 304 Not-Modified teruggeven, eventueel met een nieuwe Expires/max-age header.

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • CodeCaster
  • Registratie: Juni 2003
  • Niet online

CodeCaster

Can I get uhm...

Hier een gedeelte van het systeempje wat ik geschreven heb om afbeeldingen te cachen:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*========================================================================================================
Read the headers used to retreive the image
========================================================================================================*/
$headers = apache_request_headers();

/*========================================================================================================
Set the default response headers
========================================================================================================*/
header('Expires: ' . date('D, j M Y H:i:s', time() + (60*60*24*31)) . ' GMT');
header('Cache-Control: Public');
header('Pragma: Public');
header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($resizedimagename)).' GMT');
header('Content-type: image/jpeg');
header('Content-Length: '.filesize($resizedimagename));

/*========================================================================================================
Check whether we need to send the whole image, or just '304 NOT MODIFIED', then the client will display
its own cached version
========================================================================================================*/
if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == filemtime($resizedimagename))) {
    // Client's cache IS current, so we just respond '304 Not Modified'.
    header('HTTP/1.0 304 Not Modified');
} else {
    // read and output the image
    print file_get_contents($resizedimagename);
}

[ Voor 5% gewijzigd door CodeCaster op 05-04-2007 09:45 ]

https://oneerlijkewoz.nl
Op papier is hij aan het tekenen, maar in de praktijk...


Acties:
  • 0 Henk 'm!

  • emkedouwe
  • Registratie: September 2001
  • Laatst online: 26-11-2021
:)

Ik lees net een vergelijkbaar stuk op php.net:
When using PHP to output an image, it won't be cached by the client so if you don't want them to download the image each time they reload the page, you will need to emulate part of the HTTP protocol.

Here's how:

<?php

// Test image.
$fn = '/test/foo.png';

// Getting headers sent by the client.
$headers = apache_request_headers();

// Checking if the client is validating his cache and if it is current.
if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == filemtime($fn))) {
// Client's cache IS current, so we just respond '304 Not Modified'.
header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($fn)).' GMT', true, 304);
} else {
// Image not cached or cache outdated, we respond '200 OK' and output the image.
header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($fn)).' GMT', true, 200);
header('Content-Length: '.filesize($fn));
header('Content-Type: image/png');
print file_get_contents($fn);
}

?>
Volgens mij gaat dit werken...

Acties:
  • 0 Henk 'm!

Verwijderd

Pas overigens op dat nare mensen je systeem redelijk plat kunnen krijgen als ze zelf iets in de url kunnen zeggen over de grootte van je plaatje...

Zorg dus voor vast gedefinieerde formaten (bv small-thumb, medium-thumb, large-thumb).

Acties:
  • 0 Henk 'm!

  • Erkens
  • Registratie: December 2001
  • Niet online

Erkens

Fotograaf

emkedouwe schreef op donderdag 05 april 2007 @ 09:41:
:)

Ik lees net een vergelijkbaar stuk op php.net:


[...]


Volgens mij gaat dit werken...
Let wel op dat dit alleen met Apache werkt en dan alleen als PHP als module gebruikt wordt.

Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 21:18

crisp

Devver

Pixelated

Erkens schreef op donderdag 05 april 2007 @ 09:53:
[...]

Let wel op dat dit alleen met Apache werkt en dan alleen als PHP als module gebruikt wordt.
Ik neem aan dat het in andere serverside languages / webserver software ook mogelijk is request-headers uit te vragen ;)

[ Voor 3% gewijzigd door crisp op 05-04-2007 10:27 ]

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • Erkens
  • Registratie: December 2001
  • Niet online

Erkens

Fotograaf

crisp schreef op donderdag 05 april 2007 @ 10:26:
[...]

Ik neem aan dat het in andere serverside languages / webserver software ook mogelijk is request-headers uit te vragen ;)
dit topic ging over PHP ;)

Acties:
  • 0 Henk 'm!

  • Voutloos
  • Registratie: Januari 2002
  • Niet online
En dan nog, al was niet bekend welke taal het over ging, ipv blind code copy pasten mag je ook wel zelf bedenken wat apache_request_headers() doet en hoe je datzelfde bereikt in je eigen omgeving. :)

{signature}


Acties:
  • 0 Henk 'm!

  • Erkens
  • Registratie: December 2001
  • Niet online

Erkens

Fotograaf

Voutloos schreef op donderdag 05 april 2007 @ 11:25:
En dan nog, al was niet bekend welke taal het over ging, ipv blind code copy pasten mag je ook wel zelf bedenken wat apache_request_headers() doet en hoe je datzelfde bereikt in je eigen omgeving. :)
Aan de naam van de functie zou je idd kunnen opmaken dat het alleen met Apache werkt, echter zodra je PHP-CGI draait met Apache werkt het alsnog niet. Ik zeg het niet vaak, maar dit is echt een van die dingen waarbij ik graag wil ranten op de developers van PHP :/

Acties:
  • 0 Henk 'm!

  • crisp
  • Registratie: Februari 2000
  • Laatst online: 21:18

crisp

Devver

Pixelated

Ik mag hopen dat je anders wel terug kan vallen op $_SERVER['HTTP_IF_MODIFIED_SINCE'] ;)

Intentionally left blank


Acties:
  • 0 Henk 'm!

  • emkedouwe
  • Registratie: September 2001
  • Laatst online: 26-11-2021
Nou het is eindelijk gelukt, het werkt nu allemaal zoals het zou moeten:

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$headers = apache_request_headers();
$file    = $this->config->path->upload . "thumbs/" . $filename;

if (isset($headers['If-Modified-Since']) && (strtotime($headers['If-Modified-Since']) == filemtime($file))) 
{
    // Client's cache IS current, so we just respond '304 Not Modified'.
    header('Cache-Control: private');
    header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($file)).' GMT', true, 304); 
} 
else 
{
    // Image not cached or cache outdated, we respond '200 OK' and output the image.
    header('Cache-Control: private');
    header('Last-Modified: '.gmdate('D, d M Y H:i:s', filemtime($file)).' GMT', true, 200);
                
    $thumbnail  = new Image($file);
}
Pagina: 1