9 января 2012 г.

Snap - Haskell web framework

Snap прежде всего первый веб фреймворк на Haskell с которым я познакомился и в общем, этот пост основан на том опыте который я получил тестируя этот фреймворк.
C помощью Snap я создал небольшой демонстрационный проект, который я представляю по ходу текста. Местами в тексте будет встречаться дословный перевод документации.
Официальный сайт
Погружение в Snap

Установка snap через cabal

#: sudo cabal update
#: sudo cabal install snap

Настройки параметров системы Ubuntu

Snap хранит свои исполняемые бинарные файлы в папке ~/.cabal/bin (snap к примеру), по этому для удобства их использования в файл ~/.bashrc следует добавить строчку:
PATH=$HOME/.cabal/bin:$PATH

Создание каркаса типового Snap проекта

#: mkdir myproject
#: cd myproject
#: snap init

команда snap init создает шаблон типового проекта в текущей директории.
Сборка проекта:
$: sudo cabal install
Запуск:
#: myproject -p 8000
Теперь по адресу  http://localhost:8000/ должен откликаться созданый сайт.

Файловая структура созданного проекта

dist/
log/
resources/
static/
templates/
src/
Site.hs
Main.hs
Application.hs
Попробую объяснить назначение файлов в папке src
Файл main.hs - это скелет проекта. В файле Application.hs определяется модуль Application, который связан с обработкой ответов сервера и который используется в файле Site.hs. Application.hs используется для расширения стандартного Snap обработчика HTTP запросов. Site.hs - это основной исходный код проекта, роутинг, контроллеры и т.п.
Обработка url адресов в файле Site.hs задается следующим образом:
site :: Application ()
site = route [ ("/",            index)
             , ("/echo/:stuff", echo)
             ]
       <|> serveDirectory "resources/static"

Url адресс "/" относится к функции-обработчику index, "/echo/:stuff" к функции-обработчику echo. Обе эти функции выполняют роли контроллеров.
Фрагмент url адреса отмеченный двоеточием (":") - захватываемое значение параметра stuff. Значение этого параметра можно получить в дальнейшем с помощью rqParams или getParam (как и параметры передаваемые через POST и GET методы).
Вторая половина отделенная от основной части "<|>" будет переправлять на содержимое папки resources/static/.
Дословно оператор "<|>" в монаде Snap означает "try a, and if it fails, try b".
К примеру файл style.css будет искаться в resources/static/. Если он там не найдется метод serveDirectory вынудит Snap выдать ответ "404 Not Found".
Функция/метод/обработчик echo выглядит следующим образом
echo :: Handler App App ()
echo = do
    message <- decodedParam "stuff"
    heistLocal (bindString "message" (T.decodeUtf8 message)) $ render "echo"
  where
    decodedParam p = fromMaybe "" <$> getParam p

Здесь функция decodedParam выступает в качестве обёртки над методом  getParam. В качестве аргумента передается ей имя параметра заданного в роутинге ("stuff"), но можно и передать и имя параметра post при необходимости.
С помощью метода heistLocal используем  полученное значение для построения страницы с помощью шаблона "echo" (render "echo").
А при помощи метода bindString подменяем параметр в шаблоне <message/> на значение (T.decodeUtf8 message).

Snap использует систему шаблонизации Heist. Довольно гибкая и на оф. сайте представлено  достаточно документации.
В предыдущем примере указанный шаблон echo располагается в файле resources/templates/echo.tpl, который представляет из себя html файл с расставленными полями подстановки/параметрами.
Поля подстановки имеют вид ${key} или <key/>
Вот так выглядит шаблон echo:
<html>
<body>
  <div id="content">
    <h1>Is there an echo in here?</h1>
  </div>
  <p>You wanted me to say this?</p>
  <p>"<message/>"</p>
  <p><a href="/">Return</a></p>
</body>
</html>
Усложним типовой проект.
Добавим на главную страницу ("/") форму, которая при отправке данных будет:
  1. сохранять данные в файле -> Вызов монады IO в монаде Snap 
  2. выдавать страницу ответа -> Передача на страницу данных методом POST 
Добавим в файл Site.hs:
import System.IO
import qualified Data.ByteString as BS
...
-- пример на обработку post параметров ($_POST[]) и вызова монады IO из монады Snap (Понадобится чтобы сохранить данные в файл)
command3post :: Handler App App ()
command3post = do
    input_name <- decodedPost "firstname" -- ByteString
    second_name <- decodedPost "secondname"
    liftIO $ appendFile "output.txt" (((T.unpack . T.decodeUtf8. BS.concat ) [input_name, " : " ,second_name])++"\n")
-- Пояснение справа на лево:
-- BS.concat - слияние списка элементов ByteString
-- T.unpack . T.decodeUtf8 - преобразуем ByteString в String
-- appendFile - добавляет строку в файл
-- liftIO - преобразует монаду IO в монаду Snap
    heistLocal (bindString "firstname" (T.decodeUtf8 input_name)) $ render "formrezult"
    where
        decodedPost p = fromMaybe "" <$> getParam p 
А список маршрутов приведем к следующему виду:
routes :: [(ByteString, Handler App App ())]
routes = [ ("/",            index)
         , ("formaction", command3post)
         , ("/echo/:stuff", echo)
         , ("", with heist heistServe)
         , ("", serveDirectory "resources/static")
         ] 
В файл resources/templates/index.tpl добавить
<form method="post" action="formaction">
    <p>First name: <input type="text" name="firstname"/></p>
    <p>Second name: <input type="text" name="secondname"/></p>
    <input type="submit" value="send"/>
</form> 
Создать файл resources/templates/formrezult.tpl
<!DOCTYPE HTML>
<html>
  <head>
    <title>Form request Page</title>
  </head>
  <body>
    <div id="content">
      <h1>Form Rezult</h1>
    </div>
    <p>Thank you <firstname/></p>
    <p><a href="/">Return on main page</a></p>
  </body>
</html>
Вот и все. Проект можно собирать и запускать.
Скачать проект целеком.

Вместо вывода

Snap фреймворк очень напоминает работу с RoR или ASP.Net MVC. Хотя я и не вижу явных преимуществ перед другими фреймворками как на Haskell так и на других ЯП, Snap может пригодиться хаскелоидам. В общем дело чисто вкусовых предпочтений аудитории.

Комментариев нет:

Отправить комментарий