Nette Http 3.1: mnohem chytřejší sessions

před 3 lety od David Grudl  

Nette vždy přistupovalo k session opatrným způsobem. Spouštělo je automaticky až když je uživatel potřeboval, aby šetřilo serverové prostředky. Nová verze 3.1 tuto filozofii posouvá ještě dál a přináší vylepšení, která dělají práci se sessions ještě efektivnější.

Kdy se session automaticky spouští?

  1. když se do ní zapisuje
  2. když se z ní čte a zároveň prohlížeč poslal cookie se session ID

Protože pokud cookie neexistuje, nemusíme session spouštět, abychom věděli, že v ní žádná data nejsou. Tedy i bez spuštění může Nette vrátit například obsah nákupního košíku jakožo prázdný atd.

Celý tento mechanismus má určitá úskalí, která nová verze 3.1.5 řeší.

Pozdní start

V celém procesu správy session jsou důležité tři prvky: identifikátor session (ID) obsažený v cookie, který posílá prohlížeč, odpovídající soubor na disku nebo záznam v databázi obsahující data session, a session ID, které server posílá prohlížeči při prvním spuštění session (aby si ho uložil jako cookie).

S automatickým spouštěním souvisí problém pozdního nastartování session, tedy v situaci, kdy už byl odeslán ze serveru výstup a nelze už posílat HTTP hlavičky. Při spuštění session se totiž musí odeslat cookie se session ID, ale také hlavičky Pragma a Expires, což po odeslání výstupu není možné. Aby se tomuto problému předcházelo, Nette při nastavení „autoStart: smart“ (což je výchozí hodnota) automaticky spustilo session při startu aplikace, pokud existovalo cookie se session ID. Což se mění, viz níže.

Přístup k datům

K datům v session sekci se často přistupuje podobně jako k proměnným:

$section = $session->getSection('unique name');
$section->userName = 'john';
echo $section->userName;

Kvůli specifickému technickému rysu PHP je však operace echo $section->userName detekována jako zápis. To znamená, že i když pouze chceme přečíst data (třeba obsah košíku pomocí $cart = $section->cart), PHP to vyhodnotí jako zápis a nastartuje session, i když cookie neexistuje. Tím přicházíme o výhodu efektivního autostartu, který měl spouštět session pouze když je to skutečně potřeba.

Tento problém lze obejít pomocí syntaxe ArrayAccess, která správně rozlišuje mezi čtením a zápisem:

$section['userName'] = 'john';
echo $section['userName'];  // jde o operaci čtení

Nicméně syntaxe ArrayAccess může být pro uživatele příliš magická a někdy nefunguje tak, jak by se mohlo očekávat (např. $section['cart'][] = $item generuje notice Indirect modification of overloaded element has no effect).

Proto v posledních verzích řady 3.1 i 3.0 najdete pro přístup k datům srozumitelnou sadu metod set(), get() a remove(), které nemají nevýhody předchozích přístupů a správně fungují s autostartem session.

Pomocí set() lze nastavit i expiraci:

$section->set('flash', $message, '30 seconds');

Používání těchto metod se tak stává preferovaným přístupem k datům v sessions. Nette je interně všude používá.

Nette nově upravuje chování v situaci, když uživatel posílá v cookie ID, kterému neodpovídá žádný soubor na disku nebo záznam v databázi. Dříve Nette v takové situaci vygenerovalo nové ID a vytvořilo nový session soubor, aby předešlo možnému podvržení ID ze strany útočníka. Nyní místo toho Nette cookie smaže a soubor vůbec nevytváří. A to proto, aby se na disku nevytvářely prázdné soubory, pokud útočník záměrně session ID podvrhává.

Nová konfigurace autoStart

V konfiguračním souboru může volba autoStart nově nabývat hodnot always a never. První z nich zapíná session vždy hned po startu a je totožná s variantou true. Novinkou je never, která zcela vypíná automatické startování a session se zapne jen tehdy, pokud sami zavoláte $session->start(). Výchozí variantou zůstává smart, jejíž chování se ale od verze 3.1 mění, jak jsem zmiňoval v úvodu. Už nezapíná session automaticky po startu ale učiní tak až v případě čtení nebo zápisu. Totožné chování má od verze 3.1 i hodnota false.

Ve starší řadě v3.0 se naopak mění výchozí nastavení ze smart na false, což vypíná autostart po spuštění.

Události $onStart, $onBeforeWrite

A nakonec objekt Nette\Http\Session má nově události $onStart a $onBeforeWrite, můžete tedy přidat callbacky, které se vyvolají po startu session nebo před jejím zápisem na disk a následným ukončením. Tyto události jsou užitečné především pro ladění a detekování míst v kódu, kde dochází k automatickému spuštění session, což vám pomůže optimalizovat práci se session.

$session->onBeforeWrite[] = function () {
	// zapíšeme data do session
	$this->section->set('basket', $this->basket);
};

Při testování nového chování nezapomeňte, že ve vývojářském režimu startuje session Tracy, protože ji používá pro zobrazování pruhů s přesměrováním a AJAXovými požadavky v Tracy Baru. Od verze 2.9 už Tracy session nevyužívá, a to právě proto, aby bylo možné lépe ladit fungování session.