Happstack Lite: Haskell Web Framework
- Transfer
- Tutorial
- Recovery mode
A picture to get attention, clckwrks is a web framework closely linked to Happstack.
Happstack is a great- featured web framework with a rich API that has evolved over the past seven years to meet the needs of everyday web development. Unfortunately, a rich and flexible API can be useless and confusing when you need something simple. However, many do not realize that under the wing of Happstack lies a very elegant and easy-to-use Happstack Lite web framework.
Foreword
Happstack Lite is a simple in structure and easy to use version of Happstack. To create it, the developers:
- We put together all the basic types and functions that you need to develop a web application in a single module
Happstack.Lite
, so you do not need to scour the modules in search of what you need. - They gave the functions much simpler signatures, eliminating monadic transformers and getting rid of most type classes.
- Created this tutorial, which in less than 2,000 words describes all the basic things you need to know in order to start writing a web application.
But most importantly - Happstack Lite is almost completely compatible with Happstack! If you are developing an application on Happstack Lite, and you need an advanced feature from Happstack, you can simply import the corresponding module and use it.
To transfer a project from Happstack Lite to regular, you only need to make 4 small changes:
import Happstack.Lite
replaced byimport Happstack.Server
serve Nothing
replaced bysimpleHTTP nullConf
- add
import Control.Monad (msum)
- add explicit call
decodeBody
( details )
While Happstack Lite is lightweight compared to the regular Happtsack, it is still a fully functional framework along with other Haskell web frameworks.
In order to simplify, the developers abandoned the use of some advanced libraries that work with Happstack. If you are interested in a framework with type-safe URLs, type-safe forms, literal HTML syntax, and more, you might consider the Happstack Foundation. The learning curve is higher, but the added reliability is worth it. Since these libraries are built on top of the Happstack core, the material studied in this tutorial is also useful for their application.
For a deeper introduction, you can read the Happstack Crash Course (which I will also translate if interest in this article is shown - approx. trans. )
Server start
First, we need a couple of language extensions:
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
Now we will connect some libraries:
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
To launch the application, we call the function
serve
. The first argument is the configuration; it is optional. The second argument is our web application itself.main :: IO ()
main = serve Nothing myApp
The web application is of type
ServerPart Response
. You can be considered the ServerPart
web equivalent of a monad IO
. ( The default port is 8000, that is to see your application, you can at http: // localhost: 8000 / - approx lane.. )
Static Addresses
Here is our web application:
myApp :: ServerPart Response
myApp = msum
[ dir "echo" $ echo
, dir "query" $ queryParams
, dir "form" $ formPage
, dir "fortune" $ fortune
, dir "files" $ fileServing
, dir "upload" $ upload
, homePage
]
In its most general form, our application is just a few handlers that are mapped to static addresses.
dir
It is used so that the handler is executed only when the static components of the path are successfully mapped. For example, dir "echo"
it will work successfully with the address localhost:8000/echo
. To assign a handler to an address "/foo/bar"
, just write it dir "foo" $ dir "bar" $ handler
. An attempt is made to apply each handler sequentially until one of them returns a successful result. In this case -
Response
. We will convert the list of handlers into one single with the help
msum
. The last handler -
homePage
- is not limited by anything ( dir does not apply to it - approx.), so it will always be called if none of the other handlers succeed.HTML templates
Since we are creating a web application, we will need to create HTML pages. We can do this using Blaze, which also has a tutorial .
The topic of HTML templating is causing widespread controversy in the community. No template system can satisfy everyone, so Happstack supports many different systems. This tutorial uses Blaze because it is supported and based on purely functional combinators. If you like compile-time patterns but want HTML syntax, you can consider HSP. If you are negative about templates in your code and prefer external XML files, consider Heist.
It’s convenient to have a template function that combines common elements for all pages of a web application, such as importing CSS, external JS files, menus, etc. In this tutorial we will use a very simple template:
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 "/" $ "На главную"
Then the main page looks like this:
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" $ "Размещение файлов"
The function
ok
sets the HTTP code “200 OK” for the page. There are other auxiliary functions, for example, notFound
sets the code "404 Not Found", seeOther
- "303 See Other". To set the HTTP code by number is used setResponseCode
.Dynamic parts of the address
The function
dir
only matches the static part of the address. We can use the function path
to extract the value from the dynamic part of the address and optionally convert it to some type, such as Integer
. In this example, we simply display the dynamic part of the path. To check visit http: // localhost: 8000 / echo / fantasticecho :: ServerPart Response
echo =
path $ \(msg :: String) ->
ok $ template "Эхо" $ do
p $ "Динамическая часть адреса: " >> toHtml msg
p "Измени адрес страницы, чтобы вывести на экран что-то иное."
Request Parameters
We can also get the values of string query parameters. The query string is the part of the address that looks like "
?foo=bar
". Try visiting http: // localhost: 8000 / query? Foo = barqueryParams :: ServerPart Response
queryParams =
do mFoo <- optional $ lookText "foo"
ok $ template "Параметры запроса" $ do
p $ "foo = " >> toHtml (show mFoo)
p $ "Измени адрес страницы, чтобы установить другое значение foo."
If the query parameter is not set, the function
lookText
will return mzero
. In this example, we use optional
from the module Control.Applicative
, so that in the end we get the type value Maybe
.Forms
We can use
lookText
to receive data from forms.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)
We use the same function
lookText
as in the previous paragraph to get the data from the form. You may also have noticed that we use the function method
to distinguish GET
and POST
requests. When the user views the form, the browser requests the page
/form
using GET
. In the HTML tag, form
as an action at the click of a button, we indicated the opening of the same page, but using the attribute we selected a method POST
.Cookies! (HTTP cookies)
This example extends the example with the form by storing the message in cookies. This means the user can leave the page, and when he returns, the page will remember the saved message.
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 ())
( I somehow could not save the pun between the HTTP cookie and fortune cookie - approx. Per. ) Compared to the previous example, quite a bit new appeared:
lookCookieValue
It works in exactly the same way as it does,lookText
with the only difference being that it searches for value in cookies, not query parameters or form.addCookies
sends cookies to the browser and has the following type:addCookies :: [(CookieLife, Cookie)] -> ServerPart ()
CookieLife
determines how long cookies exist and are considered correct.Session
means the lifetime for cookies before closing the browser window.mkCookie
accepts the name of the cookie, its meaning, and createsCookie
.seeOther
(i.e., 303, redirect) tells the browser to make a newGET
request for the page/fortune
.
File access
In most web applications, there is a need to provide access to static files from disk, such as images, style sheets, scripts, etc. We can achieve this using the function
serveDirectory
:fileServing :: ServerPart Response
fileServing =
serveDirectory EnableBrowsing ["index.html"] "."
The first argument determines whether
serveDirectory
to create a list of files in the directory so that they can be viewed. The second argument is a list of index files. If the user asks to view the directory and it contains the index file, then instead of the list of files it will be displayed.
The third argument is the path to the directory to which access is granted. In this example, we provide access to the current directory.
On supported platforms (Linux, OS X, Windows), the function
serveDirectory
automatically uses sendfile()
to access files. The sendfile()
low-level kernel operations are used to ensure the transfer of files from the drive to the network with a minimum processor load and maximum use of the network channel.File Location
The processing of uploading files to the server is fairly straightforward. We create the form, as in the previous example, but
lookText
use it instead 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)
When a file is uploaded, we store it in a temporary location. The temporary file will be automatically deleted when the server sends a response to the browser. This ensures that unused files do not pollute disk space.
In most cases, the user does not want to download the file just to be deleted. Usually, the handler calls
moveFile
either copyFile
to move (or copy) the file to its permanent location.From translator
The author of the article assumes the presence of basic knowledge of the Haskell language. To install Happstack, use the instructions on the site.
If you are interested in this framework, I recommend that you familiarize yourself with its full version (a course on which I am also going to translate), as well as clckwrks based on it . Have a nice development!