PHP - Podmíněné požadavky


Návody pro C4


V textu ukážeme, jakým způsobem implementovat podmíněné požadavky v PHP skriptech a omezit tím zbytečné přenosy dat a snížit zatížení web serveru. Klient (internetový prohlížeč) posílá na web server požadavky na jednotlivé stránky. Pro stránku, pro každý obrázek na stránce, pro každý css soubor na stránce posílá klient samostatný požadavek a server posílá příslušná data (obsah stránky, obrázek, css soubor, ..). Pro opakované požadavky na stejnou adresu nabízí HTTP protokol optimalizaci v tom smyslu, že klient pošle tzv. podmíněný požadavek "pošli mi obrázek logo.jpg, ale pouze v případě, že obrázek je v novější verzi, než kterou jsi mi už poslal". V případě, že se obrázek od posledního požadavku nezměnil, tak server pošle pouze ujištění "obrázek logo.jpg se nezměnil" a samotný obrázek nepošle. Nedochází tak ke zbytečným přenosům stránek a obrázků, které byly již jednou přeneseny.

Mechanismus podmíněných požadavků

U statických stránek a obrázků (požadavek se nezpracovává PHP skriptem, data se berou přímo ze souboru s koncovkou .html, .htm, .jpg, ..) web server standardně podmíněné požadavky podporuje. Server s obsahem stránky pošle zároveň HTTP hlavičky Last-Modified a ETag. Last-Modified obsahuje datum poslední změny obsahu souboru, který se posílá. ETag obsahuje unikátní řetězec, který jednoznačně identifikuje obsah souboru, v případě statických stránek se jedná o hash obsahu souboru. Hodnoty hlaviček Last-Modified a ETag použije klient při opakovaném požadavku na stejnou adresu (URL). V požadavku pošle klient hlavičky If-Modified-Since (nastaví ji na hodnotu hlavičky Last-Modified) a If-None-Match (nastaví ji na hodnotu hlavičky ETag). Server na základě obsahu hlaviček If-Modified-Since a If-None-Match zjistí, jestli je k dispozici novější verze stránky. Pokud ano, tak pošle obsah stránky. Pokud novější verze k dispozici není, tak pošle pouze stavový kód 304 Not Modified (a obsah stránky neposílá).

Implementace podmíněných požadavků v PHP skriptech

Pro podporu podmíněných požadavků v PHP skriptech potřebujeme vědět čas poslední modifikace výsledné stránky. Většinou přirozeně plyne z logiky skriptu:

Funkce exit_with_304_if_not_modified(), jejíž zdrojový kód uvádíme níže, se bude ve skriptu volat před výpisem dat na výstup. Jediný parametr $time je UNIX timestamp, tj. počet sekund od 1.1.1970 00:00:00 GMT. Čas ve formátu UNIX timestamp vrací většina PHP funkcí, které mají jako návratovou hodnotu čas.

<?php

function exit_with_304_if_not_modified($time)
{
    // datum posilame ve formatu dle RFC 822
    $date = gmdate('D, d M Y H:i:s \G\M\T', $time);

    // retezec $date obsahuje mezery, proto musi byt (dle
    // RFC 2616) v hlavicce ETag hodnota $date v uvozovkach
    header("ETag: \"$date\"");

    $isset = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ||
             isset($_SERVER['HTTP_IF_NONE_MATCH']);
    $etag_test = !isset($_SERVER['HTTP_IF_NONE_MATCH']) ||
                 $_SERVER['HTTP_IF_NONE_MATCH'] == "\"$date\"";
    $lastmod_test = !isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ||
                    $_SERVER['HTTP_IF_MODIFIED_SINCE'] == "$date";

    // [1] klient musi poslat hlavicku If-None-Match a/nebo
    // If-Modified-Since
    // [2] hlavicka (resp. obe hlavicky) musi mit spravny obsah
    if($isset && $etag_test && $lastmod_test) {
        header('HTTP/1.1 304 Not Modified');
        exit;
    }

    // [1] pokud neposilame data (vracime 304 Not Modified), tak
    // neposilame ani hlavicku Last-Modified
    // [2] hodnotu $date v hlavicce Last-Modified do uvozovek
    // nedavame (narozdil od hlavicky ETag)
    header("Last-Modified: $date");
}

?>

Z implementace je patrné, že pro hlavičku ETag nepoužíváme hash obsahu výstupu, ale používáme stejnou hodnotu (akorát "obalenou" uvozovkami) jako v hlavičce Last-Modified. To má drobnou nevýhodu, že pokud by došlo k více změnám v obsahu souboru ve stejném čase (v rámci jedné sekundy), tak by hlavička ETag obsahovala stejnou hodnotu (pro více verzí souboru). V praxi je tato nevýhoda zanedbatelná, protože pravděpodobnost, že dojde k více změnám dat v rámci jedné sekundy a uprostřed změn (v rámci téže sekundy) přistoupí klient na stránku, je téměř nulová. Naopak velkou výhodou je, že nemusíme kvůli výpočtu hashe souboru procházet celý obsah výstupu (např. získat text článku z databáze) - díky tomu je skript rychlejší a odčerpává méně výkonu serveru.

V implementaci spoléháme na to, že klient pošle datum v hlavičce If-Modified-Since ve stejném formátu, v jakém ho obdržel v hlavičce Last-Modified. To nemusí být vždy pravda, nicméně všechny rozšířené prohlížeče dodržují pravidlo, že posílají datum ve stejném formátu, v jakém ho obdržely, takže tato nevýhoda je zanedbatelná (mohla by se projevit jen u prohlížečů, které skoro nikdo nepoužívá, pokud vůbec nějaké takové existují).

Příklad skriptu

Ukažme si konkrétní použití funkce exit_with_304_if_not_modified() ve skriptu, který na výstup posílá jpg obrázek, který je uložen v souboru na disku.

<?php

$cesta_k_obrazku = $_SERVER['DOCUMENT_ROOT'].'/obrazek.jpg';
$unix_timestamp = filemtime($cesta_k_obrazku);

exit_with_304_if_not_modified($unix_timestamp);

header('Content-Type: image/jpeg');
readfile($cesta_k_obrazku);

?>

Závěr

Implementaci podmíněných požadavků v PHP lze využít pro veškerý obsah, který může být generován PHP skiptem - kromě již zmíněných stránek a obrázků se může jednat např. o dynamicky generované RSS feedy.

Pokud chceme ověřit, že podmíněné požadavky fungují, stačí se podívat do access logu příslušného webu (access logy jsou dostupné v administračním systému). První přístup na stránku by měl v logu vypadat takto:

1.2.3.4 - - [02/May/2009:22:27:45 +0200] "GET /a.php HTTP/1.1" 200 6138 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; cs; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10"

Z logu je vidět, že klient přistoupil na stránku /a.php. Dále je vidět, že byl vrácen stavový kód 200 (OK - odpověď na požadavek byla úspěšně zaslána). Číslo 6138 (nachází se hned za 200) je počet bytů (bez HTTP hlaviček), které byly přeneseny ke klientovi. Je tedy vidět, že klient úspěšně obdržel stránku o velikostí 6138 bytů. Při opakovaném přístupu na stejnou stránku bude v logu:

1.2.3.4 - - [02/May/2009:22:27:45 +0200] "GET /a.php HTTP/1.1" 304 - "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; cs; rv:1.9.0.10) Gecko/2009042316 Firefox/3.0.10"

Stavový kód 304 znamená, že server pouze informoval klienta, že obsah stránky se nezměnil (a samotný obsah stránky poslán nebyl).

Související odkazy

Diskuzní fórum pro článek PHP - Podmíněné požadavky

Nové diskuzní téma můžete založit v sekci PHP, MySQL.

Změny a kontroly

K dispozici je kompletní přehled všech změn a kontrol v tomto návodu.


Diskuzní fórum
  • Webové aplikace
  • Tvorba web stránek
  • PHP, MySQL

forum.c4.cz