diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,31 @@
+Copyright (c) 2011, Jinjing Wang
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Jinjing Wang nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Nemesis b/Nemesis
new file mode 100644
--- /dev/null
+++ b/Nemesis
@@ -0,0 +1,57 @@
+nemesis = do
+  
+  clean
+    [ "**/*.hi"
+    , "**/*.o"
+    , "manifest"
+    , "main"
+    , "nemesis-tmp.*"
+    , "Test"
+    ]
+  
+
+  desc "prepare cabal dist"
+  task "dist" - do
+    sh "cabal clean"
+    sh "cabal configure"
+    sh "cabal sdist"
+
+
+  desc "put all .hs files in manifest"
+  task "manifest" - do
+    sh "find . | grep 'hs-' > manifest"
+
+
+  desc "start console"
+  task "i" (sh "ghci -isrc test/hello.hs")
+  
+  desc "run main"
+  task "run" (sh "runghc -isrc src/Hack2/Handler/Mongrel2HTTP.hs")
+  
+
+  desc "test hello"
+  task "hello" - do
+    sh "runghc test/hello.hs"
+
+  desc "test ab"
+  task "ab" - do
+    sh "runghc -isrc test/ab.hs"
+
+  
+  desc "show sloc"
+  task "stat" - do
+    sh "cloc -match-f=hs- --quiet src"
+  
+  desc "load mongrel config from config/test.conf"
+  task "load" - do
+    sh "m2sh load -config config/test.conf"
+  
+  desc "list mongrel2 servers"
+  task "list" - do
+    sh "m2sh servers"
+  
+  desc "start mongrel2 server"
+  task "start" - do
+    sh "m2sh start -name test"
+    
+    
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,4 @@
+#! /usr/bin/env runhaskell
+
+> import Distribution.Simple
+> main = defaultMain
diff --git a/changelog.md b/changelog.md
new file mode 100644
--- /dev/null
+++ b/changelog.md
diff --git a/hack2-handler-mongrel2-http.cabal b/hack2-handler-mongrel2-http.cabal
new file mode 100644
--- /dev/null
+++ b/hack2-handler-mongrel2-http.cabal
@@ -0,0 +1,46 @@
+Name:                 hack2-handler-mongrel2-http
+Version:              2011.6.22
+Build-type:           Simple
+Synopsis:             Hack2 Mongrel2 HTTP handler
+Description:          Hack2 Mongrel2 HTTP handler
+License:              BSD3
+License-file:         LICENSE
+Author:               Jinjing Wang
+Maintainer:           Jinjing Wang <nfjinjing@gmail.com>
+Build-Depends:        base
+Cabal-version:        >= 1.2
+category:             Web
+homepage:             https://github.com/nfjinjing/hack2-handler-mongrel2-http
+data-files:           readme.md, changelog.md, Nemesis
+
+library
+
+  build-depends: 
+                      base >= 4.0 && < 5
+                    , network
+                    , bytestring
+                    , data-default >= 0.2
+                    , hack2 >= 2011.6.20
+                    , containers
+                    , mtl
+                    , enumerator < 5
+                    , blaze-builder < 0.4
+                    , case-insensitive < 0.3
+                    , air
+                    , zeromq-haskell
+                    , directory
+                    , safe
+                    , text
+                    , aeson
+                    , blaze-textual
+                    , unix
+                    , attoparsec
+  hs-source-dirs: src/
+  exposed-modules:  
+                    Hack2.Handler.Mongrel2HTTP
+  other-modules:
+                    Hack2.Handler.Mongrel2
+                    Hack2.Handler.Mongrel2.Parser
+                    Hack2.Handler.Mongrel2.Response
+                    Hack2.Handler.Mongrel2.Types
+                    
diff --git a/readme.md b/readme.md
new file mode 100644
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,11 @@
+Serving Hack2 application with the great Mongrel2 server
+---------------------------------------------------------
+
+* enumerator enabled, constant memory consumption when enumerator all the way down
+
+Know Issue
+----------
+
+* Server run loop is naive ><
+* default start 10 instances, which might not be the right way to run mongrel2 apps
+* Very early stage in development
diff --git a/src/Hack2/Handler/Mongrel2.hs b/src/Hack2/Handler/Mongrel2.hs
new file mode 100644
--- /dev/null
+++ b/src/Hack2/Handler/Mongrel2.hs
@@ -0,0 +1,115 @@
+-- adopted from Bardur Arantsson's MIT mongrel2-handler
+
+
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+
+
+module Hack2.Handler.Mongrel2
+       ( 
+         mkHandler
+       , withConnectedHandler
+       , receiveRequest
+       , sendResponse
+       -- Re-exported
+       , module Hack2.Handler.Mongrel2.Types
+       ) where
+
+import Blaze.ByteString.Builder (Builder,fromByteString)
+import Data.Attoparsec
+import Hack2.Handler.Mongrel2.Parser (messageParser)
+import Hack2.Handler.Mongrel2.Response
+import Hack2.Handler.Mongrel2.Types
+import qualified System.ZMQ as ZMQ
+import Data.ByteString.Char8 (ByteString, unpack)
+import Control.Monad (forM_)
+import qualified Data.ByteString.Char8 as B
+
+import Data.Enumerator
+import Air.Env
+import Prelude ()
+
+
+-- | Create a new handler. @mkHandler pullFromAddress publishToAddress id@
+-- creates a handler which pulls requests from @fromAddress@ and publishes
+-- replies to @publishAddress@.
+mkHandler :: String -> String -> Maybe String -> Handler
+mkHandler = Handler
+
+-- | Run an IO action with a connected handler.
+withConnectedHandler :: Handler -> (ConnectedHandler -> IO a) -> IO a
+withConnectedHandler h io =
+  ZMQ.withContext 1 - \c ->
+    ZMQ.withSocket c ZMQ.Pull - \s -> do
+      ZMQ.withSocket c ZMQ.Pub - \ps -> do
+        -- Connect to the poll socket.
+        ZMQ.connect s - handlerPullFrom h
+        -- Bind to publish socket.
+        ZMQ.connect ps - handlerPublishTo h
+        case handlerId h of
+          Nothing -> return ()
+          Just uuid -> ZMQ.setOption ps - ZMQ.Identity uuid
+        -- Run the action with the connected handler.
+        io - ConnectedHandler s ps
+
+-- | Receive a parsed request from the Mongrel2 server. Blocks until
+-- a message is received.
+receiveRequest :: ConnectedHandler -> IO Request
+receiveRequest handler = do
+      msg <- ZMQ.receive (chPullSocket handler) []
+      
+      -- putStrLn - "handler received: " + unpack msg
+      
+      -- Parse into a structured message.
+      case parseOnly messageParser msg of
+        Left errMsg ->
+          -- Invalid message. This can only happen if there's
+          -- an error in our parsing code or Mongrel2 itself.
+          fail - "Couldn't parse message: " + (show errMsg)
+        Right m -> do
+          -- Return the parsed message.
+          return m
+
+sendResponse :: ConnectedHandler -> Response -> IO ()
+sendResponse handler response = 
+  run_ - _responseEnum $$ _send_iteratee
+  
+  where
+      uuid             = responseUuid response
+      clientId         = responseClientId response
+      _responseEnum    = responseBody response
+      _mk_response msg = mkResponse uuid clientId msg
+      
+      _socket          = chPublishSocket handler
+
+      _send msg        = ZMQ.send _socket (_mk_response msg) [] -- [ZMQ.SndMore]
+      _done            = ZMQ.send _socket (_mk_response "") []
+  
+      -- head :: Monad m => Iteratee B.ByteString m (Maybe Word8)
+      -- head = continue loop where
+      --  loop (Chunks xs) = case BL.uncons (BL.fromChunks xs) of
+      --    Just (char, extra) -> yield (Just char) (toChunks extra)
+      --    Nothing -> head
+      --  loop EOF = yield Nothing EOF
+      --  
+      
+      _send_iteratee :: Iteratee ByteString IO ()
+      _send_iteratee = continue loop where
+        loop (Chunks xs) = do
+          io - xs.mapM_ (\msg -> do
+            -- puts - "send msg: " + msg.unpack
+            _send msg
+            )
+          _send_iteratee
+          
+        loop EOF = do
+          -- io - puts - "done"
+          io - _done
+          yield () EOF
+  
+  -- let combined = B.append _responseHeader _responseBody
+  -- putStrLn - "message: " + unpack combined
+  
+  -- ZMQ.send _socket combined []
+  
diff --git a/src/Hack2/Handler/Mongrel2/Parser.hs b/src/Hack2/Handler/Mongrel2/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Hack2/Handler/Mongrel2/Parser.hs
@@ -0,0 +1,91 @@
+module Hack2.Handler.Mongrel2.Parser
+       ( 
+         messageParser
+       ) where
+
+import Data.Aeson (Value(..), json)
+import Data.Attoparsec
+import Data.Attoparsec.Char8 (decimal)
+import Data.ByteString (ByteString)
+import Data.Text (Text)
+import Data.Text.Encoding (encodeUtf8)
+import Data.Word (Word8)
+import Prelude hiding (take)
+import qualified Data.Map as M
+import Hack2.Handler.Mongrel2.Types
+
+
+
+-- Predicates for parsing.
+isSpace :: Word8 -> Bool
+isSpace 0x20 = True
+isSpace _    = False
+
+isColon :: Word8 -> Bool
+isColon 0x3a = True
+isColon _    = False
+
+isComma :: Word8 -> Bool
+isComma 0x2c = True
+isComma _    = False
+
+-- Skip spaces.
+skipSpace :: Parser ()
+skipSpace = skipWhile isSpace
+
+-- Parse a UUID.
+uuidParser :: Parser ByteString
+uuidParser = takeWhile1 $ \w ->       ((w >= 65) && (w <= 90 ))   -- A-Z
+                                  ||  ((w >= 97) && (w <= 122))   -- a-z
+                                  ||  ((w >= 48) && (w <= 57 ))   -- 0-9
+                                  ||  (w == 45)                   -- Dash
+                                  -- ||  (w == 95)                   -- _
+                                  
+                                  
+
+netstringParser :: (Int -> Parser a) -> Parser a
+netstringParser contentParser = do
+  len <- decimal
+  skip isColon
+  str <- contentParser len
+  return str
+
+--   mongrel_send 2 / 238:{"PATH":"/","x-forwarded-for":"127.0.0.1","accept":"*/*","user-agent":"curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3","host":"127.0.0.1:6767","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/","PATTERN":"/"},0:,
+
+-- def parse(msg):
+--     sender, conn_id, path, rest = msg.split(' ', 3)
+--     headers, rest = tnetstrings.parse(rest)
+--     body, _ = tnetstrings.parse(rest)
+-- 
+--     if type(headers) is str:
+--         headers = json.loads(headers)
+-- 
+--     return Request(sender, conn_id, path, headers, body)
+    
+messageParser :: Parser Request
+messageParser = do
+  uuid <- uuidParser
+  skipSpace
+  clientId <- decimal
+  skipSpace
+  path <- takeTill isSpace
+  skipSpace
+  reqHdr <- netstringParser $ \_ -> json
+  skip isComma
+  reqBody <- netstringParser take
+  
+  return $ Request 
+    {
+      requestUuid = uuid
+    , requestClientId = clientId
+    , requestPath = path
+    , requestHeaders = reqHdr
+    , requestBody = reqBody
+    }
+
+-- extractQuery :: Value -> ([Text], Query)
+-- extractQuery (Object hdrs) =
+--   case M.lookup "URI" hdrs of
+--     Just (String uriText) -> decodePath $ encodeUtf8 uriText
+--     _ -> fail "Missing/invalid 'URI' in headers received from Mongrel2"
+-- extractQuery _ = fail "Invalid headers received from Mongrel2"
diff --git a/src/Hack2/Handler/Mongrel2/Response.hs b/src/Hack2/Handler/Mongrel2/Response.hs
new file mode 100644
--- /dev/null
+++ b/src/Hack2/Handler/Mongrel2/Response.hs
@@ -0,0 +1,50 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+module Hack2.Handler.Mongrel2.Response
+       ( Response
+       , mkResponse
+       ) where
+
+import Blaze.ByteString.Builder (Builder, toByteString, fromByteString)
+import Blaze.Text.Int (integral)
+import Data.ByteString (ByteString)
+import Data.ByteString.Char8 ()
+import Data.List (intersperse)
+import Data.Monoid
+import Hack2.Handler.Mongrel2.Types
+import qualified Data.ByteString as S
+
+
+-- | Build a single space.
+buildSpace :: Builder
+buildSpace = fromByteString " "
+
+-- | Build a UUID.
+buildUUID :: UUID -> Builder
+buildUUID = fromByteString
+
+
+  -- def send(self, uuid, conn_id, msg):
+  --     """
+  --     Raw send to the given connection ID at the given uuid, mostly used 
+  --     internally.
+  --     """
+  --     header = "%s %d:%s," % (uuid, len(str(conn_id)), str(conn_id))
+  --     self.resp.send(header + ' ' + msg)
+  
+  -- bcc1453e-9cc0-11e0-af11-6cf049b16ec3 1:0, HTTP/1.1 200 OK
+      
+-- | Build a response from a server UUID, a list of
+-- client IDs and a response body builder.
+mkResponse :: UUID -> ClientID -> ByteString -> ByteString
+mkResponse uuid clientID body =
+  toByteString $ mconcat [ buildUUID uuid
+                         , buildSpace
+                         , integral $ S.length $ toByteString $ integral clientID
+                         , fromByteString ":"
+                         , integral clientID
+                         , fromByteString ","
+                         , buildSpace
+                         , fromByteString body
+                         ]
+
diff --git a/src/Hack2/Handler/Mongrel2/Types.hs b/src/Hack2/Handler/Mongrel2/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Hack2/Handler/Mongrel2/Types.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE Rank2Types #-}
+
+module Hack2.Handler.Mongrel2.Types where
+
+import Data.ByteString (ByteString)
+import Data.Int (Int64)
+import Data.Aeson (Value(..))
+import Data.Enumerator (Enumerator, Iteratee (..), ($$), joinI, run_, Enumeratee, Step, (=$), ($=))
+
+import qualified System.ZMQ as ZMQ
+import Data.Default
+
+-- | Client identifier from Mongrel2. This identifies the currently
+-- connected client uniquely.
+type ClientID = Int64
+
+-- | UUID for communicating with Mongrel2.
+type UUID = ByteString
+
+data Request = Request
+  {
+    -- (UUID, ClientID, Path, RequestHeaders, RequestBody)
+    requestUuid :: UUID
+  , requestClientId :: ClientID
+  , requestPath :: ByteString
+  , requestHeaders :: Value
+  , requestBody :: ByteString
+  }
+  deriving (Show)
+
+data Response = Response
+  {
+    responseUuid :: UUID
+  , responseClientId :: ClientID
+  , responseBody :: (forall a. Enumerator ByteString IO a)
+  }
+
+
+data Handler = Handler 
+  { handlerPullFrom :: String
+  , handlerPublishTo :: String
+  , handlerId :: Maybe String
+  }
+
+instance Default Handler where
+  def = Handler def def def
+
+
+data ConnectedHandler = ConnectedHandler 
+  { chPullSocket :: ZMQ.Socket ZMQ.Pull
+  , chPublishSocket :: ZMQ.Socket ZMQ.Pub
+  }
diff --git a/src/Hack2/Handler/Mongrel2HTTP.hs b/src/Hack2/Handler/Mongrel2HTTP.hs
new file mode 100644
--- /dev/null
+++ b/src/Hack2/Handler/Mongrel2HTTP.hs
@@ -0,0 +1,306 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Rank2Types #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Hack2.Handler.Mongrel2HTTP 
+(
+
+  runWithConfig
+, ServerConfig(..)
+, Types.Handler(..)
+) where
+
+import Prelude ()
+import Air.Env hiding (def, Default, log)
+
+import Hack2
+import Data.Default (def, Default)
+import qualified Data.CaseInsensitive as CaseInsensitive
+import Data.ByteString.Char8 (ByteString, pack)
+import qualified Data.ByteString.Char8 as B
+import Data.Enumerator (Enumerator, Iteratee (..), ($$), joinI, run_, Enumeratee, Step, (=$), ($=), enumList, concatEnums)
+import qualified Data.Enumerator.List as EL
+import Blaze.ByteString.Builder (Builder, fromByteString, fromLazyByteString, toByteString)
+
+import Data.Maybe (listToMaybe, fromMaybe, isJust, fromJust)
+import Data.Map (toAscList, fromAscList)
+
+import Data.IORef (readIORef)
+
+import System.Directory (createDirectory, doesDirectoryExist)
+import Control.Monad (when, forever)
+
+import qualified Hack2.Handler.Mongrel2 as Mongrel2
+
+import qualified Hack2.Handler.Mongrel2.Types as Types
+
+import qualified Control.Exception as Exception
+import System.IO (hPutStr, stderr)
+
+import System.Posix.Signals
+import Control.Concurrent
+import Control.Concurrent.MVar
+import System.Exit (exitSuccess)
+
+import qualified Data.Map as Map
+import Blaze.Text.Int (integral)
+import Data.Monoid
+import qualified Data.Aeson as Aeson
+import Data.Text.Encoding (encodeUtf8)
+import Safe (readMay, readDef)
+
+import Control.Concurrent
+import System.IO.Unsafe (unsafePerformIO)
+
+    
+buildSpace :: Builder
+buildSpace = fromByteString " "
+
+buildNewLine :: Builder
+buildNewLine = fromByteString "\r\n"
+
+make_response_enumerator :: Response -> Enumerator ByteString IO a
+make_response_enumerator response = 
+  concatEnums 
+    [
+      header_enum
+    , response.body.unHackEnumerator
+    ]
+  where
+    header_enum = enumList 1 - return - make_response_header - response
+
+make_response_header :: Response -> ByteString
+make_response_header response = 
+  [
+    fromByteString "HTTP/1.1"
+  , buildSpace
+  , integral - response.status
+  , buildSpace
+  , fromByteString - (statusReasonMap.Map.lookup (response.status) .fromMaybe "Unknown Status Code")
+  
+  , buildNewLine
+  , mconcat - response.headers.map build_header
+  , buildNewLine
+  ]
+  
+  .mconcat
+  .toByteString
+  
+  where
+    build_header :: (ByteString, ByteString) -> Builder
+    build_header (header, value) = 
+      [
+        fromByteString header
+      , fromByteString ": "
+      , fromByteString value
+      , buildNewLine
+      ]
+      .mconcat
+
+-- example json request string
+-- {"PATH":"/","x-forwarded-for":"127.0.0.1","accept":"*/*","user-agent":"curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3","host":"127.0.0.1:6767","METHOD":"GET","VERSION":"HTTP/1.1","URI":"/","PATTERN":"/"}
+
+-- {  requestMethod  :: RequestMethod
+-- ,  scriptName     :: ByteString
+-- ,  pathInfo       :: ByteString
+-- ,  queryString    :: ByteString
+-- ,  serverName     :: ByteString
+-- ,  serverPort     :: Int
+-- ,  httpHeaders    :: [(ByteString, ByteString)]
+-- ,  hackVersion    :: (Int, Int, Int)
+-- ,  hackUrlScheme  :: HackUrlScheme
+-- ,  hackInput      :: HackEnumerator
+-- ,  hackErrors     :: HackErrors
+-- ,  hackHeaders    :: [(ByteString, ByteString)]
+
+
+requestToEnv :: Mongrel2.Request -> Env
+requestToEnv request = 
+  let
+    maybe_string (Aeson.String x) = Just - x
+    maybe_string _ = Nothing
+    
+    _headers = 
+      case request.Mongrel2.requestHeaders of
+        Aeson.Object headerMap -> 
+          headerMap
+          .Map.toAscList
+          .map_snd maybe_string
+          .select (snd > isJust)
+          .map (\(x,y) -> (x.encodeUtf8, fromJust y.encodeUtf8))
+        _ -> []
+    
+    _host = _headers.lookup "host" .fromMaybe ""
+    _query = _headers.lookup "QUERY" .fromMaybe ""
+    
+    _mongrel2_header_names = 
+      
+        [
+          "PATH"
+        , "host"
+        , "METHOD"
+        , "VERSION"
+        ]
+        
+    _http_headers = _headers
+    
+    _input_enum :: (forall a . Enumerator ByteString IO a)
+    _input_enum = enumList 1 [request.Mongrel2.requestBody]
+    
+  in
+    
+    def
+      {
+        requestMethod = ( _headers.lookup "METHOD" >>= B.unpack > readMay ).fromMaybe GET
+      , pathInfo = request.Mongrel2.requestPath
+      , queryString = _query
+      , serverName = _host.B.takeWhile (is_not ':')
+      , serverPort = _host.B.dropWhile (is_not ':') .B.dropWhile (is ':') .B.unpack.readDef 80
+      , httpHeaders = _http_headers
+      , hackInput = HackEnumerator _input_enum
+      }
+  
+
+
+
+-- copied from snap-core
+statusReasonMap :: Map.Map Int ByteString
+statusReasonMap = Map.fromAscList [
+        (100, "Continue"),
+        (101, "Switching Protocols"),
+        (200, "OK"),
+        (201, "Created"),
+        (202, "Accepted"),
+        (203, "Non-Authoritative Information"),
+        (204, "No Content"),
+        (205, "Reset Content"),
+        (206, "Partial Content"),
+        (300, "Multiple Choices"),
+        (301, "Moved Permanently"),
+        (302, "Found"),
+        (303, "See Other"),
+        (304, "Not Modified"),
+        (305, "Use Proxy"),
+        (307, "Temporary Redirect"),
+        (400, "Bad Request"),
+        (401, "Unauthorized"),
+        (402, "Payment Required"),
+        (403, "Forbidden"),
+        (404, "Not Found"),
+        (405, "Method Not Allowed"),
+        (406, "Not Acceptable"),
+        (407, "Proxy Authentication Required"),
+        (408, "Request Time-out"),
+        (409, "Conflict"),
+        (410, "Gone"),
+        (411, "Length Required"),
+        (412, "Precondition Failed"),
+        (413, "Request Entity Too Large"),
+        (414, "Request-URI Too Large"),
+        (415, "Unsupported Media Type"),
+        (416, "Requested range not satisfiable"),
+        (417, "Expectation Failed"),
+        (500, "Internal Server Error"),
+        (501, "Not Implemented"),
+        (502, "Bad Gateway"),
+        (503, "Service Unavailable"),
+        (504, "Gateway Time-out"),
+        (505, "HTTP Version not supported")
+    ]
+    
+    
+
+--  
+
+data ServerConfig = ServerConfig
+  {
+    handler :: Mongrel2.Handler
+  , number_of_application_instances :: Int
+  }
+
+instance Default ServerConfig where
+  def = ServerConfig
+    {
+      handler = def
+    , number_of_application_instances = 10
+    }
+
+
+exit_handler :: MVar () -> IO ()
+exit_handler exit_indicator = do
+  log "Exiting..."
+  putMVar exit_indicator ()
+
+sync_lock :: MVar ()
+sync_lock = unsafePerformIO - newMVar ()
+
+jailed :: IO a -> IO a
+jailed io = do
+  withMVar sync_lock (const io)
+
+-- log :: String -> IO ()
+-- log x = jailed (putStrLn x)
+
+log :: String -> IO ()
+log = const (return ())
+  
+
+runWithConfig :: ServerConfig -> Application -> IO ()
+runWithConfig config app = do
+    exit_indicator <- newEmptyMVar
+
+    installHandler sigINT (Catch (exit_handler exit_indicator)) Nothing
+
+    threads <- [1..config.number_of_application_instances] .mapM (\instance_id -> 
+        forkIO - server_safe_loop instance_id
+      )
+    
+    log "taking exit_indicator"
+    takeMVar exit_indicator
+    log "done taking exit_indicator"
+
+    
+    -- log "killing threads ..."
+    -- threads.mapM_ killThread
+    -- 
+    -- log "waiting threads termination ..."
+    -- sleep 2
+    -- 
+    -- log "all threads killed"
+    -- log "Mongrel2 main will exist"
+    
+    where
+      server_safe_loop instance_id = do
+        forever - do
+          Exception.catch (loop instance_id (config.handler) app) - \e ->
+            log - "Mongrel2 server error: " + (e :: Exception.SomeException).show + "\n"
+
+
+
+loop :: Int -> Mongrel2.Handler -> Application -> IO ()
+loop instance_id _handler app = do
+
+  Mongrel2.withConnectedHandler _handler - \connected_handler -> do
+    log - "instance " + show instance_id + " waiting ..."
+    request <- Mongrel2.receiveRequest connected_handler
+    log - "instance " + show instance_id + " handling ..."
+    -- putStrLn - "instance " + show instance_id + " handling ..."
+    
+
+    let uuid = request.Mongrel2.requestUuid
+        client_id = request.Mongrel2.requestClientId
+
+    app_response <- app - requestToEnv request
+  
+    let { resposne = 
+      Mongrel2.Response
+        {
+          Mongrel2.responseUuid = uuid
+        , Mongrel2.responseClientId = client_id
+        , Mongrel2.responseBody = make_response_enumerator app_response
+        }
+      }
+
+    Mongrel2.sendResponse connected_handler resposne
+
