📜 ⬆️ ⬇️

Happstack Lite: Haskell Web Framework

image
Picture to attract attention, clckwrks is a web framework closely related to Happstack.

Happstack is a great- powered 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 web framework Happstack Lite.


Foreword


Happstack Lite is a simple and easy-to-use version of Happstack. To create it, the developers:
  1. We collected all the main types and functions that you need to develop a web application in a single Happstack.Lite module, so you don’t need to search the modules for what you need.
  2. They gave functions much simpler signatures, eliminating monad transformers and getting rid of most types of types.
  3. We created this tutorial, which in less than 2000 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 app on Happstack Lite, and you need an advanced feature from Happstack, you can simply import the appropriate module and use it.
To translate a project from Happstack Lite to a regular one, you need to make only 4 small changes:
  1. import Happstack.Lite to replace import Happstack.Server
  2. serve Nothing to replace with simpleHTTP nullConf
  3. add import Control.Monad (msum)
  4. add explicit decodeBody call ( details )

')
While Happstack Lite is lightweight compared to regular Happtsack, it still is a full-featured framework along with other Haskele web frameworks.

In order to simplify the developers have abandoned the use of some of the advanced libraries that work with Happstack. If you are interested in a framework with type-safe URLs, type-safe forms, HTML syntax in literals and many others, then perhaps you should consider the Happstack Foundation. The learning curve is higher, but the additional reliability is worth it. Since these libraries are built on top of the Happstack kernel, the material studied in this tutorial will also be useful in their use.

For more in-depth information, you can read the Happstack Crash Course ( which I will also translate if interest is shown in this article - note. Lane. )

Server startup


First we need a couple of language extensions:
 {-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-} 

Now let's 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 start the application, we call the serve function. 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 the ServerPart Response type. You can consider ServerPart web equivalent of an IO monad.

( The default port is 8000, that is, you can see your application 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 the most general form, our application is just a few handlers assigned to static addresses.

dir used so that the handler is executed only when the static components of the path are successfully matched. For example, dir "echo" will successfully work with the address localhost:8000/echo localhost:8000/echo . To assign a handler to the address "/foo/bar" , simply write dir "foo" $ dir "bar" $ handler .

An attempt is made to consistently apply each handler, until one of them returns a result successfully. In this case, the Response .

We convert the list of handlers into one with the help of msum .

The last handler, homePage , is unlimited ( dir doesn't apply to it ), so it will always be called if none of the other handlers work successfully.

HTML templates


As we create a web application, we will need to create HTML pages. We can do this using Blaze, which also has a tutorial .

The subject of HTML templating causes widespread controversy in the community. No template system can satisfy everyone, so Happstack supports many different systems. Blaze is used in this tutorial because it is supported and based on purely functional combinators. If you like compile time patterns, but you want HTML syntax, you can consider HSP. If you are negative about patterns in your code and prefer external XML files, consider Heist.

It is 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 "!" Hp "   Happstack Lite   !" Hp "   :" Hp $ a ! href "/echo/secret%20message" $ "" Hp $ a ! href "/query?foo=bar" $ " " Hp $ a ! href "/form" $ " " Hp $ a ! href "/fortune" $ "- ()" Hp $ a ! href "/files" $ "  " Hp $ a ! href "/upload" $ " " 

The ok function sets the page’s HTTP code “200 OK”. There are other auxiliary functions, for example, notFound sets the code “404 Not Found”, seeOther - “303 See Other”. To set the HTTP code number, use setResponseCode .

Dynamic parts of the address


The dir function matches only the static part of the address. We can use the path function to extract a 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 the string parameters of the request. 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." 

In case the request parameter is not set, the lookText function will return mzero . In this example, we use optional from the Control.Applicative module, so we end up with a value of type Maybe .

Forms


We can use lookText to get 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 Hp " :" Hp (toHtml msg) 

We use the same lookText function as in the previous paragraph to get the data from the form. You may also have noticed that we use the method function to distinguish between GET and POST requests.
When a user views a form, the browser requests the page /form using GET . In the HTML form tag, we specified the opening of the same page as the button action, but using the attribute we chose the POST method.

Cookies! (HTTP cookies)


This example extends the form example by storing the message in a cookie. This means the user can leave the page, and when he comes back, 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 Hp "   - ():" Hp (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 didn't manage to save the word game between the HTTP cookie and the fortune cookie somehow - approx. Lane. )

Compared to the previous example, quite a bit of new has appeared:
  1. lookCookieValue works in the same way as lookText , with the only difference that it looks for the value in the cookies, not the query parameters or the form.
  2. addCookies sends cookies to the browser and has the following type: addCookies :: [(CookieLife, Cookie)] -> ServerPart ()
  3. CookieLife determines how long cookies exist and are considered valid. Session means the lifetime for cookies until the browser window is closed.
  4. mkCookie takes the name of the cookie, its value, and creates a Cookie .
  5. seeOther (i.e., 303, redirect) tells the browser to make a new GET request to the /fortune page.


File access


Most web applications need to provide access to static files from the disk, such as images, style sheets, scripts, etc. We can do this with the serveDirectory function:
 fileServing :: ServerPart Response fileServing = serveDirectory EnableBrowsing ["index.html"] "." 

The first argument specifies whether serveDirectory create a list of files in a directory so that they can be viewed.
The second argument is a list of index files. If the user requests to browse the directory and it contains an index file, then instead of a 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 serveDirectory function automatically uses sendfile() to access files. In sendfile() low-level kernel operations are used to ensure the transfer of files from the drive to the network with minimal processor overhead and maximum utilization of the network channel.

File allocation


Processing file uploads to the server is fairly straightforward. We create the form, as in the previous example, but instead of lookText use 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 the 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 for the sake of it being deleted. Usually in the handler, moveFile or copyFile to move (or copy) the file to its permanent location.

From translator


The author of the article assumes the existence of basic knowledge of the Haskel language. To install Happstack use the instructions on the site.

If you are interested in this framework, I recommend to get acquainted with its full version (the course on which I am also going to translate), as well as the clckwrks based on it. Pleasant development!

Source: https://habr.com/ru/post/185524/


All Articles