The Complete Magazine on Open Source

Web Programming in Haskell

web2

In this final article in the series on Haskell, the author explores how to use it for Web programming.

Scotty is a Web framework written in Haskell, which is similar to Ruby’s Sinatra. You can install it on Ubuntu using the following commands:

$ sudo apt-get install cabal-install
$ cabal update
$ cabal install scotty

Let us write a simple ‘Hello, World!’ program using the Scotty framework:

-- hello-world.hs

{-# LANGUAGE OverloadedStrings #-}

import Web.Scotty
main :: IO ()
main = scotty 3000 $ do
get “/” $ do
html “Hello, World!”

You can compile and start the server from the terminal using the following command:

$ runghc hello-world.hs
Setting phasers to stun... (port 3000) (ctrl-c to quit)

The service will run on port 3000, and you can open localhost:3000 in a browser to see the ‘Hello, World!’ text. You can then stop the service by pressing Control-C in the terminal. You can also use Curl to make a query to the server. Install and test it on Ubuntu as shown below:

$ sudo apt-get install curl
$ curl localhost:3000
Hello, World!

You can identify the user client that made the HTTP request to the server by returning the ‘User-Agent’ header value as illustrated in the following example:

-- request-header.hs

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty

main :: IO ()
main = scotty 3000 $ do
get “/agent” $ do
agent <- header “User-Agent”
maybe (raise “User-Agent header not found!”) text agent

You can execute the above code in a terminal using the following command:

$ runghc request-header.hs
Setting phasers to stun... (port 3000) (ctrl-c to quit)

If you open the URL localhost:3000/agent in the browser, it returns the following User-Agent information on Ubuntu 14.10: Mozilla/5.0 (X11: Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/41.0.2272.76 Chrome/41.0.2272.76 Safari/537.36. The Curl version is returned for the same URL request as shown below:

$ curl localhost:3000/agent -v

* Hostname was NOT found in DNS cache
* Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 3000 (#0)
> GET /agent HTTP/1.1
> User-Agent: curl/7.37.1
> Host: localhost:3000
> Accept: */*
>
< HTTP/1.1 200 OK
< Transfer-Encoding: chunked
< Date: Wed, 29 Apr 2015 07:46:21 GMT
* Server Warp/3.0.12.1 is not blacklisted
< Server: Warp/3.0.12.1
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact

curl/7.37.1

You can also return different content types (HTML, text, JSON) based on the request. For example:

-- content-type.hs

{-# LANGUAGE OverloadedStrings #-}

import Web.Scotty as W
import Data.Monoid
import Data.Text
import Data.Aeson

main :: IO ()
main = scotty 3000 $ do
get “/hello” $ do
html $ mconcat [“<h1>”, “Hello, World!”, “</h1>”]

get “/hello.txt” $ do
text “Hello, World!”

get “/hello.json” $ do
W.json $ object [“text” .= (“Hello, World!” :: Text)]

You can start the above server in a terminal as follows:

$ runghc content-type.hs
Setting phasers to stun... (port 3000) (ctrl-c to quit)

You can then open the three URLs listed above in a browser to see the different output. The respective outputs when used with Curl are shown below:

$ curl localhost:3000/hello
<h1>Hello, World!</h1>

$ curl localhost:3000/hello.txt
Hello, World!

$ curl localhost:3000/hello.json
{“text”:”Hello, World!”}

You can also pass parameters in the URL when you make a request. The param function can be used to retrieve the parameters as indicated below:

-- params.hs

{-# LANGUAGE OverloadedStrings #-}

import Web.Scotty
import Data.Monoid

main :: IO ()
main = scotty 3000 $ do
get “/user” $ do
name <- param “name”
html $ mconcat [“<h1>Hello “, name, “</h1>”]

You can start the above server using the runghc command:

$ runghc params.hs
Setting phasers to stun... (port 3000) (ctrl-c to quit)

You can now try the URL requests with and without parameters. The observed outputs are shown below:

$ curl localhost:3000/user
<h1>500 Internal Server Error</h1>Param: name not found!

$ curl localhost:3000/user?name=Shakthi
<h1>Hello Shakthi</h1>

The Hspec testing framework can be used for integration testing of the Web application. Install the required dependencies as shown below:

$ cabal install happy hspec hspec-wai hspec-wai-json

The content type example has been updated to use Hspec, as illustrated below:

-- content-type-spec.hs

{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
module Main (main) where

import Data.Monoid
import Data.Text

import Network.Wai (Application)
import qualified Web.Scotty as W
import Data.Aeson (object, (.=))

import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON

main :: IO ()
main = hspec spec

app :: IO Application
app = W.scottyApp $ do
W.get “/hello.txt” $ do
W.text “Hello, World!”

W.get “/hello” $ do
W.html $ mconcat [“<h1>”, “Hello, World!”, “</h1>”]

W.get “/hello.json” $ do
W.json $ object [“text” .= (“Hello, World!” :: Text)]

spec :: Spec
spec = with app $ do
describe “GET /” $ do
it “responds with text” $ do
get “/hello.txt” `shouldRespondWith` “Hello, World!”

it “responds with HTML” $ do
get “/hello” `shouldRespondWith` “<h1>Hello, World!</h1>”

it “responds with JSON” $ do
get “/hello.json” `shouldRespondWith` [json|{text: “Hello, World!”}|]

You can compile the above code as follows:

$ ghc --make content-type-spec.hs
Linking content-type-spec ...

The following output is observed when you run the above built test executable:

$ ./content-type-spec

GET /
responds with text
responds with HTML
responds with JSON

Finished in 0.0010 seconds
3 examples, 0 failures

Please refer to the hspec-wai Web page at https://github.com/hspec/hspec-wai for more information.

Template support is available through many Haskell packages. The use of the blaze-html package is demonstrated below. Install the package first using the following command:

$ cabal install blaze-html

Consider a simple Web page with a header and three unordered lists. Using blaze-html, the template can be written in Haskell DSL as follows:

-- template.hs

{-# LANGUAGE OverloadedStrings #-}

import Web.Scotty as W
import Text.Blaze.Html5
import Text.Blaze.Html.Renderer.Text

main :: IO ()
main = scotty 3000 $ do
get “/” $ do
W.html . renderHtml $ do
h1 “Haskell list”
ul $ do
li “http://haskell.org”
li “http://learnyouahaskell.com/”
li “http://book.realworldhaskell.org/”

You can compile the above code using GHC:

$ ghc --make template.hs
Linking template ...

You can then execute the built executable, which starts the server as shown below:

$ ./template
Setting phasers to stun... (port 3000) (ctrl-c to quit)

Opening a browser with URL localhost:3000 will render the expected HTML file. You can also verify the resultant HTML output using the Curl command as shown below:

$ curl localhost:3000
<h1>Haskell list</h1><ul><li>http://haskell.org</li><li>http://learnyouahaskell.com/</li><li>http://book.realworldhaskell.org/</li></ul>

It is good to separate the views from the actual application code. You can move the template content to a separate file as shown below:

-- Haskell.hs

{-# LANGUAGE OverloadedStrings #-}

module Haskell where

import Text.Blaze.Html5

render :: Html
render = do
html $ do
body $ do
h1 “Haskell list”
ul $ do
li “http://haskell.org”
li “http://learnyouahaskell.com/”
li “http://book.realworldhaskell.org/”

The main application code is now simplified as shown below:

-- template-file.hs

{-# LANGUAGE OverloadedStrings #-}

import qualified Haskell
import Web.Scotty as W
import Text.Blaze.Html
import Text.Blaze.Html.Renderer.Text

blaze :: Text.Blaze.Html.Html -> ActionM ()
blaze = W.html . renderHtml

main :: IO ()
main = scotty 3000 $ do
get “/” $ do
blaze Haskell.render

You need to place both the source files (Haskell.hs and template-file.hs) in the same top level directory, and you can then compile the template-file.hs file that will also compile the dependency Haskell.hs source file as shown below:

$ ghc --make template-file.hs

You can now run the server as follows:

$ ./template-file

Executing template-file produces the same output as in the case of the template.hs example.

$ curl localhost:3000
<html><body><h1>Haskell list</h1><ul><li>http://haskell.org</li><li>http://learnyouahaskell.com/</li><li>http://book.realworldhaskell.org/</li></ul></body></html>

You can refer to the Scotty wiki page at https://github.com/scotty-web/scotty/wiki for more information.

The clay package is a CSS preprocessor similar to LESS and Sass. You can install it using the following Cabal command:

$ cabal install clay

Let us consider a simple CSS example to generate a list of fonts to be used in the body section of an HTML page. The corresponding Clay Haskell embedded DSL looks like what’s shown below:

-- clay-simple.hs
{-# LANGUAGE OverloadedStrings #-}

import Clay

main :: IO ()
main = putCss exampleStylesheet

exampleStylesheet :: Css
exampleStylesheet = body ? fontFamily [“Baskerville”, “Georgia”, “Garamond”, “Times”] [serif]

You can compile the above code as follows:

$ ghc --make clay-simple.hs
[1 of 1] Compiling Main ( clay-simple.hs, clay-simple.o )
Linking clay-simple ...

You can then execute clay-simple to generate the required CSS output as shown below:

$ ./clay-simple

body
{
font-family : “Baskerville”,”Georgia”,”Garamond”,”Times”, serif;
}

/* Generated with Clay, http://fvisser.nl/clay */

A more comprehensive example is shown below for the HTML pre-tag:

-- clay-pre.hs

{-# LANGUAGE OverloadedStrings #-}

import Clay

main :: IO ()
main = putCss $
pre ?
do border dotted (pt 1) black
whiteSpace (other “pre”)
fontSize (other “8pt”)
overflow (other “auto”)
padding (em 20) (em 0) (em 20) (em 0)

You can compile the above clay-pre.hs file as shown below:

$ ghc --make clay-pre.hs

Executing the above compiled clay-pre binary produces the following output:

$ ./clay-pre

pre
{
border : dotted 1pt rgb(0,0,0);
white-space : pre;
font-size : 8pt;
overflow : auto;
padding : 20em 0em 20em 0em;
}

/* Generated with Clay, http://fvisser.nl/clay */

You can also add custom values using the Other type class or the fallback operator ‘-:’ to explicitly specify values. For example:

-- clay-custom.hs

{-# LANGUAGE OverloadedStrings #-}

import Clay

main :: IO ()
main = putCss $
body ?
do fontSize (other “11pt !important”)
“border” -: “0”

Compiling and executing the above code produces the following output:

$ ghc --make clay-custom.hs
[1 of 1] Compiling Main ( clay-custom.hs, clay-custom.o )
Linking clay-custom ...

$ ./clay-custom

body
{
font-size : 11pt !important;
border : 0;
}

/* Generated with Clay, http://fvisser.nl/clay */

You can explore more of Clay from the official project home page http://fvisser.nl/clay/.
A number of good books are available for further learning. Two books that I recommend, which are available online and in print, are listed at the end of the article.

The https://www.haskell.org website has plenty of useful resources. You can also join the [email protected] and [email protected] mailing lists (https://wiki.haskell.org/Mailing_lists ) for discussions. The folks in the #haskell channel on irc.freenode.net are also very helpful.

Recommended books on Haskell
1. Bryan O’Sullivan, Don Stewart, and John Goerzen (December 1, 2008): ‘Real World Haskell’ (http://book.realworldhaskell.org/). O’Reilly.
2. Miran Lipovaca (April 21, 2011): ‘Learn You a Haskell for Great Good! A Beginner’s Guide’ (http://learnyouahaskell.com/). No Starch Press.