packages feed

hack-2009.4.52: readme.md

Hack: a sexy Haskell Webserver Interface
========================================


Hack is a brain-dead port of the brilliant Ruby [Rack](http://rack.rubyforge.org/) 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](http://rack.rubyforge.org/doc/files/SPEC.html).

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.

Links
-----

### Resources

* [hack-contrib](http://github.com/nfjinjing/hack-contrib)
* [hack-handler-kibro](http://github.com/nfjinjing/hack-handler-kibro)
* [hack-handler-hyena](http://github.com/nfjinjing/hack-handler-hyena)
* [hack-handler-happstack](http://github.com/nfjinjing/hack-handler-happstack)

### Reference

* [Rack](http://rack.rubyforge.org/ )
* [Rack wiki](http://wiki.github.com/rack/rack)
* [rack-contrib](http://github.com/rack/rack-contrib/tree/master)
* [Chris Done's Blog @Kibro](http://chrisdone.com/blog/tags/Kibro.html)
* [Hyena](http://github.com/tibbe/hyena/tree/master)
* [Happstack](http://happstack.com/)
* [Jinjing's Blog @Hack](http://jinjing.easymic.com/tag/Hack)
* [Bamboo blog engine](https://github.com/nfjinjing/bamboo/tree)