
Картинка для привлечения внимания, clckwrks — веб-фреймворк, тесно связанный с Happstack.
Happstack — веб-фреймворк с большими возможностями и богатым API, который развивался на протяжении последних семи лет, чтобы соответствовать нуждам повседневной веб-разработки. К сожалению, богатый и гибкий API может быть бесполезным и запутывающим, когда вам нужно что-то простое. Однако многие и не догадываются, что под крылом Happstack кроется очень элегантный и простой в использовании веб-фреймворк Happstack Lite.
Предисловие
Happstack Lite представляет из себя простую в своей структуре и легкую в использовании версию Happstack. Для его создания разработчики:
- Собрали все основные типы и функции, которые вам нужны для разработки веб-приложения, в единственном модуле
Happstack.Lite, так что вам не нужно рыскать по модулям в поисках того, что вам нужно. - Дали функциям намного более простые сигнатуры, исключив монадные трансформеры и избавившись от большинства классов типов.
- Создали этот туториал, который в менее чем 2000 словах описывает все основные вещи, которые вам нужно знать, чтобы начать писать веб-приложение.
Но самое главное — Happstack Lite почти полностью совместим с Happstack! Если вы разрабатываете приложение на Happstack Lite, и вам нужна продвинутая возможность из Happstack, вы можете просто-напросто импортировать соответствующий модуль и использовать его.
Чтобы перевести проект с Happstack Lite на обычный, вам понадобится внести всего лишь 4 небольших изменения:
import Happstack.Liteзаменить наimport Happstack.Serverserve Nothingзаменить наsimpleHTTP nullConf- добавить
import Control.Monad (msum) - добавить явный вызов
decodeBody(подробности)
В то время как Happstack Lite легковесен по сравнению с обычным Happtsack, он по-прежнему является полнофункциональным фреймворком наряду с другими веб-фреймворками на Хаскеле.
В целях упрощения разработчики отказались от использования некоторых продвинутых библиотек, которые работают с Happstack. Если вы заинтересованы в фреймворке с типобезопасными URL, типобезопасными формами, HTML-синтаксисом в литералах и многим другим, то возможно вам стоит рассмотреть Happstack Foundation. Кривая обучения выше, но дополнительная надежность стоит того. Поскольку эти библиотеки построены поверх ядра Happstack, то изученный в данном туториале материал пригодится и при их применении.
Для более глубокого ознакомления вы можете прочитать Happstack Crash Course (который я тоже переведу, если будет проявлен интерес к этой статье — прим. пер.)
Запуск сервера
Для начала нам понадобится пара расширений языка:
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
Теперь подключим некоторые библиотеки:
module Main where import Control.Applicative ((<$>), optional) import Data.Maybe (fromMaybe) import Data.Text (Text) import Data.Text.Lazy (unpack) import Happstack.Lite import Text.Blaze.Html5 (Html, (!), a, form, input, p, toHtml, label) import Text.Blaze.Html5.Attributes (action, enctype, href, name, size, type_, value) import qualified Text.Blaze.Html5 as H import qualified Text.Blaze.Html5.Attributes as A
Чтобы запустить приложение, мы вызываем функцию serve. Первый аргумент — конфигурация, она опциональна. Второй аргумент — наше, непосредственно, веб-приложение.
main :: IO () main = serve Nothing myApp
Веб-приложение имеет тип ServerPart Response. Вы можете считать ServerPart веб-эквивалентом монады IO.
(По умолчанию используется порт 8000, то есть увидеть ваше приложение вы можете по адресу http://localhost:8000/ — прим. пер.)
Статичные адреса
Вот и наше веб-приложение:
myApp :: ServerPart Response myApp = msum [ dir "echo" $ echo , dir "query" $ queryParams , dir "form" $ formPage , dir "fortune" $ fortune , dir "files" $ fileServing , dir "upload" $ upload , homePage ]
В самом общем виде наше приложение — просто несколько обработчиков, поставленных в соответствие статичным адресам.
dir используется, чтобы обработчик выполнялся только при успешном сопоставлении статичных компонентов пути. Например, dir "echo" успешно сработает с адресом http://localhost:8000/echo. Чтобы назначить обработчик для адреса "/foo/bar", достаточно просто написать dir "foo" $ dir "bar" $ handler.
Выполняется попытка последовательно применить каждый обработчик, до тех пор пока один из не вернет реузльтат успешно. В данном случае — Response.
Мы преобразуем список обработчиков в один единственный с помощью msum.
Последний обработчик — homePage — ничем не ограничен (к нему не применяется dir — прим. пер.), поэтому он всегда будет вызван, если ни один из других обработчиков не сработает успешно.
HTML-шаблоны
Поскольку создаем мы веб-приложение, то нам понадобится создавать HTML-страницы. Мы можем делать это, используя Blaze, по которому тоже есть туториал.
Тема шаблонизации HTML вызывает масштабные разногласия в сообществе. Ни одна шаблонная система не может удовлетворить всех, так что Happstack поддерживает множество разных систем. В данном туториале применяется Blaze, потому что он поддерживается и базируется на чисто функциональных комбинаторах. Если вам нравятся шаблоны времени компиляции, но вы желаете HTML-синтаксис, можете рассмотреть HSP. Если вы негативно относитесь к шаблонам в своем коде и предпочитаете внешние XML-файлы, рассмотрите Heist.
Удобно иметь шаблонную функцию, которая сочетает в себе общие элементы для всех страниц веб-приложения, такие как импорт CSS, внешние JS-файлы, меню и т. д. В данном туториале мы будем использовать очень простой шаблон:
template :: Text -> Html -> Response template title body = toResponse $ H.html $ do H.head $ do H.title (toHtml title) H.body $ do body p $ a ! href "/" $ "На главную"
Тогда главная страница выглядит вот так:
homePage :: ServerPart Response homePage = ok $ template "Главная страница" $ do H.h1 "Привет!" H.p "Писать приложения на Happstack Lite быстро и просто!" H.p "Зацени эти крутые приложения:" H.p $ a ! href "/echo/secret%20message" $ "Эхо" H.p $ a ! href "/query?foo=bar" $ "Параметры запроса" H.p $ a ! href "/form" $ "Обработка формы" H.p $ a ! href "/fortune" $ "Печеньки-предсказания (куки)" H.p $ a ! href "/files" $ "Доступ к файлам" H.p $ a ! href "/upload" $ "Размещение файлов"
Функция ok устанавливает для страницы HTTP-код «200 OK». Есть и другие вспомогательные функции, например notFound устанавливает код «404 Not Found», seeOther — «303 See Other». Чтобы установить HTTP-код числом, используется setResponseCode.
Динамические части адреса
Функция dir выполняет сопоставление только со статичной частью адреса. Мы можем использовать функцию path, чтобы извлечь значение из динамической части адреса и опционально сконвертировать его в некий тип, такой как Integer. В данном примере мы просто выводим на экран динамическую часть пути. Для проверки посетите http://localhost:8000/echo/fantastic
echo :: ServerPart Response echo = path $ \(msg :: String) -> ok $ template "Эхо" $ do p $ "Динамическая часть адреса: " >> toHtml msg p "Измени адрес страницы, чтобы вывести на экран что-то иное."
Параметры запроса
Мы также можем получить значения строковых параметров запроса. Строка запроса — это часть адреса, которая выглядит как "?foo=bar". Попробуйте посетить http://localhost:8000/query?foo=bar
queryParams :: ServerPart Response queryParams = do mFoo <- optional $ lookText "foo" ok $ template "Параметры запроса" $ do p $ "foo = " >> toHtml (show mFoo) p $ "Измени адрес страницы, чтобы установить другое значение foo."
В случае, если параметр запроса не установлен, функция lookText вернет mzero. В данном примере мы используем optional из модуля Control.Applicative, так что в итоге получаем значение типа Maybe.
Формы
Мы можем использовать lookText и для получения данных с форм.
formPage :: ServerPart Response formPage = msum [ viewForm, processForm ] where viewForm :: ServerPart Response viewForm = do method GET ok $ template "form" $ form ! action "/form" ! enctype "multipart/form-data" ! A.method "POST" $ do label ! A.for "msg" $ "Напиши что-нибудь умное" input ! type_ "text" ! A.id "msg" ! name "msg" input ! type_ "submit" ! value "Отправить" processForm :: ServerPart Response processForm = do method POST msg <- lookText "msg" ok $ template "form" $ do H.p "Ты написал:" H.p (toHtml msg)
Мы используем ту же функцию lookText, что и в предыдущем параграфе, чтобы получить данные из формы. Вы также могли заметить, что мы используем функцию method, чтобы различать GET и POST запросы.
Когда пользователь просматривает форму, браузер запрашивает страницу /form с помощью GET. В HTML-теге form в качестве действия по нажатию кнопки мы указали открытие этой же страницы, но с помощью аттрибута выбрали метод POST.
Печеньки! (HTTP-cookies)
Данный пример расширяет пример с формой, сохраняя сообщение в куки. Это значит, пользователь может покинуть страницу, а когда вернется назад — страница будет помнить сохраненное сообщение.
fortune :: ServerPart Response fortune = msum [ viewFortune, updateFortune ] where viewFortune :: ServerPart Response viewFortune = do method GET mMemory <- optional $ lookCookieValue "Печеньки-предсказания (куки)" let memory = fromMaybe "Твое будущее будет определено с помощью веб-технологий!" mMemory ok $ template "fortune" $ do H.p "Сообщение из твоей печеньки-предсказания (куки):" H.p (toHtml memory) form ! action "/fortune" ! enctype "multipart/form-data" ! A.method "POST" $ do label ! A.for "fortune" $ "Измени свою судьбу: " input ! type_ "text" ! A.id "fortune" ! name "new_fortune" input ! type_ "submit" ! value "Отправить" updateFortune :: ServerPart Response updateFortune = do method POST fortune <- lookText "new_fortune" addCookies [(Session, mkCookie "fortune" (unpack fortune))] seeOther ("/fortune" :: String) (toResponse ())
(Игру слов между HTTP-cookie и fortune cookie мне сохранить как-то не удалось — прим. пер.)
По сравнению с предыдущим примером появилось совсем немного нового:
lookCookieValueработает точно так же, как иlookText, с той лишь разницей, что ищет значение в куках, а не параметрах запроса или форме.addCookiesотправляет куки браузеру и имеет следующий тип:addCookies :: [(CookieLife, Cookie)] -> ServerPart ()CookieLifeопределяет, как долго куки существуют и считаются корректными.Sessionозначает срок жизни для куки до закрытия окна браузера.mkCookieпринимает имя куки, ее значение, и создаетCookie.seeOther(т. е. 303, редирект) говорит браузеру сделать новыйGET-запрос на страницу/fortune.
Доступ к файлам
В большинстве веб-приложений возникает нужда предоставить доступ к статичным файлам с диска, таким как изображения, таблицы стилей, скрипты и т. д. Мы можем достичь этого с помощью функции serveDirectory:
fileServing :: ServerPart Response fileServing = serveDirectory EnableBrowsing ["index.html"] "."
Первый аргумент определяет, должна ли serveDirectory создать список файлов в директории, чтобы их можно было просматривать.
Второй аргумент — список файлов индексации. Если пользователь запрашивает просмотр директории и она содержит файл индексации, то вместо списка файлов будет отображен он.
Третий аргумент — путь к директории, к которой предоставляется доступ. В данном примере мы обеспечиваем доступ к текущей директории.
На поддерживаемых платформах (Linux, OS X, Windows), функция serveDirectory автоматически использует sendfile() для доступа к файлам. В sendfile() применяются низкоуровневые операции ядра, обеспечивающие перенос файлов с накопителя в сеть с минимальной нагрузкой на процессор и максимальным использованием сетевого канала.
Размещение файлов
Обработка загрузки файлов на сервер достаточно прямолинейна. Мы создаем форму, как и в предыдущем примере, но вместо lookText используем lookFile.
upload :: ServerPart Response upload = msum [ uploadForm , handleUpload ] where uploadForm :: ServerPart Response uploadForm = do method GET ok $ template "Размещение файла" $ do form ! enctype "multipart/form-data" ! A.method "POST" ! action "/upload" $ do input ! type_ "file" ! name "file_upload" ! size "40" input ! type_ "submit" ! value "upload" handleUpload :: ServerPart Response handleUpload = do (tmpFile, uploadName, contentType) <- lookFile "file_upload" ok $ template "Файл загружен" $ do p (toHtml $ "Временный файл: " ++ tmpFile) p (toHtml $ "Имя загрузки: " ++ uploadName) p (toHtml $ "Тип контента: " ++ show contentType)
Когда файл загружен, мы храним его во вре́менной локации. Временный файл будет автоматически удален, когда сервер отправит ответ браузеру. Это гарантирует, что неиспользуемые файлы не загрязняют дисковое пространство.
В большинстве случаев, пользователь не хочет загрузить файл только ради того, чтобы он был удален. Обычно в обработчике вызываются moveFile или copyFile, чтобы переместить (или скопировать) файл в его перманентную локацию.
От переводчика
Автор статьи предполагает наличие базовых знаний языка Хаскель. Для установки Happstack воспользуйтесь инструкцией на сайте.
Если вас заинтересовал этот фреймворк, я рекомендую ознакомиться с его полной версией, а также основанном на нем clckwrks. Приятной разработки!
ссылка на оригинал статьи http://habrahabr.ru/post/185524/
Добавить комментарий