Devel.cz Lupa Měšec Podnikatel Root Zdroják.cz DigiZone Slunečnice Vitalia TopDrive KupDnes Navrcholu NovýTarif Dobrý web Weblogy Woko Jagg Computer.cz SK: MojeLinky

Hlavní navigace

Happstack: část druhá

V první části jsme si Happstack zběžně představili, nainstalovali a napsali si triviální program. Abychom si mohli předvést něco složitějšího, musíme se nejprve podívat na zpracování URL a na různé způsoby prezentace HTML kódu.

Tweetni to Twitter Jaggni to! Jagg Del.icio.us Delicious

Jádrem programů napsaných v Haskellu jsou typy. Ani Happstack není výjimkou. Jak jsme si ukázali minule, funkce simpleHTTP, pomocí které se dá jednoduše v prohlížeči vypsat text, jako jeden ze dvou parametrů požaduje parametr typu ServerPartT IO a (zkráceně ServerPart a), což jsme si označili jako aplikační kód. Typ IO značí, že se jedná o vstupně-výstupní akci a typ ServerPartT představuje součást webového serveru. Tento pojem v sobě zahrnuje HTTP požadavky (typ Request) a odpovědi (typ Response). Použití jedné nebo více součástí dává dohromady celý Happstack server, což je vlastně obyčejná serverová aplikace.

Pod tímto, na první pohled neintuitivním, konceptem si lze představit aplikaci v Happstacku jako jednu komplexní funkci, která na základě požadavku a dalších případných okolnostech vrátí odpověď. Hlavní součást HTTP požadavku je URL, jenž je v Happstacku vyhodnocováno pomocí monadických funkcí.

URL monády

Typický případ použití funkcí pro zpracování URL v Happstacku je jejich zapsání do seznamu a jejich následné sloučení pomocí funkce msum. Jestliže jste nikdy o této funkci neslyšeli, nalézá se v modulu Control.Monad a funguje podobně jako standardní seznamová funkce concat, jenom pracuje s monádami. Výsledkem její aplikace bude funkce, která postupně prochází jednotlivé položky seznamu. V případě, že některá položka seznamu bude vyhovovat HTTP požadavku, zpracuje se a procházení skončí; v opačném případě se bude pokračovat v procházení seznamu dále, dokud některá položka nebude vyhovovat. Pokud se žádná vyhovující položka v seznamu nenalezne, zobrazí se výchozí stránka s chybou 404 (nenalezeno). Dobrým nápadem je si vždy na posledním místě seznamu definovat vlastní chybovou stránku, jenž nahradí výchozí.

Základní funkce pro zpracování URL jsou nullDir a dir. První uspěje, pokud je zadaná adresa prázdná (jedná se tedy o kořenový adresář), druhá, pokud zadaná část adresy odpovídá řetězci. Jejich použití je bohužel trochu nekonzistentní, jak ukazuje příklad:

module Main where

import Control.Monad
import Happstack.Server

main :: IO ()
main = simpleHTTP nullConf handleURL

handleURL :: ServerPart Response
handleURL = msum [ do nullDir; ok $ toResponse "Výchozí stránka"
                 , dir "prvni" . ok $ toResponse "První stránka"
                 , dir "druhy" . ok $ toResponse "Druhá stránka"
                 , dir "treti" $ msum [ do nullDir; ok $ toResponse "Třetí stránka"
                                      , dir "ctvrty" . ok $ toResponse "Čtvrtá stránka"
                                      ]
                 , notFound $ toResponse "Chyba 404"
                 ]

Námi definovaná funkce handleURL vrací typ Response, tedy HTTP odpověď, proto je na všechny řetězce volána funkce toResponse, která je převede na odpovídající typ. Funkce ok je speciálním případem funkce return, ale chová se úplně stejně — naše odpovědí obalí typovým konstruktorem IO. Jak můžeme vidět u parametru treti, součásti URL lze zanořovat. V našem případě se tak v prohlížeči po zadání adresy http://localhost:8000/treti/ vypíše text „Třetí stránka“ a po zadání adresy http://localhost:8000/treti/ctvrty/ text „Čtvrtá stránka“. Poslední řádek definuje pomocí funkce notFound chybovou stránku.

Funkce dir zpracovává adresy bez ohledu na to, zda-li končí lomítkem nebo ne. Doporučuji toto chování změnit na přesměrování na verzi s lomítkem, aby byla každá adresa stránky unikátní. Provedeme to zapsáním následujících řádek do souboru .htaccess:

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([a-z0-9\/\_-]+[^/])$ $1/ [R=301,NC]

Funkce dir zachytí pouze statické části adresy stránek. Pro zpracování dynamických částí slouží funkce path. Ta jako parametr očekává funkci zpracovávající řetězec nebo číslo a vracející odpověď. Typu této funkce určuje, co se bude v adrese očekávat. Následující kód vypíše číslo zadané v URL:

handleURL :: ServerPart Response
handleURL = msum [ do nullDir; ok $ toResponse "Výchozí stránka"
                 , path pathNumber
                 , notFound $ toResponse "Chyba 404"
                 ]

pathNumber :: Int -> ServerPart Response
pathNumber n = ok . toResponse $ "Číslo v URL: " ++ show n

Po navštívení kupříkladu adresy http://localhost:8000/42/ se vypíše odpověď „Číslo v URL: 42“. Funkce pro zpracování URL můžeme mezi sebou libovolně kombinovat, jenom si vždy musíme dávat pozor, abychom vraceli hodnotu správného typu.

Kromě části URL můžeme určovat i jiné parametry dotazu. Kupříkladu funkce methodOnly určuje, kterou HTTP metodu od dotazu očekáváme. Protože se taktéž jedná o monadickou funkci, kombinuje se pomocí operátoru >>:

handleURL :: ServerPart Response
handleURL = msum [ do methodOnly GET >> nullDir; ok $ toResponse "Výchozí stránka"
                 , methodOnly GET >> path pathNumber
                 , notFound $ toResponse "Chyba 404"
                 ]

V tomto kódu vracíme na všechny dotazy využívající jiné metody než GET (tedy např. POST) chybovou stránku 404.

Psaní HTML kódu

Happstack přináší tři různé způsoby zadávání HTML kódu aplikace: přímé zadávání do řetězců, generování pomocí kombinátorů a šablony. První způsob je nejjednodušší, ale není příliš vhodný pro netriviální aplikace, protože pak náš kód nebývá příliš přehledný a ani bezpečný. Kombinátorový generátor je údajně o něco rychlejší než šablonovací systém, ten ovšem nabízí větší kontrolu nad kódem a práce s ním je přirozenější.

Ještě než se do toho pustíte, nainstalujte si balík Happstack.Helpers pomocí příkazu cabal install happstack-helpers, protože obsahuje navíc instance funkcí pro generování HTML kódu.

HTML řetězce

Aby se odlišil obyčejný text od HTML, modul Text.Html zavádí nový datový typ HtmlString. Když funkci toResponse předáme obyčejný řetězec typu String, je automaticky escapován — některé speciální znaky jsou převedeny na HTML entity. Musíme tedy převést typ String na HtmlString:

module Main where

import Happstack.Server
import Happstack.Helpers
import Text.Html hiding (HtmlString)

main :: IO ()
main = simpleHTTP nullConf . ok $ toResponse ref

ref :: HtmlString
ref = HtmlString "<p><a href=\"http://zdrojak.cz/\">Zdroják</a></p>"

Konstruktor HtmlString převede řetězec na HTML řetězec, v prohlížeči se tedy zobrazí odkaz. Kdybychom chtěli k řetězci před jeho převodem na HTML řetězec přiřetězit nějaký vstup, je nutné použít funkci s relativně dlouhým názvem stringToHtmlString, jinak nedojde k jeho escapování a my bychom si v aplikaci vytvořili potenciální bezpečnostní díru:

ref :: HtmlString
ref = HtmlString $ "<p><a href=\"http://zdrojak.cz/\">" ++ stringToHtmlString "<Zdroják>" ++ "</a></p>"

Generování HTML značek

Po nahlédnutí do dokumentace modulu Text.Html uvidíme velké množství funkcí se stejným názvem jako jsou některé HTML značky. To jsou již zmiňované kombinátory. S jejich použitím a kombinací operátorů << (vložení dovnitř prvku), +++ (přiřetězení dalšího prvku) a ! (modifikace atributu) si HTML kód lehce vygenerujeme:

ref :: Html
ref = header << thetitle << "Titulek" +++ body << p << anchor ! [href "http://zdrojak.cz/"] << "<Zdroják>"

Řetězce se automaticky escapují, takže by nemělo dojít k vložení nějakého zákeřného kódu zvenku. Jak už název napovídá, kombinátory jsou funkce, jež lze lehce kombinovat a spojovat dohromady, takže při dobrém návrhu nebývá problém s opakováním kódu.

HTML šablony

Šablony mají tu výhodu, že je oddělen HTML kód od aplikačního. Lehce tak můžeme zadat vytvoření HTML kódu někomu úplně jinému a pouze mu poskytnout seznam proměnných, které v šablonách chceme použít. Haskell obsahuje několik šablonovacích systémů, my se podíváme na HStringTemplate (modul Text.StringTemplate). Tento systém je založen na knihovně Java StringTemplate.

Nejprve si založíme adresář templates a vytvoříme v něm hlavní šablonu body.st:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>$ title $</title>
</head>

<body>
$ ref() $
</body>

Tato šablona obsahuje proměnnou title a bude inkludovat šablonu ref.st, kterou umístíme taktéž do adresáře templates:

<a href="http://zdrojak.cz/">$ link $</a>

Proměnné se v šabloně vyznačují pomocí dvou dolarových symbolů, inkluze další šablony stejně, jenom se za identifikátor dopíšou závorky. Kdybychom chtěli do šablony přidat komentář, stačí ho napsat mezi dvojici znaků $! a !$. Zbývá už jen napsat nový kód aplikace:

module Main where

import Happstack.Server
import Happstack.Helpers
import Control.Monad.Trans
import Text.StringTemplate
import Text.StringTemplate.Helpers
import Text.Html hiding (HtmlString)

main :: IO ()
main = simpleHTTP nullConf ref

escapeKeys :: [(String, String)] -> [(String, String)]
escapeKeys = map (\(x, y) -> (x, stringToHtmlString y))

ref :: ServerPart Response
ref = liftIO $ do
    templates <- directoryGroup "templates"
    let keys = escapeKeys [("title", "Titulek"), ("link", "<Zdroják>")]
        body = renderTemplateGroup templates keys "body"
    return . toResponse $ HtmlString body

Tento kód je o trochu složitější. V první řadě je zde potřeba funkce liftIO z modulu Control.Monad.Trans, jež zařídí převod z monády IO na jinou. Ve funkci ref jsou totiž načítány šablony, proto se celá funkce snaží zabalit do monády IO. To je taktéž důvod, proč musíme použít obecnější funkci return namísto funkce ok. Dále se zde volá funkce directoryGroup sloužící k načtení adresáře s šablonami. Funkce renderTemplateGroup vygeneruje šablonu spolu s proměnnými v proměnné keys a zajistí i inkluzi. Protože šablonový systém automaticky neescapuje proměnné, bylo potřeba sem připsat vlastní funkci escapeKeys, která zajišťuje ošetření vstupu.

Související odkazy

V následujícím dílu si vyzkoušíme zpracovávání formulářů.

Pavel Dvořák

Pavel Dvořák

Autor je dlouhodobým studentem Fakulty informatiky, webový nadšenec a programátor — nejraději programuje v jazycích Haskell a Python.

Školení Google Analytics pro pokročilé

DW - Školení Google Analytics
  • Jak využít nové funkce Google Analytics
  • Vyhodnocování kampaní díky používání Multichannel funnels
  • Kde návštěvníci vašeho webu utíkají z objednávacího procesu.
  • Nebudete opakovat časté chyby při vyhodnocování dat o návštěvnosti.

Detailní informace o školení Google Analytics pro pokročilé »

Přehled názorů

Re: Happstack: část druhá
Radek Miček 26. 5. 2010 09:19
Nový
       

Tento text je již více než dva měsíce starý. Chcete-li na něj reagovat v diskusi, pravděpodobně vám již nikdo neodpoví. Pro řešení aktuálních problémů doporučujeme využít naše diskusní fórum.

Zasílat nově přidané příspěvky e-mailem