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>
Усложним типовой проект.
Добавим на главную страницу ("/") форму, которая при отправке данных будет:
- сохранять данные в файле -> Вызов монады IO в монаде Snap
- выдавать страницу ответа -> Передача на страницу данных методом 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 может пригодиться хаскелоидам. В общем дело чисто вкусовых предпочтений аудитории.