HTTP požadavky a odpovědi – Část 3.
V první a druhé části této minisérie popisuji možnosti ovládání HTTP protokolu z presenteru Nette aplikace. V tomto dílu se zaměřuji na nástroje pro práci s HTTP keší v Nette.
HTTP cache a Nette
Pokud jste se někdy o HTTP protokolu zajímali trochu do hloubky, mohli jste narazit na téma kešování. Tedy jak ušetřit nějaká ta přenášená data. Názorně je to vidět v „Developer tools“ v prohlížečích. Otevřte si je na záložce „Síť“ a načtěte si nějaký web, třeba GitHub. Poté klikněte na refresh. Hlavní stránka se pravděpodobně načte s kódem 200, ale spousta ostatních souborů, styly, skripty a ikony, bude zašedlých s kódem 304. Byly načteny z keše prohlížeče a 304 znamená „Not Modified“.
HTTP kešování je složitá problematika, takže pod pokličku pouze
nahlédneme. Hrají zde roli HTTP hlavičky Last-Modifed
,
ETag
, Pragma
, Cache-Control
,
If-Modified-Since
a If-None-Match
. Ty jsou na úvod
nejdůležitější, ale existuje jich více. Princip ukážu na tom, jak si
povídá webový klient s webovým serverem:
- Client: Ahoj.
- Server: Čau.
- Client: Mám prý od tebe stáhnout soubor main.css,
If-Modified-Since
11.11.2018. - Server: 304, nic nestahuj, soubor se od té doby nezměnil.
- Client: Díky (a soubor si vezme ze své keše).
anebo:
- Client: Ahoj.
- Server: Čau.
- Client: Mám prý od tebe stáhnout soubor selfie.jpg,
If-None-Match
akjJ54sd
- Server: 200, jo, ten už má zase jiný hash, tady ho máš, jeho
ETag
je teďbfhd54se
- Client: Ach jo, už zase? (a začne stahovat soubor a uloží si ho do keše s novým hashem)
Otázka zní, jak nám může Nette s HTTP kešováním pomoct? Pomůže
nám Nette\Http\Context
. Metoda isModified()
podle
HTTP hlaviček požadavku rozhodne, jestli se má soubor znovu odeslat anebo ne.
A také nastaví potřebné hlavičky a kód odpovědi.
Ukážeme si, poněkud zjednodušenou, verzi implementace
FileResponse
, která bere v potaz kešování.
final class FileCachedResponse implements Nette\Application\IResponse
{
private $file;
public function __construct(string $file)
{
$this->file = $file;
}
public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response)
{
$response->setContentType('...');
$response->setHeader('Content-Description', '...');
$response->setHeader('Content-Disposition', '...');
$response->setHeader('Content-Length', filesize($this->file));
$response->setHeader('Pragma', null);
$response->setHeader('Cache-Control', null);
$context = new Nette\Http\Context($request, $response);
$mTime = filemtime($this->file);
if ($context->isModified($mTime)) {
readfile($this->file);
}
}
}
Hlavičky Pragma
a Cache-Control
nastavuje PHP.
Zjednodušeně řečeno, je potřeba je z HTTP odpovědi odebrat, jinak
kešování nebude fungovat.
Metodě Nette\Http\Context::isModified()
jsme jako parametr
předali časové razítko změny stahovaného souboru. Metoda porovná časové
razítko s hlavičkou If-Modified-Since
, pokud existuje, a vrátí
true/false. Metoda má ještě druhý parametr, hash odesílaného obsahu. Ten
porovnává s hlavičkou ETag
. Hash se hodí v případě, kdy
neznáme čas modifikace odesílaných dat. Nejedná se o hashování
bezpečnostního rázu, MD5 suma postačí, a asi i prostší CRC32 nebo CRC64.
Pokud by vás zajímala validace hashe na základě významu odesílaného
obsahu (například XML, ve kterém někdy nezáleží na pořadí tagů) a ne
jeho přesného bitového obsahu, najděte si něco o „weak ETag“
validaci.
Jestli použít časové razítko, hash, nebo oboje je pouze na vás. Oba dva
parametry metody isModified()
jsou volitelné a nullable.
Použití kešování pomocí Last-Modified
a ETag
hlaviček sníží datové toky mezi HTTP klientem a serverem, ale nesníží
počet HTTP dotazů. HTTP hlavičky dotazu a odpovědi se přenesou vždy,
uspoří se přenos těla odpovědi. Pokud jste si jisti, že se odesílaná
data určitě nezmění, dejme tomu, dalších 10 minut, můžete jejich
expiraci klientovi sdělit pomocí
Nette\Http\IResponse::setExpiration()
. Například:
$response->setExpiration('10 minutes');
V tom případě si klient následujících 10 minut o danou URL vůbec
nepožádá, nebude se vůbec dotazovat, jestli se obsah nějak změnil a vždy
použije svou keš. Metoda setExpiration()
nastavuje hlavičky
Cache-Control
a Expires
.
Při kešování myslete hlavně na největší problém keše a to její invalidaci. Já osobně jsem radši, když mi obsah dorazí o něco málo později správně, než okamžitě, ale už neplatný.
A to je vše. Tímto bych tuhle třídílnou minisérii o HTTP v Nette uzavřel :o)
Doplnění
Adam Zemek mi na Twitteru připomněl,
odkud se vlastně HTTP hlavičky Pragma
a
Cache-Control
, a k nim ještě Expires
, odesílané
automaticky z PHP, berou.
Hlavičky se začnou automaticky odesílat, pokud nastartujete session. Jak jejich odesílání upravit, nebo vypnout, najdete v PHP manuálu u funkce session_cache_limiter().
Chcete-li odeslat komentář, přihlaste se