@hackage hack2009.4.52

a sexy Haskell Webserver Interface

Hack: a sexy Haskell Webserver Interface

Hack is a brain-dead port of the brilliant Ruby Rack webserver interface.

Hack is made of 3 parts:

  • hack: the spec
  • hack-contrib: middleware
  • hack-handler: drivers

The spec should be as stable as possible, hack-contrib is about middleware and tools, and hack-handler provides server connectivity.

Hack explained in one line

type Application = Env -> IO Response

What does a Hack app look like

module Main where

import Hack
import Hack.Handler.Happstack

hello :: Application
hello = \env -> return $ Response 
    { status  = 200
    , headers = [ ("Content-Type", "text/plain") ]
    , body    = "Hello World"
    }

main = run hello

1 minute tutorial

update cabal

cabal update

install hack

cabal install happy
cabal install hack
cabal install hack-contrib

pick a backend

cabal install hack-handler-happstack

Create a Hack app

put the following code in src/Main.hs

module Main where

import Hack
import Hack.Handler.Happstack

hello :: Application
hello = \env -> return $ Response 
    { status  = 200
    , headers = [ ("Content-Type", "text/plain") ]
    , body    = "Hello World"
    }

main = run hello

run

ghc --make -O2 Main.hs
./Main

It should be running on [http://127.0.0.1:3000](http://127.0.0.1:3000) now.

Middleware

demo usage of middleware

module Main where

import Hack
import Hack.Handler.Happstack
import Hack.Contrib.Utils
import Hack.Contrib.Middleware.SimpleRouter

import Data.Default

hello :: Application
hello = \env -> return $ def {body = show env}

app :: Application
app = route [("/hello", hello), ("", hello)] empty_app

main = run app

create a middleware

inside Hack.hs:

type Middleware = Application -> Application

since Haskell has curry, middleware api can be of type

Anything -> Application -> Application

just pass an applied middleware into a chain.

finally the source code of SimpleRouter.hs:

module Hack.Contrib.Middleware.SimpleRouter where

import Hack
import Hack.Contrib.Utils

import MPSUTF8
import Prelude hiding ((.), (^), (>))
import List (find, isPrefixOf)

type RoutePath = (String, Application)

route :: [RoutePath] -> Middleware
route h app = \env ->
  let path             = env.path_info
      script           = env.script_name
      mod_env location = env 
        { script_name  = script ++ location
        , path_info    = path.drop (location.length)
        }
  in
  case h.find (fst > (`isPrefixOf` path) ) of
    Nothing -> app env
    Just (location, app) -> app (mod_env location)

Use the middleware stack

Rack provides a builder DSL, Hack just use a function. From Contrib.Utils.hs:

-- usage: app.use [content_type, cache]
use :: [Middleware] -> Middleware
use = reduce (<<<)

Handlers

Just like Rack, once an application is written using Hack, it should work on any web server that provides a Hack handler. I'm only familiar with Kibro, so it became the first handler that's included.

The handler should expose only one function of type:

run :: Application -> IO ()

Spec

Hack spec = Rack spec :)

Please read The Rack interface specification.

Tip

encoding can be tricky

  1. hack uses utf8 bytes for IO
  2. when dealing with html, sometimes, e.g. using template haskell, you have to
    1. encode unicode string using: escape_unicode_xml for plain text
    2. before rendering to response body, do unescape_unicode_xml
    3. then use u2b to convert unicode string to utf8 bytes, and push to response
  3. see, xml encoding and utf8 byte encoding are different, so we need to convert twice
  4. why bother unescape_unicode_xml at all? since we want the user to be able to see unicode chars when viewing source!
  5. these helpers are in hack-contrib and mps
  6. the default error stream in hack takes unicode string as input. It will be fixed in the next release.

Resources

Reference