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)