V Error4×xPresenteru nefunguje handle<signal> komponenty
- m.brecher
- Generous Backer | 889
Mám tento problém – v Error4xxPresenteru nefunguje handle<signal> komponenty.
V layout.latte mám vloženo odhlašovací tlačítko, pro odhlášení signálem komponenty, a uživatel zůstane na stejné stránce (Presenter:action se nezmění).
layout.latte:
Odhlašovací tlačítko vyrábím jako komponentu tovární metodou v BasePresenteru:
Třída LogoutButton – obsahuje handler pro obsluhu signálu logoutButton:logout! k volání handleru ale nedojde
Šablona tlačítka logoutButton.latte:
Chci, aby se při BadRequestException vykreslil celý layout webu včetně menu. Výjimka BadRequestException skončí nakonec u Error4xxPresenteru. V layoutu je vloženo menu, které se generuje z dat v databázi a odhlašovací tlačítko. Chybová šablona 404.latte se vykreslí do standardního layoutu layout.latte a aby byly k dispozici data, dědí ErrorXxxPresenter z BasePresenteru, kde využije metodu injektXxx() a beforeRender(), kde se závislosti předají do layoutu. Děděním předejdu duplikování kódu, i když s sebou možná nese drobné riziko ohledně bezpečnosti (?)
Všechno to funguje, až na situaci, když se vyvolá při testování BadRequestException např. záměrně chybějící šablonou akce. Vzniknou hned tři problémy:
- Tlačítko neodkazuje na původní presenter, ale na Error4xxPresenter.
- Po kliknutí na odhlašovací tlačítko se v presenteru vyvolá signál logoutButton:logout, nedojde ale k zavolání LogoutButton::handleLogout()
- $this->presenter->redirect(‚this‘) v handleru signálu tlačítku by přesměroval na aktuální presenter a akci, což je Error4xx, k tomu sice díky chybám ani nedošlo, ale i toto není dobré
Pokud není BadRequestException vš funguje ok, když se vyvolá BadRequestException, tlačítko se vykreslí, signál je presenterem správně zachycen, ale nezavolá se LogoutButton::handleLogout().
Odhaduji, že framework nette nedovolí zpracovat signál, když se presenter forwarduje na další presenter – asi z bezpečnostních důvodů (?)
Zkusil jsem ještě toto, ale není to v Nette dovoleno:
Zřejmě není úplně dobrý nápad při BadRequestException se pokoušet odhlásit. Tak asi při vyvolání Error4xxPresenteru v layout.latte odstraním vykreslení tlačítka a bude to.
K uvedené problematice mám dva dotazy:
- Není chybou z pohledu best practice a z pohledu bezpečnosti podědit Error4xxPresenter extends BasePresenter?
- Je zablokování signálu v Error4xxPresenteru správné chování frameworku, nebo tam mám nějakou chybu a mělo by to fungovat?
Díky za případné nápady.
Editoval m.brecher (24. 9. 2021 3:25)
- m.brecher
- Generous Backer | 889
Nakonec jsem dospěl k názoru, že ErrorPresenter by neměl z BasePresenteru dědit z bezpečnostních důvodů.
Error4xxPresenter jsem odvodil z Nette\Application\UI\Presenter, závislost pro vykreslení menu v layout.latte jsem předal metodou inject<component>() a problematickou komponentu logoutButton jsem zrušil úplně:
Neexistenci komponenty logoutButton v layout.latte při volání ErrrorPresenteru jsem ošetřil takto:
- corben
- Člen | 4
Řeším ten samý problém a nevím si s tím rady. Já na férovku (jako znouzecnost řešení) dal natvrdo link: <a href=„/?do=navigation-openModal“ class=„ajax“>…, kdy volám handler by správně měl být <a href=„{link openModal!}“…, když volám URL kde není 404 tak to funguje.
Prakticky by stačilo, kdyby se dal podvrhnout v ErrorPresenteru komponentám jiný presenter.
- Marek Bartoš
- Nette Blogger | 1297
Problém je, že Nette nedovoluje mít na error presenter routu a tak v něm ani nefungují „this“ odkazy. Trik je v tom v interním error presenteru jen forwardovat na jiný, routovatelný presenter.
Inspirovat se můžeš tady https://github.com/…8f/src/Error
Důležité jsou UI\ErrorForwardPresenter, UI\ErrorPresenterUtil a
Public\ErrorPresenter
ErrorForwardPresenter je ten hlavní, registrovaný do Nette. Stará se
o logování chyb a přesměrování na jiný error presenter.
Public\ErrorPresenter pak jen (skrze použitou traitu) vykresluje chybou
stránku podle kódu exception, případně simuluje chybu při přímém
přístupu uživatelem
- m.brecher
- Generous Backer | 889
Ahoj @MarekBartoš a @corben
nemám teď tolik času, ale zkusil jsem ke svému Error4xxPresenteru přidat odpovídající routu:
a Latte mě na neexistující stránce v layoutu který Error4xxPresenter používá vykresluje odkaz se signálem na komponentu:
Vykreslené html:
Tlačítko sice nefunguje, ale vzhledem k tomu, že se link na signál vykresluje bezpochyby to půjde nějak dodělat. Díky moc @MarekBartoš za navedení správným směrem.
Ošetřovat přímý vstup na url pro 404 není nutné, protože se tam vykreslí Page Not Found stránka.
Ale asi bude užitečné sem dát moje řešení – je totiž zcela jednoduché. Jiným uživatelům Nette by se mohlo hodit. Dám zítra, musím kód upravit.
Editoval m.brecher (5. 10. 2022 1:56)
- m.brecher
- Generous Backer | 889
Další testování ErrorPresenteru a Error4xxPresenteru přineslo důležité poznatky:
Jak jdou jednotlivé requesty při výjimce za sebou jsem prozkoumal v Tracy panelu DIC ve službě application.application, parametr requests.
ErrorPresenter obdrží Nette\Application\Request s method = ‚FORWARD‘
Error4xxPresenter také obdrží Nette\Application\Request s method = ‚FORWARD‘
Mám to v projektu tak, že Error4xxPresenter dědí z BasePresenter, tím podědí i všechny globální komponenty a služby pro ně:
Když přidám routu jak jsem ukázal kód o příspěvek výše, tak generování linků na signály funguje, ale co nefunguje je zpracování v presenteru handle<Signal>. Blokování provádí Nette Framework a sice kvůli property method=‚FORWARD‘ Nette\Application\Request-u který je v Error4xxPresenter-u. Když půjdeme na routu Error4xxPresenter-u přímo z url, tak to zase skončí chybou kvůli tomuhle:
Vyhození výjimky $presenter->error() jsem zakomentoval a ověřil, že přístupem z url, kdy je method=‚GET‘ zpracování signálů funguje.
Nojo, jenže Nette Framework automaticky podstrčí do forwardnutého presenteru request s metodou ‚FORWARD‘. To je naprosto v pořádku, takhle to být asi musí, ale problém je ten, že se tím na jedné straně zablokují signály, ale v dokumentaci Nette je forward na jiný presenter prezentován jako legální způsob jak přehodit zpracování requestu na jiný presenter víceméně na úrovni přesměrování $presenter->redirect(). Bezpochyby by bylo dobré do dokumentace vysvětlit, jaký je zamýšlený účel forwardu na jiný presenter a zda by se měl/neměl běžně používat + upozornit na to, že při forwardu jsou signály blokované (kvůli bezpečnosti?). Já si netroufám tohle do dokumentace zodpovědně psát, protože jde o zpracování výjimek a otázku bezpečnosti. Zde je nezbytná součinnost autora @DavidGrudl , ten ví proč při ‚FORWARD‘ blokuje Framework signály.
Takže, necháme forward z ErrorPresenteru na Error4xxPresenter být jak je a zaměříme se na to, jak zprovoznit signály. To je naštěstí velmi, velmi jednoduché. Protože díky routě Error4xxPresenter-u se generují GET linky na Error4xxPresenter, tak signály jdou přímo metodou GET (formuláře POST) na Error4xxPresenter. Ten jak jsem si udělal dědí z BasePresenteru a tam obsluha signálů je. Takže nechme požadavky GET/POST žít:
a to je všechno. Máme velmi jednoduchý a nativní způsob jak zajistit, aby na 404 stránce byl normální layout včetně zpracování signálů komponent i formulářů. Netestoval jsem jestli i ajaxové signály fungují, ale proč by neměly?
Blokovat signály pro ErrorPresenter je jasné, ale pokud člověk ví co dělá a dá si pozor, tak mě nenapadá důvod proč blokovat na vstupu do Error4xxPresenteru GET i POST a proč by nemohl mít Error4xxPresenter svoji routu. Tak jako tak se na té GET/POST routě uživateli vykreslí 404 – to je právě naprosto klíčové, člověk musí naprosto přesně vědět, že musí Error4xxPresenter dobře zabezpečit.
Současně jsem v kódu nette/web-project skeletonu objevil, že autor skeletonu pro nette/web-project asi zamýšlel, že by ErrorPresenter měl z requestu vysosat modul a separátor a forwardnout pro každý modul na samostatný Error4xxPresenter. To je skvělá myšlenka, protože pak je naprosto jednoduché, aby každý modul vykresloval svoje 404 do svého layoutu a 404stránky se mohly modul od modulu řešit individuálně.
Jenže je tam CHYBA (pokud se samozřejmě nemýlím já, což je dost často), protože se sosá jméno modulu ze špatného requestu – je potřeba jít o jeden request v historii requestů zpátky. Takhle jak to tam je udělané vyjde vždycky aktuální presenter Error a modul žádný (při standardní konfiguraci v common.neon). Sice to funguje, ale ne tak, jak bylo zamýšleno.
Správně by to mělo vypadat nějak takhle:
Budu rád, když se k problematice ErrorPresenteru vyjádří další zkušení znalci Nette.
- m.brecher
- Generous Backer | 889
Ještě mě napadlo, že v řešení ErrorPresenteru jak jsem ho nastínil výše – tj. že necháme přímé http požadavky GET/POST na routu Error4xxPresenteru projít a ošetříme to tak, že zajistíme, aby těmto požadavkům se vrátila korektní 404 stránka je asi jedno skryté nebezpečí. Vlastně to nesouvisí s propuštěním GET/POST požadavků, ale s následným děděním Error4xxPresenteru ze standardního BasePresenteru a vykreslování standardního layoutu webu včetně všech komponent, kde jsou různé ajaxové subkomponenty apod… Totiž, že by se uvnitř takto komplikované 404 stránky opět vyhodila 404. Obávám se, aby nenastala interní smyčka např.: 404 stránka ⇒ standardní layout webu ⇒ ajaxová komponenta ⇒ chyba 404 v komponentě ⇒ ErrorPresenter(FORWARD) ⇒ Error4xxPresenter(FORWARD) ⇒ 404 stránka ⇒ standardní layout webu ⇒ ajaxová komponenta .....
Přemýšlím, že tím, že Nette blokuje zpracování signálů pro presentery s metodou FORWARD se možná i předchází eventuálním potížím se zpracováním signálů a snižuje se riziko výše popsané interní smyčky.
Takže můj momentální závěr je – zvážit, zda je nutné v layoutu 404 stránky používat jakékoliv složité prvky, jestli není lepší je odstranit a vykreslit standardní layout webu bez jakýchkoliv komponent, potom není potřeba žádná routa na Error4xxPresenter a můžeme nechat standardní http požadavky GET/POST na Error4xxPresenter zablokované. Bezpečnost je hodně o jednoduchosti.
- Marek Bartoš
- Nette Blogger | 1297
Když se vyhodí exception v error presenteru, tak to způsobí fallback na interní error presenter v Nette.
- m.brecher
- Generous Backer | 889
Marek Bartoš napsal(a):
Když se vyhodí exception v error presenteru, tak to způsobí fallback na interní error presenter v Nette.
Ano souhlasím – a to by mohlo způsobit interní nekonečnou smyčku jak píšu nahoře, proto je potřeba:
- zamezit vzniku 404 exception v error presenteru tím, že 404 stránka bude velmi jednoduchá
- ošetřit v 404 stránce a jejím layoutu potenciální místa (komponenty), kde by další 404 exception mohla vzniknout např try/catch a při chybě takovou problematickou komponentu vůbec nevykreslovat
Editoval m.brecher (5. 10. 2022 10:53)
- Marek Bartoš
- Nette Blogger | 1297
Nezpůsobí. Skončí to na interním error presenteru, co je přímo v Nette. Ten tvůj to znova nezkusí.
Editoval Marek Bartoš (5. 10. 2022 11:39)
- m.brecher
- Generous Backer | 889
Marek Bartoš napsal(a):
Nezpůsobí. Skončí to na interním error presenteru, co je přímo v Nette. Ten tvůj to znova nezkusí.
Ano, máš pravdu, zkusil jsem co se stane, když vyvolám výjimku ve forwardnutém Error4xxPresenteru:
A v developer módu se vyhodí normální BadRequestException a zobrazí se chybová stránka Tracy i když ji máme v common.neon vypnutou:
A když se přepne developer mód na production tak se vyhodí 500.
OK, dobře vymyšleno, takže Nette takhle pěkně myslí i na případné výjimky v samotných error presenterech.
Editoval m.brecher (5. 10. 2022 15:26)
- m.brecher
- Generous Backer | 889
Ještě připojuji poznámku k nette/web-project ErrorPresenteru – jak jsem výše psal, že je potřeba pro zjištění modulu ve kterém nastal upravit kód ErrorPresenteru a jít o jeden request v historii zpět, tak to není tak jednoduché. Záleží na tom, jak kde BadRequestException vznikne. Pokud ji vyhodí nějaký presenter aplikace, třeba na základě neexistence záznamu v databázi, tak moje oprava funguje. Ale BadRequestException může vyhodit i Router ještě předtím, než se presenter vytvoří a žádný request se nevytvoří. Potom máme v historii služby application.application jenom jeden request a sice ten s forwardovaným ErrorPresenterem. Potom můj kód nefunguje. Takže pokud by se to mělo takto použít, bylo by potřeba ještě ošetřit případ, kdy se request vůbec nevytvoří a zřejmě v tomto případě forwardovat na nějaký preferovaný modul.
- Marek Bartoš
- Nette Blogger | 1297
Takže pokud by se to mělo takto použít, bylo by potřeba ještě ošetřit případ, kdy se request vůbec nevytvoří a zřejmě v tomto případě forwardovat na nějaký preferovaný modul.
Pokud je request null, tak se přesměruje na error ve veřejné části
webu
Viz https://github.com/…resenter.php#…