news

[Published in Open Source For You (OSFY) magazine, June 2015 edition.]

In this final article in the Haskell series, we shall explore 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 x8664) 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 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 shown below:

$ 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 webpage 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 

[1 of 2] Compiling Haskell          ( Haskell.hs, Haskell.o )
[2 of 2] Compiling Main             ( template-file.hs, template-file.o )
Linking template-file ...

You can now run the server as follows:

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

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 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 a HTML page. The corresponding Clay Haskell embedded DSL looks like the following:

-- 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 
[1 of 1] Compiling Main             ( clay-pre.hs, clay-pre.o )
Linking clay-pre ...

Executing the above complied 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 homepage http://fvisser.nl/clay/.

A number of good books are available for further learning. I recommend the following books available online and in print:

  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.

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

I hope you enjoyed learning Haskell through this series, as much as I did creating them. Please feel free to write to me (author at shakthimaan dot com) with any feedback or suggestions.

Happy Hacking!