Scotty is a Web framework written in Haskell, which is similar to Rubys 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 whats 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 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.
Recommended books on Haskell
1. Bryan OSullivan, 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.