Happstack Lite: Haskell Web Framework

Original author: Happstack.com
image
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:
  1. 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.
  2. They gave the functions much simpler signatures, eliminating monadic transformers and getting rid of most type classes.
  3. 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:
  1. import Happstack.Lite replaced by import Happstack.Server
  2. serve Nothing replaced by simpleHTTP nullConf
  3. add import Control.Monad (msum)
  4. 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 ServerPartweb 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.

dirIt 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 oksets the HTTP code “200 OK” for the page. There are other auxiliary functions, for example, notFoundsets 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 dironly matches the static part of the address. We can use the function pathto 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 / fantastic
echo :: 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 = bar
queryParams :: 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 lookTextwill return mzero. In this example, we use optionalfrom the module Control.Applicative, so that in the end we get the type value Maybe.

Forms


We can use lookTextto 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 lookTextas in the previous paragraph to get the data from the form. You may also have noticed that we use the function methodto distinguish GETand POSTrequests.
When the user views the form, the browser requests the page /formusing GET. In the HTML tag, formas 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:
  1. lookCookieValueIt works in exactly the same way as it does, lookTextwith the only difference being that it searches for value in cookies, not query parameters or form.
  2. addCookies sends cookies to the browser and has the following type: addCookies :: [(CookieLife, Cookie)] -> ServerPart ()
  3. CookieLifedetermines how long cookies exist and are considered correct. Sessionmeans the lifetime for cookies before closing the browser window.
  4. mkCookieaccepts the name of the cookie, its meaning, and creates Cookie.
  5. seeOther(i.e., 303, redirect) tells the browser to make a new GETrequest 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 serveDirectoryto 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 serveDirectoryautomatically 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 lookTextuse 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 moveFileeither copyFileto 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!

Also popular now: