Cookies jsou jednoduchým prostředkem, kterým může webový server uchovávat na straně uživatele data mezi jednotlivými sezeními. Velmi často se cookies používají k rychlé identifikaci návštěvníka, který se na server nemusí ručně znovu přihlašovat, k nastavování jeho oblíbeného barevné schématu apod.
Seaside samozřejmě cookies podporuje a dovoluje s nimi poměrně pohodlně pracovat. Kvůli programátorsky velice příjemné kontrole toku řízení seasidovských aplikací se ale cookies využívají poměrně málo, a to především k účelům, ke kterým jsou primárně určeny. V jiných prostředích se často používají i pro přenos informací mezi stránkami v rámci jednoho uživatelova sezení. Takto vytvořené aplikace mají problémy, pokud má uživatel cookies v prohlížeči vypnuty.
Vytvoříme si demonstrační komponentu, která bude počítat s tím, že s ní pracuje nějaký definovaný uživatel, jehož jméno si uchovává v instanční proměnné.
WAComponent subclass: #CookiesTest
instanceVariableNames: 'user'
classVariableNames: ''
poolDictionaries: ''
category: 'CookiesTest'
Při inicializaci této komponenty předpokládáme, že už s ní uživatel někdy pracoval, a pokusíme se jej pomocí cookies identifikovat. Napřed si ale zjistíme, jestli má uživatel podporu cookies ve svém prohlížeči vůbec aktivovánu. Pokud ji mít nebude, nijak ho to neomezí, pouze bude nucen se identifikovat při začátku každého sezení znovu. Na to, že má coocies vypnuté, ho pomocí dekorátoru přidávajícího zprávu ke komponentě upozorníme.
initialize
(self session checkForCookies)
ifTrue: [ self setUserFromCookies ]
ifFalse: [ self addMessage: 'Varovani: cookies nejsou povoleny' ].
Cookies je slovník (třída Dictionary) textových záznamů obsažený v aktivním sezení. My použijeme klíč user a jako hodnotu jméno uživatele. Pokud uživatel přistupuje ke komponentě poprvé, nebude slovník cookies tento klíč obsahovat a instanční proměnná user se naplní nedefinovanou hodnotou.
setUserFromCookies
| cookies |
cookies := self session currentRequest cookies.
user := cookies at: #user ifAbsent: [].
Při vlastním vykreslení komponenty se podíváme, jestli jí byl uživatel již přiřazen. Pokud ano, vypíšeme v komponentě jeho jméno. V případě, že uživatel definován není, tzn. že uživatel buď přistupuje poprvé, má vypnutou podporu cookies, nebo platnost předchozí již vypršela, zobrazíme mu přihlašovací dialog tvořený vstupním textovým polem a odesílacím tlačítkem.
renderContentOn: html
user ifNil: [
html paragraph: 'Prihlaste se, prosim:'.
html form: [
html textInputWithValue: 'zde vlozte sve jmeno' callback: [:name |
user := name.
self setCookieForNewUser: name ].
html submitButton ] ]
ifNotNil: [
html paragraph: 'Uzivatel: ', user ].
Během přihlášení se pokusíme klíč pro identifikaci uživatele do cookies zapsat. K tomu se používá třída WACookie. Platnost záznamu nastavíme na pět dnů. Při nastavení cookie dojde k obnovení stránky. Pokud se cookie nepodaří korektně nastavit, Seaside negeneruje žádnou chybu.
setCookieForNewUser: userName
self session redirectWithCookie:
(WACookie new
key: #user;
value: userName;
expireIn: 5 days;
yourself).
Zpracování výjimek a chyb
V Seaside se zpracovávají výjimky a chyby podobně jako v běžných smalltalkovských aplikacích. Některé rozdíly ale lze nalézt.
Jako příklad si vezměme komponentu, která generuje chyby v různých kontextech.
renderContentOn: html
[
[ 1/0 ] ifError: [html text: 'chyba 1'. ].
html break.
html anchorWithAction: [ [ 1/0] ifError: [self inform: 'Nastala chyba 1'] ]
text: 'chyba 2'.
html break.
html anchorWithAction: [
[self call: VadnaKomponenta new]
ifError: [self inform: 'Nastala chyba 3'] ]
text: 'chyba 3'.
html break.
html anchorWithAction: [ 1/0 ] text: 'chyba 4'.
] ifError: [ self inform: 'chyba aplikace' ].
První případ (chyba 1) je ošetřená výjimka při vykreslování komponenty. Tato výjimka je zpracována korektně a do stránky je vepsán informační text.
Ve druhém případě (chyba 2) ošetřujeme výjimku, která nastává v bloku prováděném při kliknutí na odkaz. I v tomto případě dojde ke korektnímu zpracování a uživateli se zobrazí informační dialog.
Obě tyto chyby mají podobný charakter. Jedná se o výjimky, které jsou vyvolány a zpracovány v kontinuálně prováděném kódu v rámci jedné stránky.
Ve třetím případě voláme komponentu, která ve vykreslovací metodě generuje chybu a vzniklou výjimku nijak neošetřuje. Podle uvedeného zápisu bychom předpokládali, že vyvstalá výjimka bude zachycena v jí nadřazené komponentě, tedy že se uživateli vykreslí informační dialog s textem ‚Nastala chyba 3‘. Bohužel se ale místo toho uživateli zobrazí běžná informační stránka Seaside s popisem chyby, výpisem zásobníku volání a s odkazem, který spustí nad procesem s výjimkou standardní debugger v nativním uživatelském rozhraní, což ovšem nemá u serverů běžících na pozadí bez možnosti výstupu na display žádný význam, pokud k image nepřistupujeme přes VNC klienta.
Stejného výsledku se dočkáme i ve čtvrtém případě, kdy v akci odkazu nastane neošetřená výjimka, a to i přesto, že se tuto výjimku snažíme odchytit a zpracovat ve zdánlivě nadřazeném bloku.
Zde lehce prosvítá reálný bezestavový charakter webových aplikací. Výjimky je potřeba ošetřovat tam, kde vzniknou – tedy v rámci zobrazované stránky. O zachycení vzniklých chyb se stará přímo Seaside a ošetřuje je právě vytvořením stránky s popisem chyby.
O zpracování chyb při generování stránek se stará třída WAWalkbackErrorHandler. Její hlášení obsahují nejnutnější ladící informace, ale při nasazení hotových aplikací jsou zbytečně obsáhlá, nemají vzhled odpovídající provedení zbytku aplikace a v neposlední řadě představují i nemalé bezpečnostní riziko. Snadno se totiž při popisu objektů mohou dopustit faux pa a zobrazit některé citlivé informace. Hodně nebezpečný je například výpis kolekcí. Omezovat kvůli tomu popisné řetězce objektů (metoda printOn:) není právě vhodné řešení, protože při programování naopak vyžadujeme, aby jejich popis byl co nejkomplexnější.
Problém bezpečnosti lze vyřešit tak, že Seaside definujeme, aby použila jinou standardní třídu pro zpracování chyb pojmenovanou WASimpleErrorHandler. Ta vypíše pouze nadpis Internal Error a doplní jej stručným textovým popisem.
Změna třídy, která se má použít pro zpracování neošetřených výjimek, se provádí v konfiguraci aplikace pod položkou Error Handler. Samozřejmě je lépe tuto informace nastavit přímo při registraci vaší aplikace, tedy například v inicializační metodě její kořenové komponenty, než tak učinit přes webové rozhraní.
initialize
| app |
app := self registerAsApplication: 'Obchod'.
app preferenceAt: #errorHandler put: WASimpleErrorHandler.
Tím samozřejmě nevyřešíme vzhledovou konzistenci. Uživateli se zobrazí pouze velmi strohá bílá stránka. Pokud se rozhodnete ji změnit, stačí si vytvořit novou třídu jako potomka třídy WAErrorHandler po vzoru těch již existujících. Konkrétně je třeba upravit metody handleError: respektive handleWarning:. Bohužel v této fázi ošetřování výjimek již přicházíte skoro o všechny výhody, které Seaside nabízí, a budete se pravděpodobně muset spokojit s pouhou ručně vygenerovanou statickou stránkou. Následkem toho může být problematické (nicméně řešitelné) použití stejných stylů, jako má zbytek vaší dynamicky generované aplikace.
Není nutné zůstat u pouhého výpisu chybového hlášení uživateli. Podobné neošetřené výjimky je velmi vhodné logovat, ukládat do databáze, nechávat si posílat informační e-maily apod.
SMTPClient
deliverMailFrom: 'seaside@seaside.tk'
to: (Array with: 'administrator@seaside.tk')
text: 'From: Seaside
Subject: Application error
Error description'
usingServer: 'smtp.server.com'
Stránky s chybami, jako je např. vypršení platnosti stránky, už tak lehce předefinovat nejdou. Programátor má dvě možnosti. Buď příslušné metody modifikuje přímo v Seaside, což je nejjednodušší řešení, nebo se pokusí o čistší přístup a zainteresované třídy a metody přetíží.
WAApplication subclass: #MyApp
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Store'
handleExpiredRequest: aRequest
^ WAResponse new nextPutAll: '<html>
<head><title>Platnost stranky vyprsela</title></head>
<body><h1>Platnost stranky vyprsela</h1>
</body></html>'
Protože Seaside nepředpokládá, že bude místo WAApplication použita jiná třída, je potřeba u kořenové komponenty, z níž se bude komponenta registrovat, vytvořit následující třídní metodu:
applicationWithPath: aString
| app |
app := MyApp path: aString.
app configuration addAncestor: WARenderLoopConfiguration localConfiguration.
app preferenceAt: #rootComponent put: self.
^ app
Náš seriál se již nezadržitelně blíží ke konci. Příště nás čeká poslední díl, kde se krátce zmíníme o živých akcích, češtině a dalších tématech, na která se v předchozích dílech nedostalo.