Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

20. 10. 2016 - Komentáře (5) Ostatní Nette

Jak se řeší 404ky, 500ky v Nette

Když jsem teď převáděl blog na Nette, tak jsem se docela zasekl na chybových stránkách - stránky, kde HTTP Response kód je 500 a 404. Typicky se jim říká "pětiskova", "500", případně "ISE" a "čtyřista-čtyřka", "404". Překvapivě celkem jednoduchá záležitost mě docela potrápila. V dokumentaci se toho příliš o tom, jak chybové stránky řeší Nette a na co si dát pozor mnoho není. Takže se hodí trochu se o tom zmínit. A proč ne zrovna na blogu, kde jsem to musel řešit :-).

Nette a ošetření chyb

Problém už je v tom, jak se klasická Nette aplikace vlastně vyvíjí. Většinou jedete podle sandboxu, kde se vám hned na začátku v boostrapu nahodí Tracy a ta za vás všechny výjimky a chyby v aplikaci řeší a k nějakým chybovým stránkám se tak vlastně vůbec nedostanete. To je vývojový režim.

Pokud je Nette aplikace v produkčním režimu, tak to funguje tak, že výjimky propadnou celou vaší aplikací až na začátek, kde si Nette vyjímku zachytí a zavolá se speciální presenter - ErrorPresenter - který se o výjimky musí postarat. Ten má pak na starost i vykreslení našich 404 a 500 stránek. První a nejdůležitější věc je tedy nezapomenout na testování aplikace v produkčním režimu před tím, že ji nasadíte. Ideálně samozřejmě to mít někde v testovacím prostředí vyřešené pomocí testů. Pokud děláte typicky nějaké malé weby, tak si to prostě pamatovat a vyzkoušet. Když to neuděláte a zapomenete na to, tak můžete dopadnout dost špatně, propadnou vám tam nějaké staré ErrorPresentery, typicky ty ještě z toho výchozího sandboxu, pak jsou v nich špatné namespaces a celá aplikace skončí právě tou 500, ačkoli na vývojovém prostředí všechno funguje. Další věc je, že výchozí sandboxové šablony chybových stránek chtějí využívat společný layout a tam vám to také spadne na tom, že v layoutu používáte už nějaké společné komponenty nebo věci, o kterých ErrorPresenter také neví.

Produkční režim při vývoji

Takže jste si na to vzpomněli a jdete přes nasazením otestovat aplikaci v produkčním režimu. Nejjednodušší je změnit na chvíli ip adresu v boostrapu, např. místo 127.0.0.1, tam dát 128.0.0.1 - $configurator->setDebugMode(["128.0.0.1"]);. Jsme tedy v produkčním režimu při vývoji. Pak už jen otevřít v prohlížeči nějakou neexistující stránku vaší aplikace a … nestačíte se divit. Stránku 500 nasimulujete nejsnáze tak, že si do nějakého existující Presenteru vrazíte hned někam na začátek nějaké throw new \Exception("test");.

Tenhle produkční režim při vývoji je ale dost nepřátelský, samozřejmě nesmíte zapomenout na ty testovací výjimky, ale já jsem ještě narazil ještě na to, že v tomhle režimu se automaticky nepromazává keš šablon. Takže když v tomhle režimu ladíte vzhled stránky 404 v latte šabloně, tak pozor nato, že při její změně je potřeba ručně promazávat keš šablon.

Pozn. Podle komentářů od D. Grudla se zdá, že není třeba vůbec používat produkční režimem při vývoji. Pro vývoj error presenterů stačí nastavit v konfiguraci 'applications: catchExceptions: yes'. Pak místo toho, aby se při výskytu výjimek používalo Tracy, tak výjimky začnou propadávat do error presenterů a můžete je tak pohodlně odladit přímo ve vývojovém režimu.

Ještě více srandy si užijete při výskytu chyb ve vlastním ErrorPresenteru. To je z hlediska frameworku poměrně komplikovaná věc, když si člověk uvědomí, že vlastně řeší případ, kdy v ErrorPresenteru, který zpracovává výjimku se vyskytne výjimka. Co jsem měl možnost vyzkoušet, tak to dopadne tak, že chybu chytí Tracy, která tam i v produkčním režimu evidentně pořád někde zůstává a čeká kromě logování i na tenhle případ. Vtip je v tom, že Tracy vyhodí téměř na chlup stejnou stránku jako máte v sandboxu pro ErrorPresenter, takže na první pohled jste naprosto zmatení. V logách Tracy je naštěstí vidět, kde je problém.

K logám - to je další věc - je sice fajn jak loguje Tracy, ale co když nastane chyba přímo v Tracy, nebo logy přetečou? Přeci jen to Tracy logování se trochu vymyká tomu standarnímu formátu unix logů. Bohužel Tracy se nedrží toho, že by respektovala error_log direktivu PHP, naopak ji potlačuje. Určitě by mě přišlo vhodné, aby Tracy respektovala error_log a posílala tak chyby dále do standarního PHP logu. Onehdá jsme na to měli do Tracy nějaké patche, ale s přechodem na Sentry v produkci jsme je zrušili a do Sentry se nám podařilo prosadit změny, které logují chyby do PHP logu.

Více možností ošetření chyb 404

Pokud máte rozsáhlejší aplikaci, tak máte v zásadě dvě možnosti, jak ošetřovat chyby 404, obecně jakékoli jiné než 500.

  • vyhozením výjimky - typicky Application\BadRequestException pro 404
  • forwardováním na specifický ErrorPresenter

První možnost je jasná, vyhodí se výjimka a skončíme v našem hlavním ErrorPresenteru kde výjimku ošetříme.

Druhá možnost se používá u větších aplikacích, kdy potřebujete odlišit od sebe stránky různých "neexistujících věcí", protože každá se snaží uživatele vrátit zpět na do aplikace jiným způsobem. Typicky stránka pro neexistující produkt se bude lišit od stránky pro neexistují kategorii produktu, neexistujícího uživatele atd. Typicky se pro tyto případy použije jeden abstraktní pro specifické stránky a další specifické ErrorPresentery od něj pak dědí kvůli vlastním šablonám a případně další logice, jak navést uživatele zpět na web.

Tyto specifické ErrorPresenty jsou ale dost často zdrojem chyb, protože používají různé sidebary a layouty s komponentami, které se pak neumí vytvořit. Takže z nich padají výjimky, kterých si při vývoji normálně nevšimnete, protože jedete v klasickém vývojovém režimu.

Chování ErrorPresenterů

ErrorPresentery si také zaslouží trochu více pozornosti. První věc je, že ten sandboxový, ze kterého budete vycházet, se mi zdá nějaký podivný, např. nedědí od UI\Presenter, nemá svou šablonu, jen phtml soubor a všechny 4xx výjimky posílá na další presentery. Cílem asi je mít obsluhu chyby 500 - fatální selhání aplikace - opravdu co nejrychleji a nejbezpečněji odbavené, i když ten forward rozhodně není jednoduchá záležitost. Jednodušší řešení mi přijde mít jeden klasický ErrorPresenter s renderdefault metodou, kde se podle výjimky, která přijde v parametru exception, rozhodneme pro šablonu a tu vykreslíme. Nemusíme nic forwardovat a máme k dispozici latte i pro chybu 500.

Pak je tu další nepříjemná věc, která platí jak pro hlavní ErrorPresenter, tak pro případné specifické neumějí obsloužit url pro signály. Nevím tedy jak je to v aktuálním Nette 2.4, ale v Nette 2.1 to platilo. Pokud jste v šabloně ErrorPresenteru volali komponentu, která vytvářela url pro nějaký svůj signál - nějaké latte volání {link signal!}, tak to v ErrorPresenteru skončí chybou. Je to tím, že ErrorPresentery nemají routy. Tohle je dost nepříjemná vlastnost, které se nedá moc předcházet, protože ve složitější aplikaci se šablony různě includují do sebe a lehce se tak stane, že nějaká komponenta z nějakého sidebaru probublá i do ErrorPresenteru. Řešením je např. udržovat šablony pro ErrorPresentery oddělené, aby se do nich takové komponenty nedostávaly, nebo podmínkovat kód těchto komponent tak aby typicky pro HTTP Response kódy 404 prostě url se signálem nezobrazovalo.

Doporučení

Na závěr doporučení pro ošetřování error stránek a tvorbu ErrorPresenterů

  • Nezapomínat testovat ErrorPresentery a jejich chování. Ideálně automatizovaně.
  • Produkční režim při vývoji má svá úskalí, např. se neinvaliduje keš šablon.
  • ErrorPresentery neumí obsloužit url pro signály.

Z toho pak plynou další doporučení, která nám ulehčí udržování aplikace.

  • Minimalizovat použití specifických ErrorPresenterů, idálně mít jen jeden hlavní ErrorPresenter. Pak se také všude dá použít jednoduché vyhazování výjimky BadRequestException.
  • Mít šablony ErrorPresenterů jednoduché a samostatné, aby pokud možno nezávisely na dalších šablonách. Zabrání se tak zavlečení různých částí aplikace, které ErrorPresenter neumí obsloužit, zejména signálů, ale i různých komponent, které ErrorPresenter neumí vytvořit.

Něco by se určitě dalo zlepšit i v samotném Nette, potažmo v sandboxu Nette aplikace na GitHubu

  • Zjednodušit ErrorPresentery v sandboxu, aby měly samostatné šablony bez závislosti na layoutu, případně aby dědily od UI\Presenter jako obyčejné presentery.
  • Nějak se pokusit vyřešit url na signály u Presenterů bez rout, pokud to tedy již není vyřešené.
  • Zkusit vymyslet nějaké opatření ať už v sandboxu nebo ve frameworku, které by zlepšilo možnosti vývoje ErrorPresenterů.
  • Pokusit se designově odlišit 500ku v Tracy a v sandboxu, aby člověk nebyl při vývoji zmatený, na co vlastně kouká.
  • Zvážit, zda nechat Tracy přes vše ostatní nakonec logovat do standarního PHP logu, je to standard, minimálně administrátoři jsou na to zvyklí.
  • Doplnit někam nějaké best practices pro ErrorPresentery, pokud se tedy na něčem komunita shodne, tohle může sloužit jako odrazový můstek :-).

Komentáře (5)

  1. martin - 21. 10. 2016 05:50

    a neni lepší se na český přežitek nette prostě vykašlat? ;)
    tuhle českou one man show jsem stále nepochopil. používat to i v roce 2016 je zvláštní druh masochismu. srovnávám třeba s RoR 5, i když to slovo "srovnat" je nesmysl, je to jak trabant proti raketě

  2. MK - 24. 10. 2016 11:21

    Technicky je na tom Nette srovnatelně s konkurencí, tedy s ohledem na to, co implementuje. Na menší weby je to pořád dobrá volba. Ta one man show je ovšem pořád problém, autor vždycky s velkou slávou něco ohlásí a pak nic. Zkusím na to téma nahodit nějaký další článek

  3. David Grudl - 12. 11. 2016 23:12

    Díky za popis testování Error presenterů.

    Aby se člověk vyhnul problémům s mazání cache a vůbec absenci Tracy baru apod, je lepší testovat Error presentery v debug modu. Akorat v konfiguraci je potřeba říct, že se mají používat, tj. v sekci application přidat "catchExceptions: yes".

    BTW Nette není onemanshow, ale normální komunitní projekt. Mimochodem netuším, jak dobře nebo špatně je třeba catchExceptions v dokumentaci vysvětlené. Pokud nedostatečně, bude super, pokud to tam doplníš (tak funguje komunitní projekt).

  4. MK - 15. 11. 2016 10:24

    Díky za info, catchException doplním do článku. Předpokládám, že se to pak zas musí vrátit zpět jinak by se objevoval Tracy bar i na produkci.

    Když budu mít chvíli, tak to zkusím otestovat a doplnit dokumentaci, ale PHP a Nette už se moc nevěnuji, tak nevím kdy to bude.

  5. David Grudl - 16. 11. 2016 21:05

    Vracet zpět se to nemusí, mění to chování jen v debug režimu.

Komentáře jsou uzavřeny.