diff --git a/salvia.cabal b/salvia.cabal
--- a/salvia.cabal
+++ b/salvia.cabal
@@ -1,5 +1,5 @@
 Name:             salvia
-Version:          0.0.5
+Version:          0.1
 Description:      Lightweight Haskell Web Server Framework
 Synopsis:         Lightweight Haskell Web Server Framework 
 Category:         Network, Web
@@ -11,19 +11,17 @@
 Build-Depends:    base,
                   bimap,
                   bytestring,
-                  clevercss,
                   containers,
                   directory,
-                  nano-md5,
+                  pureMD5 == 0.2.4,
+                  fclabels ==0.1,
                   encoding,
                   filepath,
-                  hscolour,
                   mtl ==1.1.0.2,
                   network,
                   old-locale,
-                  parsec,
+                  parsec ==3.0.0,
                   process,
-                  template-haskell >= 2.2,
                   random,
                   stm,
                   time,
@@ -33,43 +31,45 @@
 HS-Source-Dirs:   src
 Other-modules:    Misc.Misc,
                   Misc.Terminal,
-                  Data.Record.Label.TH
-Exposed-modules:  Data.Record.Label,
+                  Network.Protocol.Http.Data,
+                  Network.Protocol.Http.Parser,
+                  Network.Protocol.Http.Printer,
+                  Network.Protocol.Http.Status
+Exposed-modules:  Demo,
                   Network.Protocol.Cookie,
                   Network.Protocol.Http,
                   Network.Protocol.Mime,
                   Network.Protocol.Uri,
-                  Network.Salvia.Httpd,
                   Network.Salvia.Core.Config,
+                  Network.Salvia.Core.Context,
                   Network.Salvia.Core.Handler,
                   Network.Salvia.Core.IO,
                   Network.Salvia.Core.Main,
-                  Network.Salvia.Core.Network,
-                  Network.Salvia.Handlers.Banner,
-                  Network.Salvia.Handlers.CGI,
-                  Network.Salvia.Handlers.Cookie,
-                  Network.Salvia.Handlers.Counter,
-                  Network.Salvia.Handlers.Default,
-                  Network.Salvia.Handlers.Directory,
-                  Network.Salvia.Handlers.Dispatching,
-                  Network.Salvia.Handlers.Error,
-                  Network.Salvia.Handlers.ExtensionDispatcher,
-                  Network.Salvia.Handlers.Fallback,
-                  Network.Salvia.Handlers.File,
-                  Network.Salvia.Handlers.FileSystem,
-                  Network.Salvia.Handlers.Head,
-                  Network.Salvia.Handlers.Log,
-                  Network.Salvia.Handlers.Login,
-                  Network.Salvia.Handlers.MethodRouter,
-                  Network.Salvia.Handlers.Parser,
-                  Network.Salvia.Handlers.PathRouter,
-                  Network.Salvia.Handlers.Printer,
-                  Network.Salvia.Handlers.Put,
-                  Network.Salvia.Handlers.Redirect,
-                  Network.Salvia.Handlers.Rewrite,
-                  Network.Salvia.Handlers.Session,
-                  Network.Salvia.Handlers.VirtualHosting,
-                  Network.Salvia.Advanced.CleverCSS,
-                  Network.Salvia.Advanced.ExtendedFileSystem,
-                  Network.Salvia.Advanced.HsColour
+                  Network.Salvia.Handler.Banner,
+                  Network.Salvia.Handler.CGI,
+                  Network.Salvia.Handler.Close,
+                  Network.Salvia.Handler.Cookie,
+                  Network.Salvia.Handler.Counter,
+                  Network.Salvia.Handler.Directory,
+                  Network.Salvia.Handler.Dispatching,
+                  Network.Salvia.Handler.Environment,
+                  Network.Salvia.Handler.Error,
+                  Network.Salvia.Handler.ExtensionDispatcher,
+                  Network.Salvia.Handler.Fallback,
+                  Network.Salvia.Handler.File,
+                  Network.Salvia.Handler.FileSystem,
+                  Network.Salvia.Handler.Head,
+                  Network.Salvia.Handler.Log,
+                  Network.Salvia.Handler.Login,
+                  Network.Salvia.Handler.MethodRouter,
+                  Network.Salvia.Handler.Parser,
+                  Network.Salvia.Handler.PathRouter,
+                  Network.Salvia.Handler.Printer,
+                  Network.Salvia.Handler.Put,
+                  Network.Salvia.Handler.Redirect,
+                  Network.Salvia.Handler.Rewrite,
+                  Network.Salvia.Handler.Session,
+                  Network.Salvia.Handler.VirtualHosting,
+                  Network.Salvia.Handlers
+                  Network.Salvia.Httpd
 
diff --git a/src/Data/Record/Label.hs b/src/Data/Record/Label.hs
deleted file mode 100644
--- a/src/Data/Record/Label.hs
+++ /dev/null
@@ -1,86 +0,0 @@
-module Data.Record.Label (
-    Getter, Setter, Modifier
-  , Label (..)
-  , lmod
-  , (%), comp
-  , getM, setM, modM
-  , bothM
-  , enterM, enterMT
-  , withM, localM
-  , list
-  , module Data.Record.Label.TH
-  ) where
-
-import Control.Monad.State
-import Data.Record.Label.TH
-
-type Getter   a b = a -> b
-type Setter   a b = b -> a -> a
-type Modifier a b = (b -> b) -> a -> a
-
-data Label a b = Label {
-    lget :: Getter a b
-  , lset :: Setter a b
-  }
-
-lmod :: Label a b -> Modifier a b
-lmod l f a = lset l (f (lget l a)) a
-
-infixr 8 %
-
-(%) :: Label t a -> Label b t -> Label b a
-a % b = Label (lget a . lget b) (lmod b . lset a)
-
--- Apply custom `parser' and 'printer' function.
-
-comp :: (b -> c) -> (c -> b) -> Label t b -> Label t c
-comp f g (Label a b) = Label (f . a) (\v -> b $ g v)
-
--- Extend the state monad with support for labels.
-
-getM :: MonadState s m => Label s b -> m b
-getM = gets . lget
-
-setM :: MonadState s m => Label s b -> b -> m ()
-setM l = modify . lset l
-
-modM :: MonadState s m => Label s b -> (b -> b) -> m ()
-modM l = modify . lmod l
-
--- Run a state computation for a sub element updating this part of the state afterwards.
-
-enterM :: MonadState s m => Label s b -> State b a -> m a
-enterM l c = do
-  b <- getM l
-  let (a, s) = runState c b
-  setM l s
-  return a
-
-enterMT :: (MonadState s (t m), MonadTrans t, Monad m) => Label s b -> StateT b m a -> t m a
-enterMT l c = do
-  b <- getM l
-  (a, s) <- lift $ runStateT c b
-  setM l s
-  return a
-
-bothM :: MonadState s m => Label s b -> State b a -> m (b, a)
-bothM parent cmp = do
-  p <- getM parent
-  c <- enterM parent cmp
-  return (p, c)
-
-localM :: MonadState s m => Label s b -> m c -> m c
-localM l c = do
-  k <- getM l
-  c' <- c
-  setM l k
-  return c'
-
-withM :: MonadState s m => Label s b -> State b a -> m c -> m c
-withM l c d = localM l (enterM l c >> d)
-
--- Lift list indexing to a label.
-
-list :: Int -> Label [a] a
-list i = Label (!! i) (\v a -> take i a ++ [v] ++ drop (i+1) a)
-
diff --git a/src/Data/Record/Label/TH.hs b/src/Data/Record/Label/TH.hs
deleted file mode 100644
--- a/src/Data/Record/Label/TH.hs
+++ /dev/null
@@ -1,47 +0,0 @@
-module Data.Record.Label.TH (mkLabels) where
-
-import Control.Monad (liftM)
-import Data.Char (toLower, toUpper)
-import Language.Haskell.TH ( Body (NormalB)
-                           , Clause (Clause)
-                           , Con (RecC)
-                           , Dec (DataD, FunD)
-                           , Exp (AppE, ConE, LamE, RecUpdE, VarE)
-                           , Info (TyConI)
-                           , Name
-                           , Pat (VarP)
-                           , Q
-                           , mkName
-                           , nameBase
-                           , reify)
-import Language.Haskell.TH.Syntax (VarStrictType)
-
-mkLabels :: [Name] -> Q [Dec]
-mkLabels = liftM concat . mapM mkLabels1
-
-mkLabels1 :: Name -> Q [Dec]
-mkLabels1 n = do
-    i <- reify n
-    let cs' = case i of
-                 TyConI (DataD _ _ _ cs _) -> cs -- only process data declarations
-                 _ -> []
-        ls' = [ l | (RecC _ ls) <- cs', l <- ls ] -- we're only interested in labels of record constructors
-    return $ map mkLabel ls'
-
-mkLabel :: VarStrictType -> Dec
-mkLabel (name, _, _) =
-    -- Generate a name for the label:
-    -- * If the original selector starts with an _, remove it and make
-    --   the next character lowercase.
-    -- * Otherwise, add 'l', and make the next character uppercase.
-    let n = mkName $ case nameBase name of
-                ('_' : c : rest) -> toLower c : rest
-                (f : rest)   -> 'l' : toUpper f : rest
-                []           -> error "Data.Record.Label.TH: this should not happen."
-    in FunD n [Clause [] (NormalB (
-           AppE (AppE (ConE (mkName "Label"))
-                      (VarE name)) -- getter
-                (LamE [VarP (mkName "b"), VarP (mkName "a")] -- setter
-                      (RecUpdE (VarE (mkName "a")) [(name, VarE (mkName "b"))]))
-                                   )) []]
-
diff --git a/src/Demo.hs b/src/Demo.hs
new file mode 100644
--- /dev/null
+++ b/src/Demo.hs
@@ -0,0 +1,21 @@
+module Demo where
+
+import Network.Socket
+import Network.Salvia.Httpd
+import Network.Salvia.Handlers
+
+-- Serve the current directory.
+
+main :: IO ()
+main = do
+  conf <- defaultConfig
+  addr <- inet_addr "127.0.0.1"
+  start 
+    (conf { listenAddr = addr, listenPort = 8080 })
+    (hDefaultEnv myHandler)
+
+-- Serve the current directory.
+
+myHandler :: Handler ()
+myHandler = hFileSystem "."
+
diff --git a/src/Misc/Misc.hs b/src/Misc/Misc.hs
--- a/src/Misc/Misc.hs
+++ b/src/Misc/Misc.hs
@@ -121,13 +121,13 @@
 pMaybe = option Nothing . liftM Just
 
 -- Make parsec both applicative and alternative.
-instance Applicative (GenParser s a) where
+{-instance Applicative (GenParser s a) where
   pure  = return
   (<*>) = ap
 
 instance Alternative (GenParser s a) where
   empty = mzero
-  (<|>) = mplus
+  (<|>) = mplus-}
 
 -------[ time utils ]----------------------------------------------------------
 
diff --git a/src/Network/Protocol/Cookie.hs b/src/Network/Protocol/Cookie.hs
--- a/src/Network/Protocol/Cookie.hs
+++ b/src/Network/Protocol/Cookie.hs
@@ -1,3 +1,7 @@
+{- |
+For more information: http://www.ietf.org/rfc/rfc2109.txt
+-}
+
 module Network.Protocol.Cookie (
     Cookie (..)
   , empty
@@ -19,10 +23,11 @@
 import Text.ParserCombinators.Parsec hiding (many, optional, (<|>))
 import qualified Data.Map as M
 
--- For more information:
--- http://www.ietf.org/rfc/rfc2109.txt
 
--------[ HTTP cookie data type ]-----------------------------------------------
+{- |
+The `Cookie` data type containg one key/value pair with all the (potentially
+optional) meta-data.
+-}
 
 data Cookie =
   Cookie {
@@ -40,7 +45,8 @@
   , version    :: Int
   }
 
--- Create an empty cookie.
+{- | Create an empty cookie. -}
+
 empty :: Cookie
 empty = Cookie {
     name       = ""
@@ -57,16 +63,23 @@
   , version    = 0
   }
 
--- A set of multiple cookies. These can all be set in one single HTTP
--- Set-Cookie header field.
+{- |
+A collection of multiple cookies. These can all be set in one single HTTP
+/Set-Cookie/ header field.
+-}
 
 type Cookies = M.Map String Cookie
 
--- Convert a list of cookies into a cookie mapping. The name will be used as
--- the key, the cookie itself as the value.
+{- |
+Convert a list of cookies into a cookie mapping. The name will be used as the
+key, the cookie itself as the value.
+-}
+
 cookies :: [Cookie] -> Cookies
 cookies = M.fromList . map (\a -> (name a, a))
 
+{- | Case-insensitive way of getting a cookie out of a collection by name. -}
+
 cookie :: String -> Cookies -> Maybe Cookie
 cookie n = M.lookup (map toLower n)
 
@@ -104,7 +117,8 @@
     optval 0     = Nothing
     optval i     = Just (show i)
 
--- Show multiple cookies, pretty printed using a comma separator.
+{- | Show multiple cookies, pretty printed using a comma separator. -}
+
 showCookies :: Cookies -> String
 showCookies = ($"")
   . intersperseS (showString ", ")
@@ -113,16 +127,14 @@
 
 -------[ cookie parser ]-------------------------------------------------------
 
--- The top-level cookie parser. This parser will return just a mapping of
--- cookies or nothing on failure.
-parseCookies :: String -> Maybe Cookies
-parseCookies = fmap cookies . (pCookie @@)
-
-{-
+{- |
 Parse a set of cookie values and turn this into a collection of real cookies.
 As the specification states, only the name, value, domain, path and port will
 be recognized.
 -}
+
+parseCookies :: String -> Maybe Cookies
+parseCookies = fmap cookies . (pCookie @@)
 
 pCookie :: GenParser Char st [Cookie]
 pCookie = map ck <$> pCookieValues
diff --git a/src/Network/Protocol/Http.hs b/src/Network/Protocol/Http.hs
--- a/src/Network/Protocol/Http.hs
+++ b/src/Network/Protocol/Http.hs
@@ -1,377 +1,73 @@
-{-# LANGUAGE FlexibleInstances, TemplateHaskell #-}
-
 module Network.Protocol.Http(
 
-    Status (..)
-  , Method (..)
+  -- * HTTP message data types.
+
+    Method (..)
   , methods
-  , Version
+  , Version (Version)
+  , HeaderKey
+  , HeaderValue
   , Headers
-  , Direction
-  , Message
+  , Direction (Request, Response)
+  , Message (Message)
 
+  -- * Creating (parts of) messages.
+
+  , emptyRequest
+  , emptyResponse
+  , http10
+  , http11
+
+  -- * Accessing fields.
+
   , major
   , minor
+  , body
+  , headers
+  , version
+  , direction
   , method
   , uri
   , status
-  , direction
-  , version
-  , headers
-  , body
 
-  , utf8
-  , http10
-  , http11
-  , emptyRequest
-  , emptyResponse
+  -- * Accessing specific header fields.
 
   , normalizeHeader
   , header
+
+  , utf8
+
+  , connection
   , contentLength
-  , keepAlive
-  , cookie
-  , location
   , contentType
+  , cookie
   , date
-  , server
   , hostname
+  , keepAlive
+  , location
+  , server
 
-  , pRequest
-  , pResponse
+  -- * Parsing HTTP messages.
 
-  , showVersion
-  , showHeaders
+  , parseRequest
+  , parseResponse
+
+  -- * Printing HTTP messages.
+
   , showMessageHeader
 
+  -- * Handling HTTP status codes.
+
+  , Status (..)
   , statusCodes
   , statusFailure
   , statusFromCode
   , codeFromStatus
-  ) where
 
-import Control.Applicative hiding (empty)
-import Data.Char
-import Data.List (intercalate)
-import Data.Maybe (fromMaybe)
-import Data.Record.Label
-import Misc.Misc
-import Network.Protocol.Uri (URI, mkURI, pUriReference, parseURI)
-import Text.ParserCombinators.Parsec hiding (many, optional, (<|>))
-import qualified Data.Bimap as Bm
-import qualified Data.Map as M
-
--------- HTTP message data-types ----------------------------------------------
-
-data Status =
-    Continue                      | SwitchingProtocols
-  | OK                            | Created
-  | Accepted                      | NonAuthoritativeInformation
-  | NoContent                     | ResetContent
-  | PartialContent                | MultipleChoices
-  | MovedPermanently              | Found
-  | SeeOther                      | NotModified
-  | UseProxy                      | TemporaryRedirect
-  | BadRequest                    | Unauthorized
-  | PaymentRequired               | Forbidden
-  | NotFound                      | MethodNotAllowed
-  | NotAcceptable                 | ProxyAuthenticationRequired
-  | RequestTimeOut                | Conflict
-  | Gone                          | LengthRequired
-  | PreconditionFailed            | RequestEntityTooLarge
-  | RequestURITooLarge            | UnsupportedMediaType
-  | RequestedRangeNotSatisfiable  | ExpectationFailed
-  | InternalServerError           | NotImplemented
-  | BadGateway                    | ServiceUnavailable
-  | GatewayTimeOut                | HTTPVersionNotSupported
-  | CustomStatus Int
-  deriving (Eq, Ord)
-
-data Method =
-    OPTIONS
-  | GET
-  | HEAD
-  | POST
-  | PUT
-  | DELETE
-  | TRACE
-  | CONNECT
-  deriving (Show, Eq)
-
-methods :: [Method]
-methods = [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT]
-
-data Version = Version {_major :: Int, _minor :: Int}
-
-type HeaderKey   = String
-type HeaderValue = String
-type Headers     = M.Map HeaderKey HeaderValue
-
-data Direction =
-    Request  {__method :: Method, __uri :: URI}
-  | Response {__status :: Status}
-
-data Message =
-  Message {
-    _direction :: Direction
-  , _version   :: Version
-  , _headers   :: Headers
-  , _body      :: String
-  }
-
-$(mkLabels [''Version, ''Direction, ''Message])
-
-major     :: Label Version   Int
-minor     :: Label Version   Int
-body      :: Label Message   String
-headers   :: Label Message   Headers
-version   :: Label Message   Version
-direction :: Label Message   Direction
-_status   :: Label Direction Status
-_uri      :: Label Direction URI
-_method   :: Label Direction Method
-
--- Public labels based on private labels.
-
-method :: Label Message Method
-method = _method % direction
-
-uri :: Label Message URI
-uri = _uri % direction
-
-status :: Label Message Status
-status = _status % direction
-
--- More advanced labels.
-
-normalizeHeader :: String -> String
-normalizeHeader = (intercalate "-") . (map normalCase) . (Misc.Misc.split '-')
-
-header :: HeaderKey -> Label Message HeaderValue
-header key =
-  Label {
-    lget = maybe "" id . M.lookup (normalizeHeader key) . lget headers
-  , lset = lmod headers . M.insert (normalizeHeader key)
-  }
-
-contentLength :: Label Message (Maybe Integer)
-contentLength = comp safeRead (maybe "" show) (header "Content-Length")
-
-keepAlive :: Label Message (Maybe Integer)
-keepAlive = comp safeRead (maybe "" show) (header "Keep-Alive")
-
-cookie :: Label Message String
-cookie =
-  Label {
-    lget = lget (header "Cookie")
-  , lset = lset (header "Set-Cookie")
-  }
-
-location :: Label Message (Maybe URI)
-location =
-  Label {
-    lget = parseURI . lget (header "Location")
-  , lset = lset (header "Location") . maybe "" show
-  }
-
-contentType :: Label Message (String, Maybe String)
-contentType = comp pa pr (header "Content-Length")
-  where pr (t, c) = t ++ maybe "" ("; charset="++) c
-        pa = error "no getter for contentType yet"
-
-date :: Label Message String
-date = header "Date"
-
-hostname :: Label Message String
-hostname = header "Host"
-
-server :: Label Message String
-server = header "Server"
-
--- Create HTTP versions.
-http10, http11 :: Version
-http10 = Version 1 0
-http11 = Version 1 1
-
-emptyRequest :: Message
-emptyRequest = Message (Request GET (mkURI)) http11 M.empty ""
-
-emptyResponse :: Message
-emptyResponse = Message (Response OK) http11 M.empty ""
-
-utf8 :: String
-utf8 = "utf-8"
-
--------- HTTP message parsing -------------------------------------------------
-
--- todo: cleanup ugly code
-
-lf, ws, ls :: String
-lf = "\r\n"
-ws = " \t\r\n"
-ls = " \t"
-
-pLf :: GenParser Char st Char
-pLf = (char '\r' <* pMaybe (char '\n')) <|> char '\n'
-
-pVersion :: GenParser Char st Version
-pVersion = 
-      (\h l -> Version (ord h - ord '0') (ord l  - ord '0'))
-  <$> (string "HTTP/" *> digit)
-  <*> (char '.'       *> digit)
-
-pHeaders :: GenParser Char st Headers
-pHeaders = M.insert
-  <$> many1 (noneOf (':':ws)) <* string ":"
-  <*> (intercalate ws <$> (many $ many1 (oneOf ls) *> many1 (noneOf lf) <* pLf))
-  <*> option M.empty pHeaders
-
-pMethod :: GenParser Char st Method
-pMethod = choice $ map (\a -> a <$ (try $ string $ show a)) methods
-
-pRequest :: GenParser Char st Message
-pRequest = (\m u v h b -> Message (Request m u) v h b)
-  <$> (pMethod <* many1 (oneOf ls))
-  <*> (pUriReference <* many1 (oneOf ls))
-  <*> (pVersion <* pLf)
-  <*> (pHeaders <* pLf)
-  <*> (many anyToken)
-
-pResponse :: GenParser Char st Message
-pResponse = (\v s h b -> Message (Response (statusFromCode $ read s)) v h b)
-  <$> (pVersion <* many1 (oneOf ls))
-  <*> (many1 digit <* many1 (oneOf ls) <* many1 (noneOf lf) <* pLf)
-  <*> (pHeaders <* pLf)
-  <*> (many anyToken)
-
--------- HTTP message pretty printing -----------------------------------------
-
-showVersion :: Version -> String
-showVersion (Version a b) = concat ["HTTP/", show a, ".", show b]
-
-showHeaders :: Headers -> String
-showHeaders =
-    intercalate lf
-  . M.elems
-  . M.mapWithKey (\k a -> k ++ ": " ++ a)
-
-showMessageHeader :: Message -> String
-showMessageHeader (Message (Response s) v hs _) =
-  concat [
-    showVersion v, " "
-  , maybe "Unknown status" show
-  $ Bm.lookupR s statusCodes, " "
-  , show s, lf
-  , showHeaders hs, lf, lf
-  ]
-showMessageHeader (Message (Request m u) v hs _) =
-  concat [show m, " ", show u, " ", showVersion v, lf, showHeaders hs, lf, lf]
-
-instance Show Message where
-  show m = concat [showMessageHeader m, lget body m]
-
--------- status code mappings -------------------------------------------------
-
--- rfc2616 sec6.1.1 Status Code and Reason Phrase
-statusCodes :: Bm.Bimap Int Status
-statusCodes = Bm.fromList [
-    (100, Continue)
-  , (101, SwitchingProtocols)
-  , (200, OK)
-  , (201, Created)
-  , (202, Accepted)
-  , (203, NonAuthoritativeInformation)
-  , (204, NoContent)
-  , (205, ResetContent)
-  , (206, PartialContent)
-  , (300, MultipleChoices)
-  , (301, MovedPermanently)
-  , (302, Found)
-  , (303, SeeOther)
-  , (304, NotModified)
-  , (305, UseProxy)
-  , (307, TemporaryRedirect)
-  , (400, BadRequest)
-  , (401, Unauthorized)
-  , (402, PaymentRequired)
-  , (403, Forbidden)
-  , (404, NotFound)
-  , (405, MethodNotAllowed)
-  , (406, NotAcceptable)
-  , (407, ProxyAuthenticationRequired)
-  , (408, RequestTimeOut)
-  , (409, Conflict)
-  , (410, Gone)
-  , (411, LengthRequired)
-  , (412, PreconditionFailed)
-  , (413, RequestEntityTooLarge)
-  , (414, RequestURITooLarge)
-  , (415, UnsupportedMediaType)
-  , (416, RequestedRangeNotSatisfiable)
-  , (417, ExpectationFailed)
-  , (500, InternalServerError)
-  , (501, NotImplemented)
-  , (502, BadGateway)
-  , (503, ServiceUnavailable)
-  , (504, GatewayTimeOut)
-  , (505, HTTPVersionNotSupported)
-  ]
-
--- rfc2616 sec6.1.1 Status Code and Reason Phrase
-
-instance Show Status where
-  show Continue                     = "Continue"
-  show SwitchingProtocols           = "Switching Protocols"
-  show OK                           = "OK"
-  show Created                      = "Created"
-  show Accepted                     = "Accepted"
-  show NonAuthoritativeInformation  = "Non-Authoritative Information"
-  show NoContent                    = "No Content"
-  show ResetContent                 = "Reset Content"
-  show PartialContent               = "Partial Content"
-  show MultipleChoices              = "Multiple Choices"
-  show MovedPermanently             = "Moved Permanently"
-  show Found                        = "Found"
-  show SeeOther                     = "See Other"
-  show NotModified                  = "Not Modified"
-  show UseProxy                     = "Use Proxy"
-  show TemporaryRedirect            = "Temporary Redirect"
-  show BadRequest                   = "Bad Request"
-  show Unauthorized                 = "Unauthorized"
-  show PaymentRequired              = "Payment Required"
-  show Forbidden                    = "Forbidden"
-  show NotFound                     = "Not Found"
-  show MethodNotAllowed             = "Method Not Allowed"
-  show NotAcceptable                = "Not Acceptable"
-  show ProxyAuthenticationRequired  = "Proxy Authentication Required"
-  show RequestTimeOut               = "Request Time-out"
-  show Conflict                     = "Conflict"
-  show Gone                         = "Gone"
-  show LengthRequired               = "Length Required"
-  show PreconditionFailed           = "Precondition Failed"
-  show RequestEntityTooLarge        = "Request Entity Too Large"
-  show RequestURITooLarge           = "Request-URI Too Large"
-  show UnsupportedMediaType         = "Unsupported Media Type"
-  show RequestedRangeNotSatisfiable = "Requested range not satisfiable"
-  show ExpectationFailed            = "Expectation Failed"
-  show InternalServerError          = "Internal Server Error"
-  show NotImplemented               = "Not Implemented"
-  show BadGateway                   = "Bad Gateway"
-  show ServiceUnavailable           = "Service Unavailable"
-  show GatewayTimeOut               = "Gateway Time-out"
-  show HTTPVersionNotSupported      = "HTTP Version not supported"
-  show (CustomStatus _)             = "Unknown Status"
-
-statusFailure :: Status -> Bool
-statusFailure st = codeFromStatus st >= 400
-
-statusFromCode :: Int -> Status
-statusFromCode num =
-    fromMaybe (CustomStatus num)
-  $ Bm.lookup num statusCodes
+  ) where
 
-codeFromStatus :: Status -> Int
-codeFromStatus st =
-    fromMaybe 0 -- total, should not happen
-  $ Bm.lookupR st statusCodes
+import Network.Protocol.Http.Data
+import Network.Protocol.Http.Parser
+import Network.Protocol.Http.Printer
+import Network.Protocol.Http.Status
 
diff --git a/src/Network/Protocol/Http/Data.hs b/src/Network/Protocol/Http/Data.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Protocol/Http/Data.hs
@@ -0,0 +1,195 @@
+{-# LANGUAGE TemplateHaskell #-}
+module Network.Protocol.Http.Data where
+
+import Data.List (intercalate)
+import Data.Map (lookup, insert, Map, empty)
+import Data.Record.Label
+import Misc.Misc (safeRead, normalCase, split)
+import Network.Protocol.Http.Status (Status (..))
+import Network.Protocol.Uri
+import Prelude hiding (lookup)
+
+{- | List of HTTP request methods. -}
+
+data Method =
+    OPTIONS
+  | GET
+  | HEAD
+  | POST
+  | PUT
+  | DELETE
+  | TRACE
+  | CONNECT
+  deriving (Show, Eq)
+
+{- | All `Method` constructors as a list. -}
+
+methods :: [Method]
+methods = [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT]
+
+{- | HTTP protocol version. -}
+
+data Version = Version {_major :: Int, _minor :: Int}
+
+{- | Create HTTP 1.0 version. -}
+
+http10 :: Version
+http10 = Version 1 0
+
+{- | Create HTTP 1.1 version. -}
+
+http11 :: Version
+http11 = Version 1 1
+
+type HeaderKey   = String
+type HeaderValue = String
+
+{- | HTTP headers as mapping from keys to values. -}
+
+type Headers     = Map HeaderKey HeaderValue
+
+{- | Request or response specific part of HTTP messages. -}
+
+data Direction =
+    Request  {__method :: Method, __uri :: URI}
+  | Response {__status :: Status}
+
+{- | An HTTP message. -}
+
+data Message =
+  Message {
+    _direction :: Direction
+  , _version   :: Version
+  , _headers   :: Headers
+  , _body      :: String
+  }
+
+{- | Create an empty HTTP request object. -}
+
+emptyRequest :: Message
+emptyRequest = Message (Request GET (mkURI)) http11 empty ""
+
+{- | Create an empty HTTP response object. -}
+
+emptyResponse :: Message
+emptyResponse = Message (Response OK) http11 empty ""
+
+$(mkLabels [''Version, ''Direction, ''Message])
+
+{- | Label to access the major part of the version. -}
+
+major :: Label Version Int
+
+{- | Label to access the minor part of the version. -}
+
+minor :: Label Version Int
+
+_status   :: Label Direction Status
+_uri      :: Label Direction URI
+_method   :: Label Direction Method
+
+{- | Label to access the body part of an HTTP message. -}
+
+body :: Label Message String
+
+{- | Label to access the header of an HTTP message. -}
+
+headers :: Label Message Headers
+
+{- | Label to access the version part of an HTTP message. -}
+
+version :: Label Message Version
+
+{- | Label to access the direction part of an HTTP message. -}
+
+direction :: Label Message Direction
+
+{- | Label to access the method part of an HTTP message. -}
+
+method :: Label Message Method
+method = _method % direction
+
+{- | Label to access the URI part of an HTTP message. -}
+
+uri :: Label Message URI
+uri = _uri % direction
+
+{- | Label to access the status part of an HTTP message. -}
+
+status :: Label Message Status
+status = _status % direction
+
+
+
+
+
+{- | Normalize the capitalization of an HTTP header key. -}
+
+normalizeHeader :: String -> String
+normalizeHeader = (intercalate "-") . (map normalCase) . (split '-')
+
+{- | Generic label to access an HTTP header field by key. -}
+
+header :: HeaderKey -> Label Message HeaderValue
+header key =
+  Label {
+    lget = maybe "" id . lookup (normalizeHeader key) . lget headers
+  , lset = lmod headers . insert (normalizeHeader key)
+  }
+
+{- | Simply /utf-8/. -}
+
+utf8 :: String
+utf8 = "utf-8"
+
+{- | Access the /Content-Length/ header field. -}
+
+contentLength :: (Read i, Integral i) => Label Message (Maybe i)
+contentLength = comp safeRead (maybe "" show) (header "Content-Length")
+
+{- | Access the /Connection/ header field. -}
+
+connection :: Label Message String
+connection = header "Connection"
+
+{- | Access the /Keep-Alive/ header field. -}
+
+keepAlive :: (Read i, Integral i) => Label Message (Maybe i)
+keepAlive = comp safeRead (maybe "" show) (header "Keep-Alive")
+
+{- | Access the /Cookie/ and /Set-Cookie/ header fields. -}
+
+cookie :: Label Message String
+cookie = Label (lget $ header "Cookie") (lset $ header "Set-Cookie")
+
+{- | Access the /Location/ header field. -}
+
+location :: Label Message (Maybe URI)
+location = Label
+  (parseURI . lget (header "Location"))
+  (lset (header "Location") . maybe "" show)
+
+{- | Access the /Content-Type/ header field. -}
+
+contentType :: Label Message (String, Maybe String)
+contentType = comp pa pr (header "Content-Type")
+  where pr (t, c) = t ++ maybe "" ("; charset="++) c
+        pa = error "no getter for contentType yet"
+
+{- | Access the /Data/ header field. -}
+
+date :: Label Message String
+date = header "Date"
+
+{- | Access the /Host/ header field. -}
+
+hostname :: Label Message (Maybe Authority)
+hostname = Label
+  (parseAuthority . lget (header "Host"))
+  (lset (header "Host") . maybe "" show)
+
+{- | Access the /Server/ header field. -}
+
+server :: Label Message String
+server = header "Server"
+
diff --git a/src/Network/Protocol/Http/Parser.hs b/src/Network/Protocol/Http/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Protocol/Http/Parser.hs
@@ -0,0 +1,63 @@
+module Network.Protocol.Http.Parser (
+    parseRequest
+  , parseResponse
+  ) where
+
+import Control.Applicative hiding (empty)
+import Data.Char (ord)
+import Data.List (intercalate)
+import Data.Map (insert, empty)
+import Misc.Misc (pMaybe)
+import Network.Protocol.Http.Data
+import Network.Protocol.Http.Status
+import Network.Protocol.Uri (pUriReference)
+import Text.ParserCombinators.Parsec hiding (many, optional, (<|>))
+
+{- | Parse a string as an HTTP request message. -}
+
+parseRequest :: String -> Either ParseError Message
+parseRequest msg = parse pRequest  "" msg
+
+{- | Parse a string as an HTTP request message. -}
+
+parseResponse :: String -> Either ParseError Message
+parseResponse msg = parse pResponse "" msg
+
+lf, ws, ls :: String
+lf = "\r\n"
+ws = " \t\r\n"
+ls = " \t"
+
+pLf :: GenParser Char st Char
+pLf = (char '\r' <* pMaybe (char '\n')) <|> char '\n'
+
+pVersion :: GenParser Char st Version
+pVersion = 
+      (\h l -> Version (ord h - ord '0') (ord l  - ord '0'))
+  <$> (string "HTTP/" *> digit)
+  <*> (char '.'       *> digit)
+
+pHeaders :: GenParser Char st Headers
+pHeaders = insert
+  <$> many1 (noneOf (':':ws)) <* string ":"
+  <*> (intercalate ws <$> (many $ many1 (oneOf ls) *> many1 (noneOf lf) <* pLf))
+  <*> option empty pHeaders
+
+pMethod :: GenParser Char st Method
+pMethod = choice $ map (\a -> a <$ (try $ string $ show a)) methods
+
+pRequest :: GenParser Char st Message
+pRequest = (\m u v h b -> Message (Request m u) v h b)
+  <$> (pMethod <* many1 (oneOf ls))
+  <*> (pUriReference <* many1 (oneOf ls))
+  <*> (pVersion <* pLf)
+  <*> (pHeaders <* pLf)
+  <*> (many anyToken)
+
+pResponse :: GenParser Char st Message
+pResponse = (\v s h b -> Message (Response (statusFromCode $ read s)) v h b)
+  <$> (pVersion <* many1 (oneOf ls))
+  <*> (many1 digit <* many1 (oneOf ls) <* many1 (noneOf lf) <* pLf)
+  <*> (pHeaders <* pLf)
+  <*> (many anyToken)
+
diff --git a/src/Network/Protocol/Http/Printer.hs b/src/Network/Protocol/Http/Printer.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Protocol/Http/Printer.hs
@@ -0,0 +1,41 @@
+{- | Provides the `Show` instance for an HTTP `Message`. -}
+
+module Network.Protocol.Http.Printer (showMessageHeader) where
+
+import Data.Bimap (lookupR)
+import Data.List (intercalate)
+import Data.Map (elems, mapWithKey)
+import Data.Record.Label (lget)
+import Network.Protocol.Http.Data
+import Network.Protocol.Http.Status
+
+lf :: String
+lf = "\r\n"
+
+instance Show Version where
+  show (Version a b) = concat ["HTTP/", show a, ".", show b]
+
+showHeaders :: Headers -> String
+showHeaders =
+    intercalate lf
+  . elems
+  . mapWithKey (\k a -> k ++ ": " ++ a)
+
+{- | Helper function that only prints the header part of an HTTP message. -}
+
+showMessageHeader :: Message -> String
+showMessageHeader (Message (Response s) v hs _) =
+  concat [
+    show v, " "
+  , maybe "Unknown status" show
+  $ lookupR s statusCodes, " "
+  , show s, lf
+  , showHeaders hs, lf, lf
+  ]
+
+showMessageHeader (Message (Request m u) v hs _) =
+  concat [show m, " ", show u, " ", show v, lf, showHeaders hs, lf, lf]
+
+instance Show Message where
+  show m = concat [showMessageHeader m, lget body m]
+
diff --git a/src/Network/Protocol/Http/Status.hs b/src/Network/Protocol/Http/Status.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Protocol/Http/Status.hs
@@ -0,0 +1,163 @@
+module Network.Protocol.Http.Status where
+
+import Data.Bimap (Bimap, fromList, lookup, lookupR)
+import Data.Maybe (fromMaybe)
+import Prelude hiding (lookup)
+
+{- | HTTP status codes. -}
+
+data Status =
+    Continue                     -- ^ 100
+  | SwitchingProtocols           -- ^ 101
+  | OK                           -- ^ 200
+  | Created                      -- ^ 201
+  | Accepted                     -- ^ 202
+  | NonAuthoritativeInformation  -- ^ 203
+  | NoContent                    -- ^ 204
+  | ResetContent                 -- ^ 205
+  | PartialContent               -- ^ 206
+  | MultipleChoices              -- ^ 300
+  | MovedPermanently             -- ^ 301
+  | Found                        -- ^ 302
+  | SeeOther                     -- ^ 303
+  | NotModified                  -- ^ 304
+  | UseProxy                     -- ^ 305
+  | TemporaryRedirect            -- ^ 307
+  | BadRequest                   -- ^ 400
+  | Unauthorized                 -- ^ 401
+  | PaymentRequired              -- ^ 402
+  | Forbidden                    -- ^ 403
+  | NotFound                     -- ^ 404
+  | MethodNotAllowed             -- ^ 405
+  | NotAcceptable                -- ^ 406
+  | ProxyAuthenticationRequired  -- ^ 407
+  | RequestTimeOut               -- ^ 408
+  | Conflict                     -- ^ 409
+  | Gone                         -- ^ 410
+  | LengthRequired               -- ^ 411
+  | PreconditionFailed           -- ^ 412
+  | RequestEntityTooLarge        -- ^ 413
+  | RequestURITooLarge           -- ^ 414
+  | UnsupportedMediaType         -- ^ 415
+  | RequestedRangeNotSatisfiable -- ^ 416
+  | ExpectationFailed            -- ^ 417
+  | InternalServerError          -- ^ 500
+  | NotImplemented               -- ^ 501
+  | BadGateway                   -- ^ 502
+  | ServiceUnavailable           -- ^ 503
+  | GatewayTimeOut               -- ^ 504
+  | HTTPVersionNotSupported      -- ^ 505
+  | CustomStatus Int
+  deriving (Eq, Ord)
+
+{- | rfc2616 sec6.1.1 Status Code and Reason Phrase. -}
+
+instance Show Status where
+  show Continue                     = "Continue"
+  show SwitchingProtocols           = "Switching Protocols"
+  show OK                           = "OK"
+  show Created                      = "Created"
+  show Accepted                     = "Accepted"
+  show NonAuthoritativeInformation  = "Non-Authoritative Information"
+  show NoContent                    = "No Content"
+  show ResetContent                 = "Reset Content"
+  show PartialContent               = "Partial Content"
+  show MultipleChoices              = "Multiple Choices"
+  show MovedPermanently             = "Moved Permanently"
+  show Found                        = "Found"
+  show SeeOther                     = "See Other"
+  show NotModified                  = "Not Modified"
+  show UseProxy                     = "Use Proxy"
+  show TemporaryRedirect            = "Temporary Redirect"
+  show BadRequest                   = "Bad Request"
+  show Unauthorized                 = "Unauthorized"
+  show PaymentRequired              = "Payment Required"
+  show Forbidden                    = "Forbidden"
+  show NotFound                     = "Not Found"
+  show MethodNotAllowed             = "Method Not Allowed"
+  show NotAcceptable                = "Not Acceptable"
+  show ProxyAuthenticationRequired  = "Proxy Authentication Required"
+  show RequestTimeOut               = "Request Time-out"
+  show Conflict                     = "Conflict"
+  show Gone                         = "Gone"
+  show LengthRequired               = "Length Required"
+  show PreconditionFailed           = "Precondition Failed"
+  show RequestEntityTooLarge        = "Request Entity Too Large"
+  show RequestURITooLarge           = "Request-URI Too Large"
+  show UnsupportedMediaType         = "Unsupported Media Type"
+  show RequestedRangeNotSatisfiable = "Requested range not satisfiable"
+  show ExpectationFailed            = "Expectation Failed"
+  show InternalServerError          = "Internal Server Error"
+  show NotImplemented               = "Not Implemented"
+  show BadGateway                   = "Bad Gateway"
+  show ServiceUnavailable           = "Service Unavailable"
+  show GatewayTimeOut               = "Gateway Time-out"
+  show HTTPVersionNotSupported      = "HTTP Version not supported"
+  show (CustomStatus _)             = "Unknown Status"
+
+{- |
+RFC2616 sec6.1.1 Status Code and Reason Phrase.
+
+Bidirectional mapping from status numbers to codes.
+-}
+
+statusCodes :: Bimap Int Status
+statusCodes = fromList [
+    (100, Continue)
+  , (101, SwitchingProtocols)
+  , (200, OK)
+  , (201, Created)
+  , (202, Accepted)
+  , (203, NonAuthoritativeInformation)
+  , (204, NoContent)
+  , (205, ResetContent)
+  , (206, PartialContent)
+  , (300, MultipleChoices)
+  , (301, MovedPermanently)
+  , (302, Found)
+  , (303, SeeOther)
+  , (304, NotModified)
+  , (305, UseProxy)
+  , (307, TemporaryRedirect)
+  , (400, BadRequest)
+  , (401, Unauthorized)
+  , (402, PaymentRequired)
+  , (403, Forbidden)
+  , (404, NotFound)
+  , (405, MethodNotAllowed)
+  , (406, NotAcceptable)
+  , (407, ProxyAuthenticationRequired)
+  , (408, RequestTimeOut)
+  , (409, Conflict)
+  , (410, Gone)
+  , (411, LengthRequired)
+  , (412, PreconditionFailed)
+  , (413, RequestEntityTooLarge)
+  , (414, RequestURITooLarge)
+  , (415, UnsupportedMediaType)
+  , (416, RequestedRangeNotSatisfiable)
+  , (417, ExpectationFailed)
+  , (500, InternalServerError)
+  , (501, NotImplemented)
+  , (502, BadGateway)
+  , (503, ServiceUnavailable)
+  , (504, GatewayTimeOut)
+  , (505, HTTPVersionNotSupported)
+  ]
+
+-- | Every status greater-than or equal to 400 is considered to be a failure.
+statusFailure :: Status -> Bool
+statusFailure st = codeFromStatus st >= 400
+
+-- | Conversion from status numbers to codes.
+statusFromCode :: Int -> Status
+statusFromCode num =
+    fromMaybe (CustomStatus num)
+  $ lookup num statusCodes
+
+-- | Conversion from status codes to numbers.
+codeFromStatus :: Status -> Int
+codeFromStatus st =
+    fromMaybe 0 -- function is total, should not happen.
+  $ lookupR st statusCodes
+
diff --git a/src/Network/Protocol/Mime.hs b/src/Network/Protocol/Mime.hs
--- a/src/Network/Protocol/Mime.hs
+++ b/src/Network/Protocol/Mime.hs
@@ -1,14 +1,23 @@
+{- |
+Handling mime types. This module contains a mapping from file extensions to
+mime-types taken from the Apache webserver project.
+-}
+
 module Network.Protocol.Mime where
 
 import Data.Map
 
--------- file extension to mime type mapping ----------------------------------
+{- | Get the mimetype for the specified extension. -}
 
 mime :: String -> Maybe String
 mime ext = Data.Map.lookup ext extensionToMime
 
+{- | The default mimetype is /text/plain/. -}
+
 defaultMime :: String
 defaultMime = "text/plain"
+
+{- | The mapping from extension to mimetype. -}
 
 extensionToMime :: Map String String
 extensionToMime = fromList [
diff --git a/src/Network/Protocol/Uri.hs b/src/Network/Protocol/Uri.hs
--- a/src/Network/Protocol/Uri.hs
+++ b/src/Network/Protocol/Uri.hs
@@ -1,9 +1,9 @@
-{-# LANGUAGE TemplateHaskell #-}
+{- | See rfc2396 for more info. -}
 
+{-# LANGUAGE TemplateHaskell #-}
 module Network.Protocol.Uri (
 
-    URI
-  , Scheme
+    Scheme
   , IPv4
   , Domain
   , RegName
@@ -17,7 +17,10 @@
   , Path
   , Host
   , Authority
+  , URI
 
+  -- * Creating (parts of) URIs.
+
   , encode
   , decode
 
@@ -31,8 +34,8 @@
   , mkHost
   , mkPort
 
-  , absolute
-  , segments
+  -- * Accessing parts of URIs.
+
   , domain
   , regname
   , ipv4
@@ -46,6 +49,15 @@
   , query
   , fragment
 
+  -- * Helper labels.
+
+  , absolute
+  , segments
+  , _host
+  , _port
+
+  -- * Parsing URIs.
+
   , parseURI
   , parseAbsoluteURI
   , parseAuthority
@@ -54,13 +66,17 @@
 
   , pUriReference
   , pAbsoluteURI
-  , pAuthority
-  , pPath
-  , pHost
+--   , pAuthority
+--   , pPath
+--   , pHost
 
+  -- * Handling query parameters.
+
   , parseQueryParams
   , queryParams
 
+  -- * Filename related utilities.
+
   , extension
 
   , mkPathRelative
@@ -73,7 +89,7 @@
 
 import Control.Applicative
 import Data.Bits
-import Data.Char (ord, chr, isDigit, isAlphaNum, intToDigit, isHexDigit)
+import Data.Char (ord, chr, isDigit, isAlphaNum, intToDigit, isHexDigit, toLower)
 import Data.List (intercalate, isPrefixOf)
 import Data.Maybe (mapMaybe, fromJust)
 import Data.Record.Label
@@ -100,20 +116,20 @@
     _absolute :: Bool
   , _segments :: [PathSegment]
   }
-  deriving Eq
+  deriving (Eq, Ord)
 
 data Host =
     Hostname { __domain  :: Domain }
   | RegName  { __regname :: String }
   | IPv4     { __ipv4    :: IPv4   }
-  deriving Eq
+  deriving (Eq, Ord)
 
 data Authority = Authority {
     __userinfo :: UserInfo
   , __host     :: Host
   , __port     :: Port
   }
-  deriving Eq
+  deriving (Eq, Ord)
 
 data URI = URI {
     _relative  :: Bool
@@ -123,7 +139,7 @@
   , __query     :: Query
   , _fragment  :: Fragment
   }
-  deriving Eq
+  deriving (Eq, Ord)
 
 $(mkLabels [''Path, ''Host, ''Authority, ''URI])
 
@@ -158,63 +174,69 @@
 userinfo  = _userinfo % authority
 port      = _port     % authority
 
-query =
-  Label {
-    lget = decode . lget _query
-  , lset = lset _query . encode
-  }
-
-host =
-  Label {
-    lget = show . lget (_host % authority)
-  , lset = lset (_host % authority) . fromJust . parseHost
-  }
-
-path =
-  Label {
-    lget = decode . show . lget _path
-  , lset = lset _path . fromJust . parsePath . encode
-  }
+query = Label
+  (decode . lget _query)
+  (lset _query . encode)
 
--- testUri0 :: URI
--- testUri0 = fromJust $ parseURI "http://sebas@hs.spugium.net:8080/wiki/first%20section/chapter-2.pdf?additional=vars"
+host = Label
+  (show . lget (_host % authority))
+  (lset (_host % authority) . maybe (Hostname []) id . parseHost)
 
--- testUri1 :: URI
--- testUri1 = fromJust $ parseURI "http://cs.uu.nl/docs/../docs/vakken/../vakken/"
+path = Label
+  (decode . show . lget _path)
+  (lset _path . maybe (Path True []) id . parsePath . encode)
 
 -------[ creating, selection and modifying URIs ]------------------------------
 
--- pseudo constructors for `empty values'
+{- | Constructors for making empty URI. -}
 
 mkURI :: URI
 mkURI = URI False mkScheme mkAuthority mkPath mkQuery mkFragment
 
+{- | Constructors for making empty `Scheme`. -}
+
 mkScheme :: Scheme
 mkScheme = ""
 
+{- | Constructors for making empty `Path`. -}
+
 mkPath :: Path
 mkPath = Path False []
 
+{- | Constructors for making empty `Authority`. -}
+
 mkAuthority :: Authority
 mkAuthority = Authority "" mkHost mkPort
 
+{- | Constructors for making empty `Query`. -}
+
 mkQuery :: Query
 mkQuery = ""
 
+{- | Constructors for making empty `Fragment`. -}
+
 mkFragment :: Fragment
 mkFragment = ""
 
+{- | Constructors for making empty `UserInfo`. -}
+
 mkUserinfo :: UserInfo
 mkUserinfo = ""
 
+{- | Constructors for making empty `Host`. -}
+
 mkHost :: Host
 mkHost = Hostname []
 
+{- | Constructors for making empty `Port`. -}
+
 mkPort :: Port
 mkPort = (-1)
 
 -------[ path encoding and decoding ]------------------------------------------
 
+{- | URI encode a string. -}
+
 encode :: String -> String
 encode = concatMap encodeChr
   where
@@ -224,10 +246,12 @@
           intToDigit (shiftR (ord c) 4) :
           intToDigit ((ord c) .&. 0x0F) : []
 
+{- | URI decode a string. -}
+
 decode :: String -> String
 decode [] = []
-decode ('%':d:e:ds) | isHexDigit d && isHexDigit e = f d e : decode ds
-  where f a b = chr $ (ord a-ord '0') * 16 + (ord b - ord '0')
+decode ('%':d:e:ds) | isHexDigit d && isHexDigit e = (chr $ digs d * 16 + digs e) : decode ds 
+  where digs a = fromJust $ lookup (toLower a) $ zip "0123456789abcdef" [0..]
 decode (d:ds) = d : decode ds
 
 -------[ show instance for URIs ]----------------------------------------------
@@ -267,19 +291,28 @@
 
 -------[ global URI parse interface ]------------------------------------------
 
--- Top level parsers.
+{- | Parse string into a `URI`. -}
+
 parseURI :: String -> Maybe URI
 parseURI = eitherToMaybe . parse pUriReference ""
 
+{- | Parse string into a `URI` and only accept absolute URIs. -}
+
 parseAbsoluteURI :: String -> Maybe URI
 parseAbsoluteURI = eitherToMaybe . parse pAbsoluteURI ""
 
+{- | Parse string into a `Authority` object. -}
+
 parseAuthority :: String -> Maybe Authority
 parseAuthority = eitherToMaybe . parse pAuthority ""
 
+{- | Parse string into a `Path`. -}
+
 parsePath :: String -> Maybe Path
 parsePath = eitherToMaybe . parse pPath ""
 
+{- | Parse string into a `Host`. -}
+
 parseHost :: String -> Maybe Host
 parseHost = eitherToMaybe . parse pHost ""
 
@@ -493,8 +526,6 @@
 
 -------[ parsing query parameters ]--------------------------------------------
 
--- Parse a pre-decoded query string into key value pairs parameters.
-
 pQueryParams :: GenParser Char st Parameters
 pQueryParams = 
       filter (not . null . fst)
@@ -504,9 +535,13 @@
    *> (translateParam <$> many (noneOf "&"))))
       (char '&')
 
+{- | Parse a pre-decoded query string into key value pairs parameters. -}
+
 parseQueryParams :: String -> Maybe Parameters
 parseQueryParams = eitherToMaybe . parse pQueryParams  ""
 
+{- | Fetch the query parameters form a URI. -}
+
 queryParams :: URI -> Parameters
 queryParams = maybe [] id . parseQueryParams . lget query
 
@@ -518,10 +553,7 @@
 
 -------[ filename path utilities ]---------------------------------------------
 
-mkPathRelative :: FilePath -> FilePath
-mkPathRelative = dropWhile (=='/')
-
--- Generate a label for the filename extension.
+{- | Label to access the extension of a filename. -}
 
 extension :: Label FilePath (Maybe String)
 extension = Label {lget = getExt, lset = setExt}
@@ -533,15 +565,21 @@
     setExt e p = let (u, v) = splt p in
                  (if isExt u v then p else init v) ++ maybe "" ('.':) e
 
--- Try to guess the 'correct' mime type for the input file based on the file
--- extension.
+{- |
+Try to guess the correct mime type for the input file based on the file
+extension.
+-}
 
 mimetype :: FilePath -> Maybe String
 mimetype p = lget extension p >>= mime
 
--- Normalize a path by removing or merging all dot or dot-dot segments and
--- double slashes. Todo: cleanup, remove fixp.
+{- |
+Normalize a path by removing or merging all dot or dot-dot segments and double
+slashes. 
+-}
 
+-- Todo: cleanup, remove fixp.
+
 normalize :: FilePath -> FilePath
 normalize [] = []
 normalize p = fixAbs absolut $ intercalate "/" $ norm
@@ -559,13 +597,25 @@
     merge (x:xs)         = x : merge xs
     merge xs             = xs
 
-jail :: FilePath -> FilePath -> Maybe FilePath
+{- | Make a path relative by removing the first slash when there is one. -}
+
+mkPathRelative :: FilePath -> FilePath
+mkPathRelative = dropWhile (=='/')
+
+{- | Jail a filepath within a jail directory. -}
+
+jail
+  :: FilePath         -- ^ Jail directory.
+  -> FilePath         -- ^ Filename to jail.
+  -> Maybe FilePath
 jail jailDir p =
   let nj = normalize jailDir
       np = normalize p in
   if nj `isPrefixOf` np -- && not (".." `isPrefixOf` np)
     then Just np
     else Nothing
+
+{- | Concatenate and normalize two filepaths. -}
 
 (/+) :: FilePath -> FilePath -> FilePath
 a /+ b = normalize (a ++ "/" ++ b)
diff --git a/src/Network/Salvia/Advanced/CleverCSS.hs b/src/Network/Salvia/Advanced/CleverCSS.hs
deleted file mode 100644
--- a/src/Network/Salvia/Advanced/CleverCSS.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-module Network.Salvia.Advanced.CleverCSS (
-    hFilterCSS
-  , hCleverCSS
-  , hParametrizedCleverCSS
-  ) where
-
-import Network.Protocol.Uri (Parameters)
-import Network.Salvia.Handlers.ExtensionDispatcher (hExtension)
-import Network.Salvia.Handlers.Fallback (hOr)
-import Network.Salvia.Handlers.File (hFile, hFileFilter)
-import Network.Salvia.Handlers.PathRouter (hParameters)
-import Network.Salvia.Handlers.Rewrite (hRewriteExt)
-import Network.Salvia.Httpd (Handler)
-import Text.CSS.CleverCSS (cleverCSSConvert)
-
-hFilterCSS :: Handler () -> Handler () -> Handler ()
-hFilterCSS cssfilter handler = do
-  hExtension (Just "css")
-    (hFile `hOr` cssfilter)
-    handler
-
-hCleverCSS :: Handler ()
-hCleverCSS = hParameters >>= hParametrizedCleverCSS
-
-hParametrizedCleverCSS :: Parameters -> Handler ()
-hParametrizedCleverCSS p = do
-  hRewriteExt (fmap ('c':)) (hFileFilter convert)
-  where convert = either id id . flip (cleverCSSConvert "") (map (fmap $ maybe "" id) p)
-
diff --git a/src/Network/Salvia/Advanced/ExtendedFileSystem.hs b/src/Network/Salvia/Advanced/ExtendedFileSystem.hs
deleted file mode 100644
--- a/src/Network/Salvia/Advanced/ExtendedFileSystem.hs
+++ /dev/null
@@ -1,20 +0,0 @@
-module Network.Salvia.Advanced.ExtendedFileSystem (hExtendedFileSystem) where
-
-import Network.Salvia.Advanced.CleverCSS (hFilterCSS, hCleverCSS)
-import Network.Salvia.Advanced.HsColour (hHighlightHaskell, hHsColour)
-import Network.Salvia.Handlers.Directory (hDirectoryResource)
-import Network.Salvia.Handlers.File (hFileResource)
-import Network.Salvia.Handlers.FileSystem (hFileTypeDispatcher)
-import Network.Salvia.Handlers.Rewrite (hWithDir)
-import Network.Salvia.Httpd (Handler)
-
-hExtendedFileSystem :: FilePath -> Handler ()
-hExtendedFileSystem dir =
-    hFileTypeDispatcher dir
-    hDirectoryResource
-  $ \r -> hHighlightHaskell (hHsColour r)
-  $ hWithDir dir
-  $ hFilterCSS hCleverCSS
-  $ hFileResource r
-
-
diff --git a/src/Network/Salvia/Advanced/HsColour.hs b/src/Network/Salvia/Advanced/HsColour.hs
deleted file mode 100644
--- a/src/Network/Salvia/Advanced/HsColour.hs
+++ /dev/null
@@ -1,54 +0,0 @@
-module Network.Salvia.Advanced.HsColour (
-    hHighlightHaskell
-  , hHsColour
-  , hHsColourCustomStyle
-
-  , defaultStyleSheet
-  ) where
-
-import Data.Record.Label
-import Language.Haskell.HsColour.CSS
-import Network.Protocol.Http (contentType, utf8)
-import Network.Salvia.Handlers.ExtensionDispatcher
-import Network.Salvia.Handlers.File
-import Network.Salvia.Httpd
-
-hHighlightHaskell :: Handler () -> Handler () -> Handler ()
-hHighlightHaskell highlighter = 
-  hExtensionRouter [
-    (Just "hs",  highlighter)
-  , (Just "lhs", highlighter)
-  , (Just "ag",  highlighter)
-  ]
-
-hHsColour :: ResourceHandler ()
-hHsColour = hHsColourCustomStyle (Left defaultStyleSheet)
-
--- Left means direct inclusion of stylesheet, right means link to external
--- stylesheet.
-
-hHsColourCustomStyle :: Either String String -> ResourceHandler ()
-hHsColourCustomStyle style r = do
-  sendStr (either id makeStyleLink style)
-  hFileResourceFilter (hscolour False True "") r
-  setM (contentType % response) ("text/html", Just utf8)
-
-makeStyleLink :: String -> String
-makeStyleLink css = "<link rel=\"stylesheet\" type=\"text/css\" href=\"" ++ css ++ "\"></link>"
-
-defaultStyleSheet :: String
-defaultStyleSheet = filter (/=' ') $ concat [
-    "<style>"
-  , ".varop      { color : #960; font-weight : normal; }"
-  , ".keyglyph   { color : #960; font-weight : normal; }"
-  , ".definition { color : #005; font-weight : bold;   }"
-  , ".varid      { color : #444; font-weight : normal; }"
-  , ".keyword    { color : #000; font-weight : bold;   }"
-  , ".comment    { color : #44f; font-weight : normal; }"
-  , ".conid      { color : #000; font-weight : normal; }"
-  , ".num        { color : #00a; font-weight : normal; }"
-  , ".str        { color : #a00; font-weight : normal; }"
-  , "</style>"
-  , ""
-  ]
-
diff --git a/src/Network/Salvia/Core/Config.hs b/src/Network/Salvia/Core/Config.hs
--- a/src/Network/Salvia/Core/Config.hs
+++ b/src/Network/Salvia/Core/Config.hs
@@ -6,25 +6,36 @@
 import Network.Socket hiding (send, listen)
 import System.IO
 
--------- server configuration -------------------------------------------------
+{- |
+The HTTP server configuration specifies some important network settings the
+server must know before being able to run. Most fields speak for themselves.
+-}
 
 data HttpdConfig =
    HttpdConfig {
-     hostname   :: String       -- Server hostname.
-   , email      :: String       -- Server admin email address.
-   , listenAddr :: HostAddress  -- Addres to bind to.
-   , listenPort :: PortNumber   -- Port to listen on.
-   , backlog    :: Int          -- TCP backlog.
-   , bufferSize :: Int          -- Serve chunck with size.
+     hostname   :: String       -- ^ Server hostname.
+   , email      :: String       -- ^ Server admin email address.
+   , listenAddr :: HostAddress  -- ^ Addres to bind to.
+   , listenPort :: PortNumber   -- ^ Port to listen on.
+   , backlog    :: Int          -- ^ TCP backlog.
+   , bufferSize :: Int          -- ^ Serve chunck with size.
    }
 
+{- |
+The default server configuration sets some safe default values. The server will
+by default bind to 0.0.0.0 at port 80. The default value for the TCP backlog is
+4, the default socket buffer size is 64KB. This function has to be in IO
+because of the translation from a `String` to a `HostAddress` using
+`inet_addr`.
+-}
+
 defaultConfig :: IO HttpdConfig
 defaultConfig = do
   addr <- inet_addr "0.0.0.0"
   return
     $ HttpdConfig {
       hostname   = "hostname"
-    , email      = "www@hostname"
+    , email      = "admin@localhost"
     , listenAddr = addr
     , listenPort = 80
     , backlog    = 4
diff --git a/src/Network/Salvia/Core/Context.hs b/src/Network/Salvia/Core/Context.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Core/Context.hs
@@ -0,0 +1,101 @@
+{-# LANGUAGE FlexibleInstances, TemplateHaskell #-}
+module Network.Salvia.Core.Context (
+    SendAction
+  , SendQueue
+  , Context
+
+  , config
+  , request
+  , response
+  , sock
+  , address
+  , queue
+
+  , mkContext
+  ) where
+
+import Data.Record.Label
+import Network.Protocol.Http (Message, emptyRequest, emptyResponse)
+import Network.Salvia.Core.Config
+import Network.Socket (SockAddr)
+import System.IO
+
+-------- single request/response context --------------------------------------
+
+-- | A send action is some thing that works on an IO handle.
+type SendAction = Handle -> IO ()
+
+{- |
+The send queue is an abstraction to make sure all data that belongs to the
+message body is sent after the response headers have been sent.  Instead of
+sending data to client directly over the socket from the context it is
+preferable to queue send actions in the context's send queue. The entire send
+queue can be flushed to the client at once after the HTTP headers have been
+sent at the end of a request handler.
+-}
+
+type SendQueue  = [SendAction]
+
+{- |
+A handler context contains all the information needed by the request handlers
+to perform their task and to set up a proper response. All the fields in the
+context are accessible using the read/write labels defined below.
+-}
+
+data Context =
+  Context {
+    _config   :: HttpdConfig -- ^ The HTTP server configuration.
+  , _request  :: Message     -- ^ The HTTP request header.
+  , _response :: Message     -- ^ The HTTP response header.
+  , _sock     :: Handle      -- ^ The socket handle for the connection with the client.
+  , _address  :: SockAddr    -- ^ The client addres.
+  , _queue    :: SendQueue   -- ^ The queue of send actions.
+  }
+
+$(mkLabels [''Context])
+
+{- | The queue containing all send actions. -}
+queue :: Label Context SendQueue
+
+{- | The client address.  -}
+address  :: Label Context SockAddr
+
+{- | The socket to the client. -}
+sock :: Label Context Handle
+
+{- |
+The server response. Using the appropriate handler the response can be sent to
+the client after processing the request.
+-}
+
+response :: Label Context Message
+
+{- |
+The client request. This request is initially empty and only available after
+the message has been parsed by the appropriate handler.
+-}
+
+request :: Label Context Message
+
+{- |
+The global server configuration. Modifying this has no effect on consecutive
+requests.
+-}
+
+config :: Label Context HttpdConfig
+
+{- |
+Create and default server context with the specified server configuration,
+client address and socket.
+-}
+
+mkContext :: HttpdConfig -> SockAddr -> Handle -> Context
+mkContext c a s = Context {
+    _config   = c
+  , _request  = emptyRequest
+  , _response = emptyResponse  -- 200 OK, by default.
+  , _sock     = s
+  , _address  = a
+  , _queue    = []
+  }
+
diff --git a/src/Network/Salvia/Core/Handler.hs b/src/Network/Salvia/Core/Handler.hs
--- a/src/Network/Salvia/Core/Handler.hs
+++ b/src/Network/Salvia/Core/Handler.hs
@@ -1,74 +1,32 @@
 {-# LANGUAGE FlexibleInstances, TemplateHaskell #-}
 
 module Network.Salvia.Core.Handler (
-    SendAction
-  , SendQueue
-  , Context (..)
-
-  , config
-  , request
-  , response
-  , sock
-  , address
-  , queue
-
-  , mkContext
-
-  , Handler
+    Handler
   , ResourceHandler
   , UriHandler
   ) where
 
 import Control.Applicative (Applicative, pure, (<*>))
 import Control.Monad.State (StateT, ap)
-import Data.Record.Label
-import Network.Protocol.Http (Message, emptyRequest, emptyResponse)
 import Network.Protocol.Uri (URI)
-import Network.Salvia.Core.Config
-import Network.Socket (SockAddr)
+import Network.Salvia.Core.Context
 import System.IO
 
--------- single request/response context --------------------------------------
-
-type SendAction = Handle -> IO ()
-type SendQueue  = [SendAction]
-
-data Context =
-  Context {
-    _config   :: HttpdConfig -- ^ The HTTP server configuration.
-  , _request  :: Message     -- ^ The HTTP request header.
-  , _response :: Message     -- ^ The HTTP response header.
-  , _sock     :: Handle      -- ^ The socket handle for the connection with the client.
-  , _address  :: SockAddr    -- ^ The client addres.
-  , _queue    :: SendQueue   -- ^ The queue of send actions.
-  }
+{- |
+A HTTP request handler lives in `IO` and carries a server context in the
+`State` monad. This module also provides an `Applicative` instance for `StateT`
+`Context` `IO`.
+-}
 
-$(mkLabels [''Context])
+type Handler a = StateT Context IO a
 
-queue    :: Label Context SendQueue
-address  :: Label Context SockAddr
-sock     :: Label Context Handle
-response :: Label Context Message
-request  :: Label Context Message
-config   :: Label Context HttpdConfig
+{- | A resource handler is something that works on some filesystem resource. -}
 
--- Create and empty server context.
-mkContext :: HttpdConfig -> SockAddr -> Handle -> Context
-mkContext c a s = Context {
-    _config   = c
-  , _request  = emptyRequest
-  , _response = emptyResponse  -- 200 OK, by default.
-  , _sock     = s
-  , _address  = a
-  , _queue    = []
-  }
+type ResourceHandler a = FilePath -> Handler a
 
--------- request handlers -----------------------------------------------------
+{- | A URI handler is something that works on an request URI. -}
 
--- HTTP handler types.
-type Handler         a = StateT Context IO a
-type ResourceHandler a = FilePath -> Handler a
-type UriHandler      a = URI -> Handler a
+type UriHandler a = URI -> Handler a
 
 -- Make handlers applicative.
 instance Applicative (StateT Context IO) where
diff --git a/src/Network/Salvia/Core/IO.hs b/src/Network/Salvia/Core/IO.hs
--- a/src/Network/Salvia/Core/IO.hs
+++ b/src/Network/Salvia/Core/IO.hs
@@ -1,20 +1,32 @@
-module Network.Salvia.Core.IO (
+{-|
+This module contains some useful functions for IO over the client socket. Some
+functions directly work on the socket and some queue actions into the send
+queue. The latter form is more high-level and recommended for common use. This
+module is likely to be extended in the future with other useful function that
+handle specific message IO.
+-}
 
-    sendHeaders
+module Network.Salvia.Core.IO (
 
-  , send
+  -- * Queing send actions to the send queue.
+    send
   , sendStr
   , sendStrLn
   , sendBs
 
+  -- * Queing spool actions to the send queue.
   , spool
   , spoolBs
-  , spoolAll
-  , spoolN
 
+  -- * Send actions directly using the socket.
+  , flushHeaders
+  , flushQueue
+
+  -- * Directly manipulate the send queue.
   , emptyQueue
   , reset
 
+  -- * Reading data from the client request.
   , contents
   , contentsUtf8
   , uriEncodedPostParamsUTF8
@@ -29,6 +41,7 @@
 import Data.Record.Label
 import Network.Protocol.Http
 import Network.Protocol.Uri (Parameters, parseQueryParams, decode)
+import Network.Salvia.Core.Context
 import Network.Salvia.Core.Handler
 import System.IO
 import qualified Data.ByteString.Lazy as B
@@ -36,77 +49,87 @@
 
 -------------------------------------------------------------------------------
 
--- Send the response header to the socket.
-sendHeaders :: Handler ()
-sendHeaders = do
-  r <- getM response
-  s <- getM sock 
-  lift $ hPutStr s (showMessageHeader r)
+{- |
+Queue one potential send action in the send queue. This will not (yet) be sent
+over the socket.
+-}
 
--- Queue a potential send action in the send queue.
 send :: SendAction -> Handler ()
-send f = modM queue (++[f]) -- modify (\m -> m { queue = (queue m) ++ [f] })
+send f = modM queue (++[f])
 
--- Queue a String for sending.
-sendStr, sendStrLn :: String -> Handler ()
-sendStr s   = send (flip U.hPutStr s)
+{- | Queue the action of sending one UTF-8 encoded `String` over the socket. -}
+
+sendStr :: String -> Handler ()
+sendStr s = send (flip U.hPutStr s)
+
+{- |
+Queue the action of sending one UTF-8 encoded `String` with extra linefeed
+over the socket.
+-}
+
+sendStrLn :: String -> Handler ()
 sendStrLn s = send (flip U.hPutStr (s ++ "\n"))
 
--- Queue a ByteString for sending.
+{- | Queue the action of sending one lazy `B.ByteString` over the socket. -}
+
 sendBs :: B.ByteString -> Handler ()
 sendBs bs = send (flip B.hPutStr bs)
 
-{-
-Queue spooling the entire contents of a stream to the socket using a String
-based filter.
+{- |
+Queue spooling the entire contents of a stream to the socket using a UTF-8
+encoded `String` based filter.
 -}
+
 spool :: (String -> String) -> Handle -> Handler ()
 spool f fd = send (\s -> U.hGetContents fd >>= \d -> U.hPutStr s (f d))
 
-{-
-Queue spooling the entire contents of a stream to the socket using a ByteString
-based filter.
+{- |
+Queue spooling the entire contents of a stream to the socket using a
+`B.ByteString` based filter.
 -}
+
 spoolBs :: (B.ByteString -> B.ByteString) -> Handle -> Handler ()
 spoolBs f fd = send (\s -> B.hGetContents fd >>= \d -> B.hPut s (f d))
 
-{-
-Spool the entire contents from one stream to another stream, after this the
-handle will be closed.
--}
-spoolAll :: Handle -> Handle -> Handler ()
-spoolAll f t = lift $ do
-  B.hGetContents f >>= B.hPut t
-  hClose t
+{- | Send all the response headers directly over the socket. -}
 
-{-
-Spool a fixed number of bytes from one stream to another stream, after this the
-handle will be closed.
--}
-spoolN :: Handle -> Handle -> Int -> Handler ()
-spoolN f t n = lift $ do
-  B.hGet f n >>= B.hPut t
-  hClose t
+flushHeaders :: Handler ()
+flushHeaders = do
+  r <- getM response
+  s <- getM sock 
+  lift $ hPutStr s (showMessageHeader r)
 
--- Reset the send queue.
+{- | Apply all send actions successively to the client socket. -}
+
+flushQueue :: Handler ()
+flushQueue =
+  do h <- getM sock
+     q <- getM queue
+     liftIO $
+       do mapM_ ($ h) q
+          hFlush h
+
+{- | Reset the send queue by throwing away all potential send actions. -}
+
 emptyQueue :: Handler ()
 emptyQueue = setM queue []
 
--- Reset both the send queue and the generated response.
+{- | Reset both the send queue and the generated server response. -}
+
 reset :: Handler ()
 reset = do
   setM response emptyResponse
   emptyQueue
 
 {- |
-First naive handler to retreive the request payload as a ByteString. This
-probably does not handle all the quirks that the HTTP protocol specifies, but
-it does the job for now. When a 'ContentLength' header field is available only
-this fixed number of bytes will read from the socket. When neither the
-'KeepAlive' and 'ContentLength' header fields are available the entire payload
-of the request will be read from the socket. This method is probably only
-useful in the case of 'PUT' request, because no decoding of 'POST' data is
-handled.
+First (possibly naive) handler to retreive the client request body as a
+`B.ByteString`. This probably does not handle all the quirks that the HTTP
+protocol specifies, but it does the job for now. When a 'contentLength' header
+field is available only this fixed number of bytes will read from the socket.
+When neither the 'keepAlive' and 'contentLength' header fields are available
+the entire payload of the request will be read from the socket. This method is
+probably only useful in the case of 'PUT' request, because no decoding of
+'POST' data is handled.
 -}
 
 contents :: Handler (Maybe B.ByteString)
@@ -115,18 +138,23 @@
   kpa <- getM (keepAlive     % request)
   s   <- getM sock
   lift $
-    case (kpa, len) of
+    case (kpa::Maybe Integer, len::Maybe Integer) of
       (_,       Just n)  -> liftM Just (B.hGet s (fromIntegral n))
       (Nothing, Nothing) -> liftM Just (B.hGetContents s)
       _                  -> return Nothing
 
 {- |
-Like the `contents' function but decodes the data as UTF8. Soon, time will come
-that decoding will be based upon the requested encoding.
+Like the `contents' function but decodes the data as UTF-8. Soon, time will
+come that decoding will be based upon the requested encoding.
 -}
 
 contentsUtf8 :: Handler (Maybe String)
 contentsUtf8 = (fmap $ decodeLazy UTF8) `liftM` contents
+
+{- |
+Try to parse the supplied request body as URI encoded `POST` parameters in
+UTF-8 encoding. Returns as a URI parameter type or nothing when parsing fails.
+-}
 
 uriEncodedPostParamsUTF8 :: Handler (Maybe Parameters)
 uriEncodedPostParamsUTF8 = liftM (>>= parseQueryParams . decode) contentsUtf8
diff --git a/src/Network/Salvia/Core/Main.hs b/src/Network/Salvia/Core/Main.hs
--- a/src/Network/Salvia/Core/Main.hs
+++ b/src/Network/Salvia/Core/Main.hs
@@ -1,15 +1,21 @@
 module Network.Salvia.Core.Main (start) where
 
+import Control.Concurrent (forkIO)
 import Control.Monad.State
 import Network.Salvia.Core.Config (listenAddr, listenPort, backlog, HttpdConfig)
-import Network.Salvia.Core.Handler (Handler, mkContext)
-import Network.Salvia.Core.Network (server)
+import Network.Salvia.Core.Context (mkContext)
+import Network.Salvia.Core.Handler (Handler)
+import Network.Socket
+import System.IO
 
 -------- HTTP deamon implementation -------------------------------------------
 
-{-
-Given a server configuration and a handler to invoke when receiving client
-request the server starts and keep running forever.
+{- |
+Start a webserver with a specific server configuration and default handler. The
+server will go into an infinite loop and will repeatedly accept client
+connections on the address and port specified in the configuration. For every
+connection the specified handler will be executed with the client address and
+socket stored in the handler context.
 -}
 
 start :: HttpdConfig -> Handler () -> IO ()
@@ -23,4 +29,31 @@
     tcpHandler handle addr = 
       fst `liftM` runStateT httpHandler
         (mkContext config addr handle)
+
+{-
+Start a listening TCP server on the specified address/port combination and
+handle every connection with a custom handler.
+-}
+
+server :: HostAddress -> PortNumber -> Int -> (Handle -> SockAddr -> IO ()) -> IO ()
+server addr port blog handler = do
+  sock <- socket AF_INET Stream 0
+  setSocketOption sock ReuseAddr 1
+  bindSocket sock $ SockAddrInet port addr
+  listen sock blog
+  acceptLoop sock handler
+
+{-
+Accept connections on the listening socket and pass execution to the
+application specific connection handler.
+-}
+
+acceptLoop :: Socket -> (Handle -> SockAddr -> IO ()) -> IO ()
+acceptLoop sock handler = do
+  forever $ do
+    (sock', addr) <- accept sock
+    forkIO $
+      do h <- socketToHandle sock' ReadWriteMode
+         handler h addr
+  putStrLn "quiting"
 
diff --git a/src/Network/Salvia/Core/Network.hs b/src/Network/Salvia/Core/Network.hs
deleted file mode 100644
--- a/src/Network/Salvia/Core/Network.hs
+++ /dev/null
@@ -1,52 +0,0 @@
-module Network.Salvia.Core.Network where
-
-import Control.Concurrent (forkIO)
-import Control.Monad
-import Network.Socket
-import System.IO
-
--------- application independent networking -----------------------------------
-
-type Server a =
-     Handle
-  -> SockAddr
-  -> IO a
-
-{-
-Start a listening TCP server on the specified address/port combination and
-handle every connection with a custom handler.
--}
-
-server :: HostAddress -> PortNumber -> Int -> Server a -> IO ()
-server addr port backlog handler = do
-  sock <- socket AF_INET Stream 0
-  setSocketOption sock ReuseAddr 1
-  bindSocket sock $ SockAddrInet port addr
-  listen sock backlog
-  acceptLoop sock handler
-
-{-
-Accept connections on the listening socket and pass execution to the
-application specific connection handler.
--}
-
-acceptLoop :: Socket -> Server s -> IO ()
-acceptLoop sock handler = do
-  forever $ do
-    (sock', addr) <- accept sock
-    forkIO $ do
-      handle <- socketToHandle sock' ReadWriteMode
-      -- TODO: Using NoBuffering here may crash the entire program (GHC
-      -- runtime?) when processing more requests than just a few:
-      hSetBuffering handle (BlockBuffering (Just (64*1024)))
-      handler handle addr
-      -- We can probably just ignore exceptions from hClose, but this for the
-      -- moment I am interested whether this even happens.
-      hClose handle
---       e <- try (hClose handle)
---       case e of
---         Left ex -> putStrLn ("Failure during hClose: " ++ show (ex :: IOException))
---         Right _ -> return ()
-  putStrLn "quiting"
-
-
diff --git a/src/Network/Salvia/Handler/Banner.hs b/src/Network/Salvia/Handler/Banner.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Banner.hs
@@ -0,0 +1,28 @@
+module Network.Salvia.Handler.Banner (hBanner) where
+
+import Control.Monad.State
+import Data.Record.Label
+import Data.Time.Clock (getCurrentTime)
+import Data.Time.Format (formatTime)
+import Data.Time.LocalTime (getCurrentTimeZone, utcToLocalTime)
+import Network.Protocol.Http
+import Network.Salvia.Httpd
+import System.Locale (defaultTimeLocale)
+
+{- |
+The 'hBanner' handler adds the current date-/timestamp and a custom server name
+to the response headers.
+-}
+
+hBanner ::
+     String     -- ^ The HTTP server name.
+  -> Handler ()
+hBanner sv = do
+  dt <- lift $ do
+    zone <- getCurrentTimeZone
+    time <- liftM (utcToLocalTime zone) getCurrentTime
+    return $ formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %Z" time
+  enterM response $ do
+    setM date   dt
+    setM server sv
+
diff --git a/src/Network/Salvia/Handler/CGI.hs b/src/Network/Salvia/Handler/CGI.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/CGI.hs
@@ -0,0 +1,22 @@
+module Network.Salvia.Handler.CGI (hCGI) where
+
+import Control.Monad.State
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Salvia.Httpd
+import System.IO
+import System.Process (runProcess, waitForProcess)
+
+{- |
+Handle CGI scripts, not yet working properly.
+-}
+
+hCGI :: ResourceHandler ()
+hCGI name = do
+  setM (status % response) OK
+  h <- getM sock
+  lift $ do
+    p <- runProcess name [] Nothing Nothing (Just h) (Just h) (Just stderr)
+    waitForProcess p
+    return ()
+
diff --git a/src/Network/Salvia/Handler/Close.hs b/src/Network/Salvia/Handler/Close.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Close.hs
@@ -0,0 +1,51 @@
+module Network.Salvia.Handler.Close (
+    hCloseConn
+  , hKeepAlive
+  ) where
+
+import Control.Monad.State
+import Data.Maybe
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Salvia.Httpd
+import System.IO
+
+{- |
+Run a handler once and close the connection afterwards.
+-}
+
+hCloseConn :: Handler () -> Handler ()
+hCloseConn h = h >> getM sock >>= liftIO . hClose
+
+{- |
+Run a handler and keep the connection open for potential consecutive requests.
+The connection will only be closed after a request finished and one or more of
+the following criteria are met:
+
+* There is no `contentLength` set in the response headers. When this is the
+  case the connection cannot be kept alive.
+
+* The client has set the `connection` header field to 'close'.
+
+* The connection has already been closed, possible due to IO errors.
+-}
+
+hKeepAlive :: Handler () -> Handler ()
+hKeepAlive handler =
+  do handler
+
+     h      <- getM sock
+     conn   <- getM (connection % request)
+     len    <- getM (contentLength % response)
+     closed <- liftIO (hIsClosed h)
+
+     if or [closed, conn == "Close", isNothing (len::Maybe Integer) ]
+       then liftIO (hClose h)
+       else resetContext >> hKeepAlive handler
+
+resetContext :: Handler ()
+resetContext =
+  do setM request emptyRequest
+     setM response emptyResponse
+     setM queue []
+
diff --git a/src/Network/Salvia/Handler/Cookie.hs b/src/Network/Salvia/Handler/Cookie.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Cookie.hs
@@ -0,0 +1,44 @@
+module Network.Salvia.Handler.Cookie (
+    hSetCookies
+  , hGetCookies
+
+  , newCookie
+  ) where
+
+import Control.Applicative hiding (empty)
+import Control.Monad.State
+import Data.Record.Label
+import Data.Time.Format
+import Data.Time.LocalTime
+import Network.Protocol.Cookie (showCookies, Cookie, Cookies, parseCookies, empty, path, port, expires)
+import Network.Protocol.Http (cookie)
+import Network.Salvia.Httpd
+import System.Locale (defaultTimeLocale)
+
+{- | Set the `cookie` HTTP response header (Set-Cookie) with the specified `Cookies`. -}
+
+hSetCookies :: Cookies -> Handler ()
+hSetCookies = setM (cookie % response) . showCookies
+
+{- | Try to get the cookies from the HTTP `cookie` request header. -}
+
+hGetCookies :: Handler (Maybe Cookies)
+hGetCookies = parseCookies <$> getM (cookie % request)
+
+{- |
+Convenient method for creating cookies that expire in the near future and are
+bound to the domain and port this server runs on. The path will be locked to
+root.
+-}
+
+newCookie :: LocalTime -> Handler Cookie
+newCookie expire = do
+  httpd <- getM config
+  return $ empty {
+      path    = Just "/"
+--  , domain  = Just $ '.' : hostname httpd
+    , port    = [fromEnum $ listenPort httpd]
+    , expires = Just $ formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %Z" expire
+    }
+
+
diff --git a/src/Network/Salvia/Handler/Counter.hs b/src/Network/Salvia/Handler/Counter.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Counter.hs
@@ -0,0 +1,11 @@
+module Network.Salvia.Handler.Counter (hCounter) where
+
+import Control.Concurrent.STM
+import Control.Monad.State
+import Network.Salvia.Httpd
+
+{- | This handler simply increases the request counter variable. -}
+
+hCounter :: TVar Int -> Handler ()
+hCounter c = lift $ atomically $ readTVar c >>= writeTVar c . (+1)
+
diff --git a/src/Network/Salvia/Handler/Directory.hs b/src/Network/Salvia/Handler/Directory.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Directory.hs
@@ -0,0 +1,64 @@
+module Network.Salvia.Handler.Directory (
+    hDirectory
+  , hDirectoryResource
+  ) where
+
+import Control.Monad.State
+import Data.List (sort)
+import Data.Record.Label
+import Misc.Misc (bool)
+import Network.Protocol.Http
+import Network.Protocol.Uri (path)
+import Network.Salvia.Handler.File (hResource)
+import Network.Salvia.Handler.Redirect
+import Network.Salvia.Httpd
+import System.Directory (doesDirectoryExist, getDirectoryContents)
+
+{- |
+Serve a simple HTML directory listing for the specified directory on the
+filesystem.
+-}
+
+hDirectoryResource :: ResourceHandler ()
+hDirectoryResource dirName = do
+  (u, p) <- bothM (uri % request) (getM path)
+  if (null p) || last p /= '/'
+   then hRedirect (lmod path (++"/") u)
+   else dirHandler dirName
+
+{- |
+Like `hDirectoryResource` but uses the path of the current request URI.
+-}
+
+hDirectory :: Handler ()
+hDirectory = hResource hDirectoryResource
+
+dirHandler:: ResourceHandler ()
+dirHandler dirName = do
+  p <- getM (path % uri % request)
+  filenames <- lift $ getDirectoryContents dirName
+  processed <- lift $ mapM (processFilename dirName) (sort filenames)
+  let b = listing p processed
+  enterM response $ do
+    setM contentType ("text/html", Nothing)
+    setM contentLength (Just $ length b)
+    setM status OK
+  sendStr b
+
+-- Add trailing slash to a directory name.
+processFilename :: FilePath -> FilePath -> IO FilePath
+processFilename d f = bool (f ++ "/") f `liftM` doesDirectoryExist (d ++ f)
+
+-- Turn a list of filenames into HTML directory listing.
+listing :: FilePath -> [FilePath] -> String
+listing dirName fileNames =
+  concat [
+    "<html><head><title>Index of "
+  , dirName
+  , "</title></head><body><h1>Index of "
+  , dirName
+  , "</h1><ul>"
+  , fileNames >>= \f -> concat ["<li><a href='", f, "'>", f, "</a></li>"]
+  , "</ul></body></html>"
+  ]
+
diff --git a/src/Network/Salvia/Handler/Dispatching.hs b/src/Network/Salvia/Handler/Dispatching.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Dispatching.hs
@@ -0,0 +1,48 @@
+module Network.Salvia.Handler.Dispatching (
+    Dispatcher
+  , ListDispatcher
+  , hDispatch
+  , hListDispatch
+  ) where
+
+import Data.Record.Label
+import Network.Salvia.Httpd
+
+{- |
+The dispatcher type takes one value to dispatch on and two handlers. The first
+handler will be used when the predicate on the dispatch value returned `True`,
+the second (default) handler will be used when the predicate returs
+`False`.
+-}
+
+type Dispatcher a b = a -> Handler b -> Handler b -> Handler b
+
+{- |
+A list dispatcher takes a mapping from dispatch values and handlers and one
+default handler.
+-}
+
+type ListDispatcher a b = [(a, Handler b)] -> Handler b -> Handler b
+
+{- |
+Dispatch on an arbitrary part of the context using an arbitrary predicate. When
+the predicate returns true on the value selected with the `Label` the first
+handler will be invoked, otherwise the second handler will be used.
+-}
+
+hDispatch :: (Show a, Show c) => Label Context c -> (a -> c -> Bool) -> Dispatcher a b
+hDispatch f match a handler _default = do
+  ctx <- getM f
+  if a `match` ctx
+    then handler
+    else _default
+
+{- |
+Turns a dispatcher function into a list dispatcher. This enables handler
+routing based on arbitrary values from the context. When non of the predicates
+in the `ListDispatcher` type hold the default handler will be invoked.
+-}
+
+hListDispatch :: Dispatcher a b -> ListDispatcher a b
+hListDispatch disp = flip $ foldr $ uncurry disp
+
diff --git a/src/Network/Salvia/Handler/Environment.hs b/src/Network/Salvia/Handler/Environment.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Environment.hs
@@ -0,0 +1,78 @@
+module Network.Salvia.Handler.Environment (
+    hDefaultEnv
+  , hSessionEnv
+  ) where
+
+import Control.Concurrent.STM
+import Network.Protocol.Http
+import Network.Salvia.Handler.Banner
+import Network.Salvia.Handler.Counter
+import Network.Salvia.Handler.Close
+import Network.Salvia.Handler.Error
+import Network.Salvia.Handler.Head
+import Network.Salvia.Handler.Log
+import Network.Salvia.Handler.Parser
+import Network.Salvia.Handler.Printer
+import Network.Salvia.Handler.Session
+import Network.Salvia.Httpd
+import System.IO
+
+{- |
+This is the default stateless handler evnironment. It takes care of request
+parsing (`hParser`), response printing (`hPrinter`), request logging (`hLog`),
+connection keep-alives (`hKeepAlive`), handling `HEAD` requests (`hHead`) and
+printing the `salvia-httpd` server banner (`hBanner`).
+-}
+
+hDefaultEnv
+  :: Handler ()  -- ^ Handler to run in the default environment.
+  -> Handler ()
+hDefaultEnv handler =
+  hKeepAlive $ 
+    hParser (1000 * 15)
+      (wrapper Nothing . parseError)
+      (wrapper Nothing $ hHead handler)
+
+{- |
+This function is a more advanced version of the `hDefaultEnv` handler
+environment that takes a global state into account. It takes a shared variable
+containg the connection counter (used by `hCounter`) and a variable containing
+all session information (used by `hSession`). Handlers that run in this
+environment take should be parametrized with a session.
+-}
+
+hSessionEnv
+  :: TVar Int             -- ^ Request count variable.
+  -> Sessions a           -- ^ Session collection variable.
+  -> SessionHandler a ()  -- ^ Handler parametrized with current session.
+  -> Handler ()
+hSessionEnv count sessions handler =
+  hKeepAlive $ 
+    hParser (1000 * 15)
+     (wrapper (Just count) . parseError)
+     (wrapper (Just count) $
+       do session <- hSession sessions 300
+          hHead (handler session))
+
+-- Helper functions.
+
+before :: Handler ()
+before = hBanner "salvia-httpd"
+
+after :: Maybe (TVar Int) -> Handler ()
+after mc = 
+  do hPrinter
+     maybe
+       (hLog stdout)
+       (\c -> hCounter c >> hLogWithCounter c stdout)
+       mc
+
+wrapper :: Maybe (TVar Int) -> Handler a -> Handler ()
+wrapper c h = before >> h >> after c
+
+parseError :: String -> Handler ()
+parseError err = 
+  do hError BadRequest
+     sendStrLn []
+     sendStrLn err
+
diff --git a/src/Network/Salvia/Handler/Error.hs b/src/Network/Salvia/Handler/Error.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Error.hs
@@ -0,0 +1,58 @@
+module Network.Salvia.Handler.Error (
+    hError
+  , hCustomError
+  , hIOError
+  , safeIO
+  ) where
+
+import Control.Monad.State
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Salvia.Httpd
+import System.IO.Error
+
+{- |
+The 'hError' handler enables the creation of a default style of error responses
+for the specified HTTP `Status` code.
+-}
+
+hError :: Status -> Handler ()
+hError e = hCustomError e
+  (concat ["[", show (codeFromStatus e), "] ", show e, "\n"])
+
+{- | Like `hError` but with a custom error message. -}
+
+hCustomError :: Status -> String -> Handler ()
+hCustomError e m = do
+  enterM response $ do
+    setM status e
+    setM contentLength (Just $ length m)
+    setM contentType ("text/plain", Nothing)
+  sendStr m
+
+{- |
+Map an `IOError` to a default style error response.
+
+The mapping from an IO error to an error response is rather straightforward:
+
+>  | isDoesNotExistError e = hError NotFound
+>  | isAlreadyInUseError e = hError ServiceUnavailable
+>  | isPermissionError   e = hError Forbidden
+>  | True                  = hError InternalServerError
+-}
+
+hIOError :: IOError -> Handler ()
+hIOError e
+  | isDoesNotExistError e = hError NotFound
+  | isAlreadyInUseError e = hError ServiceUnavailable
+  | isPermissionError   e = hError Forbidden
+  | True                  = hError InternalServerError
+
+{- |
+Execute an handler with the result of an IO action. When the IO actions fails a
+default error handler will be executed.
+-}
+
+safeIO :: IO a -> (a -> Handler ()) -> Handler ()
+safeIO io h = lift (try io) >>= either hIOError h
+
diff --git a/src/Network/Salvia/Handler/ExtensionDispatcher.hs b/src/Network/Salvia/Handler/ExtensionDispatcher.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/ExtensionDispatcher.hs
@@ -0,0 +1,21 @@
+module Network.Salvia.Handler.ExtensionDispatcher (
+    hExtension
+  , hExtensionRouter
+  ) where
+
+import Data.Record.Label
+import Network.Protocol.Http (uri)
+import Network.Protocol.Uri (extension, path)
+import Network.Salvia.Handler.Dispatching
+import Network.Salvia.Httpd (request)
+
+{- | Request dispatcher based on the request path file extenstion. -}
+
+hExtension :: Dispatcher (Maybe String) a
+hExtension = hDispatch (extension % path % uri % request) (==)
+
+{- | List dispatcher version of `hExtension`. -}
+
+hExtensionRouter :: ListDispatcher (Maybe String) a
+hExtensionRouter = hListDispatch hExtension
+
diff --git a/src/Network/Salvia/Handler/Fallback.hs b/src/Network/Salvia/Handler/Fallback.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Fallback.hs
@@ -0,0 +1,33 @@
+module Network.Salvia.Handler.Fallback (hOr, hEither) where
+
+import Control.Applicative
+import Control.Monad.State
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Salvia.Httpd
+
+{- |
+Until I figure out how to do this correctly, this handler somewow implements
+the MonadPlus/Alternative instance using a custom function.
+
+When the first handler fails the response will be reset and the second handler
+is executed. Failure is indicated by an HTTP response `Status` bigger than or
+equal to 400 (`BadRequest`). See `statusFailure`.
+-}
+
+hOr :: Handler a -> Handler a -> Handler a
+hOr h0 h1 = do
+  a  <- h0
+  st <- getM (status % response)
+  if statusFailure st
+    then reset >> h1
+    else return a
+
+{- |
+Like the `hOr` function, but the types of the alternatives may differ because
+the values are packed up in an `Either`.
+-}
+
+hEither :: Handler a -> Handler b -> Handler (Either a b)
+hEither h0 h1 = (Left <$> h0) `hOr` (Right <$> h1)
+
diff --git a/src/Network/Salvia/Handler/File.hs b/src/Network/Salvia/Handler/File.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/File.hs
@@ -0,0 +1,88 @@
+module Network.Salvia.Handler.File (
+    hFile
+  , hFileResource
+
+  , hFileFilter
+  , hFileResourceFilter
+
+  , hResource
+  , hUri
+  ) where
+
+import Control.Monad.State
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Protocol.Mime
+import Network.Protocol.Uri (mimetype, path, parseURI)
+import Network.Salvia.Handler.Error
+import Network.Salvia.Httpd
+import System.IO
+
+{- |
+Serve a file from the filesystem indicated by the specified filepath. When
+there is some kind of `IOError` the `safeIO` function will be used to produce a
+corresponding error response. The `contentType` will be the mime-type based on
+the filename extension using the `mimetype` function. The `contentLength` will
+be set the file's size.
+-}
+
+-- TODO: what to do with encoding?
+hFileResource :: ResourceHandler ()
+hFileResource file = do
+  let m = maybe defaultMime id $ (parseURI file >>= mimetype . lget path)
+  safeIO (openBinaryFile file ReadMode)
+    $ \fd -> do
+      fs <- lift $ hFileSize fd
+      enterM response $ do
+        setM contentType (m, Just "utf-8")
+        setM contentLength (Just fs)
+        setM status OK
+      spoolBs id fd
+
+{- |
+Like the `hFileResource` handler, but with a custom filter over the content.
+This function will assume the content is an UTF-8 encoded text file. No
+`contentLength` header will be set using this handler.
+-}
+
+-- TODO: what to do with encoding?
+hFileResourceFilter :: (String -> String) -> ResourceHandler ()
+hFileResourceFilter fFilter file = do  -- TODO... this should be a more general hFilter
+  let m = maybe defaultMime id $ (parseURI file >>= mimetype . lget path)
+  safeIO (openBinaryFile file ReadMode)
+    $ \fd -> do
+      enterM response $ do
+        setM contentType (m, Just "utf-8")
+        setM status OK
+      spool fFilter fd
+
+{- |
+Turn a resource handler into a regular handler that utilizes the path part of
+the request URI as the resource identifier.
+-}
+
+hResource :: ResourceHandler a -> Handler a
+hResource rh = getM (path % uri % request) >>= rh
+
+{- |
+Turn a URI handler into a regular handler that utilizes the request URI as the
+resource identifier.
+-}
+
+hUri :: UriHandler a -> Handler a
+hUri rh = getM (uri % request) >>= rh
+
+{- |
+Like `hFileResource` but uses the path of the current request URI.
+-}
+
+hFile :: Handler ()
+hFile = hResource hFileResource
+
+{- |
+Like `hFileResourceFilter` but uses the path of the current request URI.
+-}
+
+hFileFilter :: (String -> String) -> Handler ()
+hFileFilter = hResource . hFileResourceFilter
+
diff --git a/src/Network/Salvia/Handler/FileSystem.hs b/src/Network/Salvia/Handler/FileSystem.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/FileSystem.hs
@@ -0,0 +1,56 @@
+module Network.Salvia.Handler.FileSystem (
+    hFileSystem
+  , hFileSystemNoIndexes
+  , hFileTypeDispatcher
+  ) where
+
+import Control.Monad.State
+import Data.Record.Label
+import Misc.Misc (bool)
+import Network.Protocol.Http
+import Network.Protocol.Uri (jail, path)
+import Network.Salvia.Handler.Directory
+import Network.Salvia.Handler.Error
+import Network.Salvia.Handler.File
+import Network.Salvia.Httpd
+import System.Directory (doesDirectoryExist)
+
+{- |
+Dispatch based on file type; regular files or directories. The first handler
+specified will be invoked in case the resource to be served is an directory,
+the second handler otherwise. The path from the request URI will be appended to
+the directory resource specified as a parameter, this new path will be used to
+lookup the real resource on the file system. Every request will be jailed in
+the specified directory resource to prevent users from requesting arbitrary
+parts of the file system.
+-}
+
+hFileTypeDispatcher :: ResourceHandler () -> ResourceHandler () -> ResourceHandler () 
+hFileTypeDispatcher hdir hfile dir =
+  getM (path % uri % request) >>=
+  hJailedDispatch dir hdir hfile . (dir ++)
+
+{- |
+Serve single directory by combining the `hDirectoryResource` and
+`hFileResource` handlers in the `hFileTypeDispatcher`.
+-}
+
+hFileSystem :: ResourceHandler ()
+hFileSystem = hFileTypeDispatcher hDirectoryResource hFileResource
+
+{- |
+Serve single directory like `hFileSystem` but do not show directory indices.
+Instead of an directory index an `Forbidden` response will be created.
+-}
+
+-- Show file contents, do not show directory indexes.
+hFileSystemNoIndexes :: ResourceHandler ()
+hFileSystemNoIndexes = hFileTypeDispatcher (const $ hError Forbidden) hFileResource
+
+hJailedDispatch :: FilePath -> ResourceHandler () -> ResourceHandler () -> ResourceHandler () 
+hJailedDispatch dir hdir hfile file = do
+  case jail dir file of
+    Nothing -> hError Forbidden
+    Just f  -> bool (hdir file) (hfile file)
+           =<< lift (doesDirectoryExist f) 
+
diff --git a/src/Network/Salvia/Handler/Head.hs b/src/Network/Salvia/Handler/Head.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Head.hs
@@ -0,0 +1,23 @@
+module Network.Salvia.Handler.Head (hHead) where
+
+import Control.Applicative
+import Control.Monad.State (put)
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Salvia.Httpd
+
+{- |
+The 'hHead' handler makes sure no response body is sent to the client when the
+request is an HTTP 'HEAD' request. In the case of a 'HEAD' request the
+specified sub handler will be executed under the assumption that the request
+was a 'GET' request, otherwise this handler will act as the identify function.
+-}
+
+hHead :: Handler a -> Handler a
+hHead handler = do
+  m <- getM (method % request)
+  case m of
+    HEAD -> withM (method % request) (put GET) $
+              handler <* emptyQueue
+    _    -> handler
+
diff --git a/src/Network/Salvia/Handler/Log.hs b/src/Network/Salvia/Handler/Log.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Log.hs
@@ -0,0 +1,47 @@
+module Network.Salvia.Handler.Log (
+    hLog
+  , hLogWithCounter
+  ) where
+
+import Control.Concurrent.STM
+import Control.Monad.State
+import Data.Record.Label
+import Misc.Terminal (red, green, reset)
+import Network.Protocol.Http
+import Network.Salvia.Httpd  (Handler, request, response, address)
+import System.IO
+
+{- |
+A simple logger that prints a summery of the request information to the
+specified file handle.
+-}
+
+hLog :: Handle -> Handler ()
+hLog = logger Nothing
+
+{- | Like `hLog` but also prints the request count since server startup. -}
+
+hLogWithCounter :: TVar Int -> Handle -> Handler ()
+hLogWithCounter a = logger (Just a)
+
+logger :: Maybe (TVar Int) -> Handle -> Handler ()
+logger count handle = do
+  c <- case count of
+    Nothing -> return ""
+    Just c' -> liftIO (show `liftM` atomically (readTVar c'))
+  mt   <- getM (method % request)
+  ur   <- getM (uri    % request)
+  st   <- getM (status % response)
+  addr <- getM address
+  let code = codeFromStatus st
+      clr  = if code >= 400 then red else green
+  liftIO $ hPutStrLn handle $ concat [
+      concat ["[", show addr, "] ", c, "\t"]
+    , show mt, "\t"
+    , show ur, " -> "
+    , clr
+    , show code, " "
+    , show st
+    , reset
+    ]
+
diff --git a/src/Network/Salvia/Handler/Login.hs b/src/Network/Salvia/Handler/Login.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Login.hs
@@ -0,0 +1,276 @@
+module Network.Salvia.Handler.Login (
+
+  -- * Basic types.
+    Username
+  , Password
+  , Action
+  , Actions
+  , User (..)
+  , Users
+  , UserDatabase
+  , TUserDatabase
+
+  -- * User Sessions.
+  , UserPayload (..)
+  , UserSession
+  , TUserSession
+  , UserSessionHandler
+
+  -- * Handlers.
+  , hSignup
+  , hLogin
+  , hLogout
+  , hLoginfo
+
+  , hAuthorized
+  , hAuthorizedUser
+
+  -- * Helper functions.
+  , readUserDatabase
+
+  ) where
+
+import Control.Concurrent.STM (TVar, atomically, readTVar, writeTVar, newTVar)
+import Control.Monad.State (lift, liftM)
+import Data.Digest.Pure.MD5 (md5)
+import Data.List (intercalate)
+import Data.Maybe (catMaybes)
+import Data.Record.Label
+import Misc.Misc (atomModTVar, safeHead)
+import Network.Protocol.Http (Status (Unauthorized, OK), status)
+import Network.Salvia.Handler.Error (hCustomError, hError)
+import Network.Salvia.Handler.Session
+import Network.Salvia.Httpd (sendStrLn, Handler, uriEncodedPostParamsUTF8, response)
+import Network.Protocol.Uri
+import Data.ByteString.Lazy.UTF8 (fromString)
+
+-------------------------------------------------------------------------------
+
+type Username = String
+type Password = String
+type Action   = String
+type Actions  = [Action]
+
+{- |
+User containg a username, password and a list of actions this user is allowed
+to perform within the system.
+-}
+
+data User = User {
+    username :: Username
+  , password :: Password
+  , email    :: String
+  , actions  :: Actions
+  } deriving (Eq, Show)
+
+type Users = [User]
+
+{- |
+A user database containing a list of users, a list of default actions the guest
+or `no-user' user is allowed to perform and a polymorphic reference to the
+place the database originates from. This source field can be used by update
+functions synchronizing changes back to the database.
+-}
+
+data UserDatabase src =
+  UserDatabase {
+    dbUsers  :: Users
+  , dbGuest  :: Actions
+  , dbSource :: src
+  } deriving Show
+
+type TUserDatabase src = TVar (UserDatabase src)
+
+{- |
+A user payload instance contains user related session information and can be
+used as the payload for regular sessions. It contains a reference to the user
+it is bound to, a flag to indicate whether the user is logged in or not and a
+possible user specific session payload.
+-}
+
+data UserPayload a = UserPayload {
+    upUser     :: User
+  , upLoggedIn :: Bool
+  , upPayload  :: Maybe a
+  } deriving (Eq, Show)
+
+type UserSession  a = Session  (UserPayload a)
+type TUserSession a = TSession (UserPayload a)
+
+{- | A handler that requires a session with a user specific payload. -}
+
+type UserSessionHandler a b = SessionHandler (UserPayload a) b
+
+-------------------------------------------------------------------------------
+
+{-|
+Read a user data from file. Format: /username password email action*/.
+-}
+
+readUserDatabase :: FilePath -> IO (TUserDatabase FilePath)
+readUserDatabase file = do
+
+  -- First line contains the default `guest` actions, tail lines contain users.
+  gst:ls <- lines `liftM` readFile file
+
+  atomically $ newTVar $ UserDatabase
+    (catMaybes $ map parseUserLine ls)
+    (words gst)
+    file
+  where
+    parseUserLine line =
+      case words line of
+        user:pass:mail:acts -> Just (User user pass mail acts)
+        _                   -> Nothing
+
+printUserLine :: User -> String
+printUserLine u = intercalate " " ([
+    username u
+  , password u
+  , email u
+  ] ++ actions u)
+
+{- |
+The signup handler is used to create a new entry in the user database. It reads
+a new username and password from the HTTP POST parameters and adds a new entry
+in the database when no user with such name exists. The user gets the specified
+initial set of actions assigned. On failure an `Unauthorized' error will be
+produced.
+-}
+
+hSignup :: TUserDatabase FilePath -> Actions -> Handler ()
+hSignup tdb acts = do
+  db <- lift . atomically $ readTVar tdb
+  params <- uriEncodedPostParamsUTF8
+  case freshUserInfo params (dbUsers db) acts of
+    Nothing -> hCustomError Unauthorized "signup failed"
+    Just u  -> do
+      lift $ do
+        atomically
+          $ writeTVar tdb
+          $ UserDatabase (u : dbUsers db) (dbGuest db) (dbSource db)
+        appendFile (dbSource db) (printUserLine u)
+
+freshUserInfo :: Maybe Parameters -> Users -> Actions -> Maybe User
+freshUserInfo params us acts = do
+  p <- params
+  user <- "username" `lookup` p >>= id
+  pass <- "password" `lookup` p >>= id
+  mail <- "email"    `lookup` p >>= id
+  case safeHead $ filter ((==user).username) us of
+    Nothing -> return $ User user (show $ md5 $ fromString pass) mail acts
+    Just _  -> Nothing
+
+-------------------------------------------------------------------------------
+
+{- |
+The login handler. Read the username and password values from the post data and
+use that to authenticate the user. When the user can be found in the database
+the user is logged in and stored in the session payload. Otherwise a
+`Unauthorized' response will be sent and the user has not logged in.
+-}
+
+hLogin :: UserDatabase b -> UserSessionHandler a ()
+hLogin db session = do
+  params <- uriEncodedPostParamsUTF8
+  maybe
+    (hCustomError Unauthorized "login failed")
+    (loginSuccessful session)
+    (authenticate params db)
+
+authenticate :: Maybe Parameters -> UserDatabase a -> Maybe User
+authenticate params db = do
+  p <- params
+  user <- "username" `lookup` p >>= id
+  pass <- "password" `lookup` p >>= id
+  case safeHead $ filter ((==user).username) (dbUsers db) of
+    Nothing -> Nothing
+    Just u  ->
+      if password u == (show $ md5 $ fromString pass)
+      then return u
+      else Nothing
+
+-- Login user and create `Ok' response on successful user.
+loginSuccessful :: TUserSession a -> User -> Handler ()
+loginSuccessful session user = do
+  lift $ atomModTVar (\s -> s {sPayload = Just (UserPayload user True Nothing)}) session
+  setM (status % response) OK
+  sendStrLn "login successful"
+
+-------------------------------------------------------------------------------
+
+{- | Logout the current user by emptying the session payload. -}
+
+hLogout :: TUserSession a -> Handler ()
+hLogout session = do
+  lift $ atomModTVar (\s -> s {sPayload = Nothing}) session
+  return ()
+
+-------------------------------------------------------------------------------
+
+{- |
+The `loginfo' handler exposes the current user session to the world using a
+simple text based file. The file contains information about the current session
+identifier, session start and expiration date and the possible user payload
+that is included.
+-}
+
+hLoginfo :: UserSessionHandler a ()
+hLoginfo session = do
+  s' <- lift $ atomically $ readTVar session
+
+  sendStrLn $ "sID="    ++ show (sID     s')
+  sendStrLn $ "start="  ++ show (sStart  s')
+  sendStrLn $ "expire=" ++ show (sExpire s')
+
+  case sPayload s' of
+    Nothing -> return ()
+    Just (UserPayload (User uname _ mail acts) _ _) -> do
+      sendStrLn $ "username=" ++ uname
+      sendStrLn $ "email="    ++ mail
+      sendStrLn $ "actions="  ++ intercalate " " acts
+
+-------------------------------------------------------------------------------
+
+{- |
+Execute a handler only when the user for the current session is authorized to
+do so. The user must have the specified action contained in its actions list in
+order to be authorized. Otherwise an `Unauthorized' error will be produced.
+When no user can be found in the current session or this user is not logged in
+the guest account from the user database is used for authorization.
+-}
+
+hAuthorized ::
+     UserDatabase b              -- ^ The user database to read guest account from.
+  -> Action                      -- ^ The actions that should be authorized.
+  -> (Maybe User -> Handler ())  -- ^ The handler to perform when authorized.
+  -> UserSessionHandler a ()     -- ^ This handler requires a user session.
+
+hAuthorized db action handler session = do
+  load <- liftM sPayload (lift $ atomically $ readTVar session)
+  case load of
+    Just (UserPayload user _ _)
+      | action `elem` actions user -> handler (Just user)
+    Nothing
+      | action `elem` dbGuest db   -> handler Nothing
+    _                              -> hError Unauthorized
+
+{- |
+Execute a handler only when the user for the current session is authorized to
+do so. The user must have the specified action contained in its actions list in
+order to be authorized. Otherwise an `Unauthorized' error will be produced. The
+guest user will not be used in any case.
+-}
+
+hAuthorizedUser ::
+     Action                      -- ^ The actions that should be authorized.
+  -> (User -> Handler ())        -- ^ The handler to perform when authorized.
+  -> UserSessionHandler a ()     -- ^ This handler requires a user session.
+
+hAuthorizedUser action handler session = do
+  load <- liftM sPayload (lift $ atomically $ readTVar session)
+  case load of
+    Just (UserPayload user _ _)
+      | action `elem` actions user -> handler user
+    _                              -> hError Unauthorized
+
diff --git a/src/Network/Salvia/Handler/MethodRouter.hs b/src/Network/Salvia/Handler/MethodRouter.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/MethodRouter.hs
@@ -0,0 +1,78 @@
+module Network.Salvia.Handler.MethodRouter (
+    hMethod
+  , hMethodRouter
+
+  , hOPTIONS
+  , hGET
+  , hHEAD
+  , hPOST
+  , hPUT
+  , hDELETE
+  , hTRACE
+  , hCONNECT
+  ) where
+
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Salvia.Handler.Dispatching
+import Network.Salvia.Handler.Error
+import Network.Salvia.Httpd
+
+{- |
+Request dispatcher based on the HTTP request `Method`. When the method does not
+match an HTTP `NotFound` will be created and `Nothing` will be returned.
+-}
+
+hMethod :: Method -> Handler a -> Handler (Maybe a)
+hMethod m h = hMethod' m
+  (Just `fmap` h)
+  (const Nothing `fmap` hError NotFound)
+
+{- | Request list dispatcher based on the `hMethod` dispatcher. -}
+
+hMethodRouter :: ListDispatcher Method ()
+hMethodRouter = hListDispatch hMethod'
+
+{- | Only invoke a handler on an `OPTIONS` request. -}
+
+hOPTIONS :: Handler a -> Handler (Maybe a)
+hOPTIONS = hMethod OPTIONS
+
+{- | Only invoke a handler on a `GET` request. -}
+
+hGET :: Handler a -> Handler (Maybe a)
+hGET = hMethod GET
+
+{- | Only invoke a handler on a `HEAD` request. -}
+
+hHEAD :: Handler a -> Handler (Maybe a)
+hHEAD = hMethod HEAD
+
+{- | Only invoke a handler on a `POST` request. -}
+
+hPOST :: Handler a -> Handler (Maybe a)
+hPOST = hMethod POST
+
+{- | Only invoke a handler on a `PUT` request. -}
+
+hPUT :: Handler a -> Handler (Maybe a)
+hPUT = hMethod PUT
+
+{- | Only invoke a handler on a `DELETE` request. -}
+
+hDELETE :: Handler a -> Handler (Maybe a)
+hDELETE = hMethod DELETE
+
+{- | Only invoke a handler on a `TRACE` request. -}
+
+hTRACE :: Handler a -> Handler (Maybe a)
+hTRACE = hMethod TRACE
+
+{- | Only invoke a handler on a `CONNECT` request. -}
+
+hCONNECT :: Handler a -> Handler (Maybe a)
+hCONNECT = hMethod CONNECT
+
+hMethod' :: Dispatcher Method a
+hMethod' = hDispatch (method % request) (==)
+
diff --git a/src/Network/Salvia/Handler/Parser.hs b/src/Network/Salvia/Handler/Parser.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Parser.hs
@@ -0,0 +1,51 @@
+module Network.Salvia.Handler.Parser (hParser) where
+
+import Control.Monad.State
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Salvia.Httpd
+import System.IO
+import System.Timeout
+
+{- |
+The 'hParser' handler is used to parse the raw request message into the
+'Message' data type. This handler is generally used as (one of) the first
+handlers in an environment. The first handler argument is executed when the
+request is invalid, possibly due to parser errors, and is parametrized with the
+error string. The second handler argument is executed when the request is
+valid. When the message could be parsed within the time specified with the
+first argument the function silently returns.
+-}
+
+hParser ::
+     Int                    -- ^ Timeout in milliseconds.
+  -> (String -> Handler ()) -- ^ The fail handler.
+  -> Handler ()             -- ^ The success handler.
+  -> Handler ()
+
+hParser t onfail onsuccess = do
+  h <- getM sock
+  -- TODO use try and fail with bad request or reject silently.
+  mMsg <- liftIO $ timeout (t * 1000) $
+    -- TODO: Using NoBuffering here may crash the entire program (GHC
+    -- runtime?) when processing more requests than just a few:
+    do hSetBuffering h (BlockBuffering (Just (64*1024)))
+       fmap Just (readHeader h) `catch` const (return Nothing)
+  case join mMsg of
+    Nothing -> return ()
+    Just msg -> 
+      do case parseRequest (msg "") of
+           Left err -> onfail (show err)
+           Right x -> do
+             setM request x
+             onsuccess
+
+-- Read all lines until the first empty line.
+readHeader :: Handle -> IO (String -> String)
+readHeader h = do
+  l <- hGetLine h
+  let lf = showChar '\n'
+  if l `elem` ["", "\r"]
+    then return lf
+    else liftM ((showString l . lf) .) (readHeader h)
+
diff --git a/src/Network/Salvia/Handler/PathRouter.hs b/src/Network/Salvia/Handler/PathRouter.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/PathRouter.hs
@@ -0,0 +1,45 @@
+module Network.Salvia.Handler.PathRouter (
+    hPath
+  , hPathRouter
+  , hPrefix
+  , hPrefixRouter
+
+  , hParameters
+  ) where
+
+import Control.Monad.State
+import Data.List (isPrefixOf)
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Protocol.Uri (path, queryParams, Parameters)
+import Network.Salvia.Handler.Dispatching
+import Network.Salvia.Httpd
+
+{- | Request dispatcher based on the request path. -}
+
+hPath :: Dispatcher String a
+hPath p h = hDispatch (path % uri % request) (==) p (chop p h)
+
+{- | List dispatcher version of `hPath`. -}
+
+hPathRouter :: ListDispatcher String b
+hPathRouter = hListDispatch hPath
+
+{- | Request dispatcher based on a prefix of the request path. -}
+
+hPrefix :: Dispatcher String a
+hPrefix p h = hDispatch (path % uri % request) isPrefixOf p (chop p h)
+
+{- | List dispatcher version of `hPrefix`. -}
+
+hPrefixRouter :: ListDispatcher String b
+hPrefixRouter = hListDispatch hPrefix
+
+{- | Helper function to fetch the URI parameters from the request. -}
+
+hParameters :: Handler Parameters
+hParameters = getM (uri % request) >>= return . queryParams 
+
+chop :: String -> Handler a -> Handler a
+chop a = withM (path % uri % request) (modify (drop $ length a))
+
diff --git a/src/Network/Salvia/Handler/Printer.hs b/src/Network/Salvia/Handler/Printer.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Printer.hs
@@ -0,0 +1,15 @@
+module Network.Salvia.Handler.Printer (hPrinter) where
+
+import Network.Salvia.Httpd
+
+{- |
+The 'hPrinter' handler print the entire response including the headers to the
+client. This handler is generally used as (one of) the last handler in a
+handler environment.
+-}
+
+hPrinter :: Handler ()
+hPrinter =
+  do flushHeaders
+     flushQueue
+
diff --git a/src/Network/Salvia/Handler/Put.hs b/src/Network/Salvia/Handler/Put.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Put.hs
@@ -0,0 +1,26 @@
+module Network.Salvia.Handler.Put (hPut) where
+
+import Control.Monad.State
+import Network.Protocol.Http
+import Network.Salvia.Handler.Error
+import Network.Salvia.Httpd
+import System.IO
+import qualified Data.ByteString.Lazy as B
+
+{- |
+First naive handler for the HTTP `PUT` request. This probably does not handle
+all the quirks that the HTTP protocol specifies, but it does the job for now.
+When a 'contentLength' header field is available only this fixed number of
+bytes will be spooled from socket to the resource. When both the `keepAlive'
+and 'contentLength' header fields are not available the entire payload of the
+request is spooled to the resource.
+-}
+
+hPut :: ResourceHandler ()
+hPut name =
+  safeIO (openBinaryFile name WriteMode)
+    $ (contents >>=) . maybe putError . putOk
+  where
+    putError   = hError NotImplemented
+    putOk fd c = lift (B.hPut fd c >> hClose fd)
+
diff --git a/src/Network/Salvia/Handler/Redirect.hs b/src/Network/Salvia/Handler/Redirect.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Redirect.hs
@@ -0,0 +1,18 @@
+module Network.Salvia.Handler.Redirect (hRedirect) where
+
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Protocol.Uri (URI)
+import Network.Salvia.Httpd
+
+{- |
+Redirect a client to another location by creating a `MovedPermanently` response
+message with the specified `URI` in the `location' header.
+-}
+
+hRedirect :: URI -> Handler ()
+hRedirect u =
+  enterM response $ do
+    setM location (Just u)
+    setM status MovedPermanently
+
diff --git a/src/Network/Salvia/Handler/Rewrite.hs b/src/Network/Salvia/Handler/Rewrite.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Rewrite.hs
@@ -0,0 +1,60 @@
+module Network.Salvia.Handler.Rewrite (
+    hRewrite
+  , hRewritePath
+  , hRewriteHost
+  , hRewriteExt
+  , hWithDir
+  , hWithoutDir
+  ) where
+
+import Control.Monad.State
+import Data.List (isPrefixOf)
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Protocol.Uri (URI, host, path, extension)
+import Network.Salvia.Httpd
+
+{- |
+Run an handler in a modified context in which the request `URI` has been
+changed by the specified modifier function. After the handler completes the `URI`
+remains untouched.
+-}
+
+hRewrite :: (URI -> URI) -> Handler a -> Handler a
+hRewrite f = withM (uri % request) (modify f)
+
+-- express these below in terms of the above?
+
+{- | Run handler in a context with a modified host. -}
+
+hRewriteHost :: (String -> String) -> Handler a -> Handler a
+hRewriteHost f = withM (host % uri % request) (modify f)
+
+{- | Run handler in a context with a modified path. -}
+
+hRewritePath :: (String -> String) -> Handler a -> Handler a
+hRewritePath f = withM (path % uri % request) (modify f)
+
+{- | Run handler in a context with a modified file extension. -}
+
+hRewriteExt :: (Maybe String -> Maybe String) -> Handler a -> Handler a
+hRewriteExt f = withM (extension % path % uri % request) (modify f)
+
+{- |
+Run handler in a context with a modified path. The specified prefix will be
+prepended to the path.
+-}
+
+hWithDir :: String -> Handler a -> Handler a
+hWithDir d = hRewritePath (d++)
+
+{- |
+Run handler in a context with a modified path. The specified prefix will be
+stripped form the path.
+-}
+
+hWithoutDir :: String -> Handler a -> Handler a
+hWithoutDir d h = do
+  p <- getM (path % uri % request)
+  (if d `isPrefixOf` p then hRewritePath (drop $ length d) else id) h
+
diff --git a/src/Network/Salvia/Handler/Session.hs b/src/Network/Salvia/Handler/Session.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/Session.hs
@@ -0,0 +1,192 @@
+module Network.Salvia.Handler.Session (
+    hSession
+
+  , SessionID
+  , Session (..)
+  , TSession
+  , SessionHandler
+  , Sessions
+
+  , mkSessions
+  ) where
+
+import Control.Applicative hiding (empty)
+import Control.Concurrent.STM
+import Control.Monad.State
+import Data.Time.LocalTime
+import Misc.Misc (safeRead, atomModTVar, atomReadTVar, now, later)
+import Network.Protocol.Cookie hiding (empty)
+import Network.Salvia.Handler.Cookie
+import Network.Salvia.Httpd
+import Prelude hiding (lookup)
+import System.Random
+import qualified Data.Map as M
+
+-------------------------------------------------------------------------------
+
+{- | A session identifier. Should be unique for every session. -}
+
+newtype SessionID = SID Integer
+  deriving (Eq, Ord)
+
+{- | The session data type with polymorphic payload. -}
+
+data Session a = Session {
+    sID      :: SessionID  -- ^ A globally unique session identifier.
+  , sStart   :: LocalTime  -- ^ The time the session started.
+  , sExpire  :: LocalTime  -- ^ The time after which the session is expired.
+  , sPayload :: Maybe a    -- ^ The information this session stores.
+  } deriving Show
+
+{- | A shared session. -}
+
+type TSession a = TVar (Session a)
+
+{- | A handler that expects a session. -}
+
+type SessionHandler a b = TSession a -> Handler b
+
+{- | Create a new, empty, shared session. -}
+
+mkSession :: SessionID -> LocalTime -> IO (TSession a)
+mkSession sid e = do
+  s <- now
+  atomically $ newTVar $ Session sid s e Nothing
+
+-------------------------------------------------------------------------------
+
+{- | A mapping from unique session IDs to shared session variables. -}
+
+type Sessions a = TVar (M.Map SessionID (TSession a))
+
+instance Show SessionID where
+  show (SID sid) = show sid
+
+{- | Create a new, empty, store of sessions. -}
+
+mkSessions :: IO (Sessions a)
+mkSessions = atomically $ newTVar M.empty
+
+-------------------------------------------------------------------------------
+
+{- |
+The session handler. This handler will try to return an existing session from
+the sessions map based on a session identifier found in the HTTP `cookie`. When
+such a session can be found the expiration date will be updated to a number of
+seconds in the future. When no session can be found a new one will be created.
+A cookie will be set that informs the client of the current session.
+-}
+
+hSession
+  :: Sessions a            -- ^ Map of shared session variables.
+  -> Integer               -- ^ Number of seconds to be added to the session expiritation time.
+  -> Handler (TSession a)
+hSession smap expiration = do
+
+  -- Get the session identifier from an existing cookie or create a new one.
+  prev <- getSessionID <$> hGetCookies
+
+  -- Compute current time and expiration time.
+  (n, ex) <- lift $ liftM2 (,) now (later expiration)
+
+  -- Either create a new session or try to reuse current one.
+  tsession <- 
+    maybe
+    (newSession smap ex)
+    (existingSession smap ex n)
+    prev
+
+  setSessionCookie tsession ex
+  return tsession
+
+-------------------------------------------------------------------------------
+
+{-
+Given the (possible wrong) request cookie, try to recover the existing --
+session identifier.
+-}
+
+getSessionID :: Maybe Cookies -> Maybe SessionID
+getSessionID prev = do
+  ck  <- prev
+  sid <- cookie "sid" ck
+  sid' <- safeRead $ value sid
+  return (SID sid')
+  
+{-
+Generate a fresh, random session identifier using the default system random
+generator.
+-}
+
+genSessionID :: IO SessionID
+genSessionID = do
+  g <- getStdGen
+  let (sid, g') = random g
+  setStdGen g'
+  return (SID (abs sid))
+
+
+{-
+This handler sets the HTTP cookie for the specified session. It will use a
+default cookie with an additional `sid' attribute with the session identifier
+as value. The session expiration date will be used as the cookie expire field.
+-}
+
+setSessionCookie :: TSession a -> LocalTime -> Handler ()
+setSessionCookie tsession ex = do
+  ck <- newCookie ex
+  sid <- lift $ liftM sID $ atomReadTVar tsession
+  hSetCookies $
+    cookies [ck {
+      name  = "sid"
+    , value = show sid
+    }]
+
+{-
+Handler when no (valid) session is available. Create a new session with a
+specified expiration date. The session will be stored in the session map.
+-}
+
+newSession :: Sessions a -> LocalTime -> Handler (TSession a)
+newSession sessions ex = lift $ do
+
+  -- Fresh session identifier.
+  sid <- genSessionID
+
+  -- Fresh session.
+  session <- mkSession sid ex
+
+  -- Place in session mapping usinf session identifier as key.
+  atomModTVar (M.insert sid session) sessions
+  return session
+
+{-
+Handler for existing sessions. Given an existing session identifier lookup a
+session from the session map. When no session is available, or the session is
+expired, create a new one using the `newSession' function. Otherwise the
+expiration date of the existing session is updated.
+-}
+
+existingSession ::
+     Sessions a -> LocalTime -> LocalTime
+  -> SessionID -> Handler (TSession a)
+existingSession sessions ex n sid = do
+
+  -- Lookup the session in the session map given the session identifier.
+  mtsession <- lift $ liftM (M.lookup sid) (atomReadTVar sessions)
+  case mtsession of
+
+    -- Unrecognized session identifiers are penalised by a fresh session.
+    Nothing -> newSession sessions ex
+    Just tsession -> do
+      expd <- lift $ liftM sExpire (atomReadTVar tsession)
+      if expd < n
+
+        -- Session is expired, create a new one.
+        then newSession sessions ex
+
+        -- Existing session, update expiration date.
+        else lift $ do
+          atomModTVar (\s -> s {sExpire = ex}) tsession
+          return tsession
+
diff --git a/src/Network/Salvia/Handler/VirtualHosting.hs b/src/Network/Salvia/Handler/VirtualHosting.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handler/VirtualHosting.hs
@@ -0,0 +1,34 @@
+module Network.Salvia.Handler.VirtualHosting (
+    hHostRouter
+  , hVirtualHosting
+  ) where
+
+import Data.Ord
+import Data.List
+import Data.Record.Label
+import Network.Protocol.Http
+import Network.Protocol.Uri
+import Network.Salvia.Handler.Dispatching
+import Network.Salvia.Httpd hiding (hostname)
+
+{- |
+List dispatcher based on the host part of the hostname request header.
+Everything not part of the real hostname (like the port number) will be
+ignored.
+-}
+
+hVirtualHosting :: ListDispatcher String a
+hVirtualHosting = (hListDispatch disp) . parse
+  where
+    disp    = hDispatch (hostname % request) cmp
+    parse   = map (\(a, b) -> (parseAuthority a, b))
+    cmp a b = (==EQ) $ comparing (fmap (lget _host)) a b
+
+{- |
+List dispatcher based on the hostname request header. This header field is
+parsed and interpreted as an `Authority` field.
+-}
+
+hHostRouter :: ListDispatcher (Maybe Authority) a
+hHostRouter = hListDispatch $ hDispatch (hostname % request) (==)
+
diff --git a/src/Network/Salvia/Handlers.hs b/src/Network/Salvia/Handlers.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Salvia/Handlers.hs
@@ -0,0 +1,213 @@
+module Network.Salvia.Handlers (
+
+  -- * Fundamental protocol handlers.
+
+    -- ** Default handler environments.
+
+      hDefaultEnv
+    , hSessionEnv
+
+    -- ** Parse client requests.
+
+    , hParser
+
+    -- ** Print server responses.
+
+    , hPrinter
+
+    -- ** HTTP header banner.
+
+    , hBanner
+
+    -- ** Closing or keeping alive connections.
+
+    , hCloseConn
+    , hKeepAlive
+
+    -- ** Enable HTTP HEAD requests.
+    
+    , hHead
+
+  -- * Error handling and logging.
+
+    -- ** Default error handlers.
+
+    , hError
+    , hCustomError
+    , hIOError
+    , safeIO
+
+    -- ** Logging of client requests.
+
+    , hLog
+    , hLogWithCounter
+
+    -- ** Fallback handlers.
+
+    , hOr
+    , hEither
+
+    -- ** Request counter.
+
+    , hCounter
+
+  -- * Redirecting and rewriting.
+
+    -- ** Redirecting the client.
+
+    , hRedirect
+
+    -- ** Request URI rewriting.
+
+    , hRewrite
+    , hRewriteHost
+    , hRewritePath
+    , hRewriteExt
+    , hWithDir
+    , hWithoutDir
+
+  -- * File and directory serving.
+
+    -- ** Serve static file resources.
+
+    , hFileResource
+    , hFileResourceFilter
+    , hResource
+    , hUri
+    , hFile
+    , hFileFilter
+
+    -- ** Serve directory indices.
+
+    , hDirectory
+    , hDirectoryResource
+
+    -- ** Serve file system directory.
+
+    , hFileTypeDispatcher
+    , hFileSystem
+    , hFileSystemNoIndexes
+
+    -- ** Enable PUTing resources to the files ystem.
+
+    , hPut
+
+    -- ** Serving CGI scripts.
+
+    , hCGI
+
+  -- * Dispatching.
+
+    -- ** Custom request dispatchers.
+
+    , Dispatcher
+    , ListDispatcher
+    , hDispatch
+    , hListDispatch
+
+    -- ** Dispatch based on request method.
+
+    , hMethod
+    , hMethodRouter
+
+    , hOPTIONS
+    , hGET
+    , hHEAD
+    , hPOST
+    , hPUT
+    , hDELETE
+    , hTRACE
+    , hCONNECT
+
+    -- ** Dispatch based on request path.
+
+    , hPath
+    , hPathRouter
+    , hPrefix
+    , hPrefixRouter
+    , hParameters
+
+    -- ** Dispatch based on filename extension.
+
+    , hExtension
+    , hExtensionRouter
+
+    -- ** Dispatch based on host name.
+
+    , hHostRouter
+    , hVirtualHosting
+
+  -- * Session and user management.
+
+    -- ** Cookie handling.
+
+    , hSetCookies
+    , hGetCookies
+    , newCookie
+
+    -- ** Session management.
+
+    , hSession
+
+    , SessionID
+    , Session (..)
+    , TSession
+    , SessionHandler
+    , Sessions
+
+    , mkSessions
+
+    -- ** User management.
+
+    , Username
+    , Password
+    , Action
+    , Actions
+    , User (..)
+    , Users
+    , UserDatabase
+    , TUserDatabase
+
+    , UserPayload (..)
+    , UserSession
+    , TUserSession
+    , UserSessionHandler
+
+    , hSignup
+    , hLogin
+    , hLogout
+    , hLoginfo
+
+    , hAuthorized
+    , hAuthorizedUser
+
+    , readUserDatabase
+
+  ) where
+
+import Network.Salvia.Handler.Banner
+import Network.Salvia.Handler.CGI
+import Network.Salvia.Handler.Close
+import Network.Salvia.Handler.Cookie
+import Network.Salvia.Handler.Counter
+import Network.Salvia.Handler.Directory
+import Network.Salvia.Handler.Dispatching
+import Network.Salvia.Handler.Environment
+import Network.Salvia.Handler.Error
+import Network.Salvia.Handler.ExtensionDispatcher
+import Network.Salvia.Handler.Fallback
+import Network.Salvia.Handler.File
+import Network.Salvia.Handler.FileSystem
+import Network.Salvia.Handler.Head
+import Network.Salvia.Handler.Log
+import Network.Salvia.Handler.Login
+import Network.Salvia.Handler.MethodRouter
+import Network.Salvia.Handler.Parser
+import Network.Salvia.Handler.PathRouter
+import Network.Salvia.Handler.Printer
+import Network.Salvia.Handler.Put
+import Network.Salvia.Handler.Redirect
+import Network.Salvia.Handler.Rewrite
+import Network.Salvia.Handler.Session
+import Network.Salvia.Handler.VirtualHosting
+
diff --git a/src/Network/Salvia/Handlers/Banner.hs b/src/Network/Salvia/Handlers/Banner.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Banner.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Network.Salvia.Handlers.Banner (hBanner) where
-
-import Control.Monad.State
-import Data.Record.Label
-import Data.Time.Clock (getCurrentTime)
-import Data.Time.Format (formatTime)
-import Data.Time.LocalTime (getCurrentTimeZone, utcToLocalTime)
-import Network.Protocol.Http
-import Network.Salvia.Httpd
-import System.Locale (defaultTimeLocale)
-
--- | The 'hBanner' handler adds the current date-/timestamp and a custom server
--- name to the response headers.
-
-hBanner ::
-     String     -- ^ The HTTP server name.
-  -> Handler ()
-hBanner sv = do
-  dt <- lift $ do
-    zone <- getCurrentTimeZone
-    time <- liftM (utcToLocalTime zone) getCurrentTime
-    return $ formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %Z" time
-  enterM response $ do
-    setM date   dt
-    setM server sv
-
diff --git a/src/Network/Salvia/Handlers/CGI.hs b/src/Network/Salvia/Handlers/CGI.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/CGI.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-module Network.Salvia.Handlers.CGI (hCGI) where
-
-import Control.Monad.State
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Salvia.Httpd
-import System.IO
-import System.Process (runProcess, waitForProcess)
-
-hCGI :: FilePath -> Handler ()
-hCGI name = do
-  setM (status % response) OK
-  h <- getM sock
-  lift $ do
-    p <- runProcess name [] Nothing Nothing (Just h) (Just h) (Just stderr)
-    waitForProcess p
-    return ()
-
diff --git a/src/Network/Salvia/Handlers/Cookie.hs b/src/Network/Salvia/Handlers/Cookie.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Cookie.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-module Network.Salvia.Handlers.Cookie (
-    hSetCookies
-  , hGetCookies
-
-  , newCookie
-  ) where
-
-import Control.Applicative hiding (empty)
-import Control.Monad.State
-import Data.Record.Label
-import Data.Time.Format
-import Data.Time.LocalTime
-import Network.Protocol.Cookie (showCookies, Cookie, Cookies, parseCookies, empty, path, port, expires)
-import Network.Protocol.Http (cookie)
-import Network.Salvia.Httpd
-import System.Locale (defaultTimeLocale)
-
-hSetCookies :: Cookies -> Handler ()
-hSetCookies = setM (cookie % response) . showCookies
-
-hGetCookies :: Handler (Maybe Cookies)
-hGetCookies = parseCookies <$> getM (cookie % request)
-
--- Convenient method for creating cookies that expire in the near future and
--- are bound to the domain and port this server runs on. The path will be
--- locked to root.
-newCookie :: LocalTime -> Handler Cookie
-newCookie expire = do
-  httpd <- getM config
-  return $ empty {
-      path    = Just "/"
---  , domain  = Just $ '.' : hostname httpd
-    , port    = [fromEnum $ listenPort httpd]
-    , expires = Just $ formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S %Z" expire
-    }
-
-
diff --git a/src/Network/Salvia/Handlers/Counter.hs b/src/Network/Salvia/Handlers/Counter.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Counter.hs
+++ /dev/null
@@ -1,10 +0,0 @@
-module Network.Salvia.Handlers.Counter (hCounter) where
-
-import Control.Concurrent.STM
-import Control.Monad.State
-import Network.Salvia.Httpd
-
-hCounter :: TVar Int -> Handler ()
-hCounter c = lift $ atomically
-  $ readTVar c >>= writeTVar c . (+1)
-
diff --git a/src/Network/Salvia/Handlers/Default.hs b/src/Network/Salvia/Handlers/Default.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Default.hs
+++ /dev/null
@@ -1,30 +0,0 @@
-module Network.Salvia.Handlers.Default (hDefault) where
-
-import Control.Concurrent.STM
-import Network.Protocol.Http
-import Network.Salvia.Handlers.Banner
-import Network.Salvia.Handlers.Counter
-import Network.Salvia.Handlers.Error
-import Network.Salvia.Handlers.Head
-import Network.Salvia.Handlers.Log
-import Network.Salvia.Handlers.Parser
-import Network.Salvia.Handlers.Printer
-import Network.Salvia.Handlers.Session
-import Network.Salvia.Httpd
-import System.IO
-
-hDefault :: Show a => TVar Int -> Sessions a -> SessionHandler a () -> Handler ()
-hDefault count sessions handler = do
-  hBanner "salvia-httpd"
-  hParser onerror $ do
-    session <- hSession sessions 300
-    hHead $ handler session
-  hPrinter
-  hLog count stdout
-  hCounter count
-  where
-    onerror err = do
-      hError BadRequest
-      sendStrLn []
-      sendStrLn err
-
diff --git a/src/Network/Salvia/Handlers/Directory.hs b/src/Network/Salvia/Handlers/Directory.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Directory.hs
+++ /dev/null
@@ -1,55 +0,0 @@
-module Network.Salvia.Handlers.Directory (
-    hDirectory
-  , hDirectoryResource
-  ) where
-
-import Control.Monad.State
-import Data.List (sort)
-import Data.Record.Label
-import Misc.Misc (bool)
-import Network.Protocol.Http
-import Network.Protocol.Uri (path)
-import Network.Salvia.Handlers.File (hResource)
-import Network.Salvia.Handlers.Redirect
-import Network.Salvia.Httpd
-import System.Directory (doesDirectoryExist, getDirectoryContents)
-
-hDirectory :: Handler ()
-hDirectory = hResource hDirectoryResource
-
-hDirectoryResource :: ResourceHandler ()
-hDirectoryResource dirName = do
-  (u, p) <- bothM (uri % request) (getM path)
-  if (null p) || last p /= '/'
-   then hRedirect (lmod path (++"/") u)
-   else dirHandler dirName
-
-dirHandler:: ResourceHandler ()
-dirHandler dirName = do
-  p <- getM (path % uri % request)
-  filenames <- lift $ getDirectoryContents dirName
-  processed <- lift $ mapM (processFilename dirName) (sort filenames)
-  let b = listing p processed
-  enterM response $ do
-    setM contentType ("text/html", Nothing)
-    setM contentLength (Just $ fromIntegral $ length b)
-    setM status OK
-  sendStr b
-
--- Add trailing slash to a directory name.
-processFilename :: FilePath -> FilePath -> IO FilePath
-processFilename d f = bool (f ++ "/") f `liftM` doesDirectoryExist (d ++ f)
-
--- Turn a list of filenames into HTML directory listing.
-listing :: FilePath -> [FilePath] -> String
-listing dirName fileNames =
-  concat [
-    "<html><head><title>Index of "
-  , dirName
-  , "</title></head><body><h1>Index of "
-  , dirName
-  , "</h1><ul>"
-  , fileNames >>= \f -> concat ["<li><a href='", f, "'>", f, "</a></li>"]
-  , "</ul></body></html>"
-  ]
-
diff --git a/src/Network/Salvia/Handlers/Dispatching.hs b/src/Network/Salvia/Handlers/Dispatching.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Dispatching.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-module Network.Salvia.Handlers.Dispatching (
-    Dispatcher
-  , ListDispatcher
-  , hDispatch
-  , hListDispatch
-  ) where
-
-import Data.Record.Label
-import Network.Salvia.Httpd
-
-type Dispatcher     a b = a -> Handler b   -> Handler b -> Handler b
-type ListDispatcher a b = [(a, Handler b)] -> Handler b -> Handler b
-
-hDispatch :: (Show a, Show c) => Label Context c -> (a -> c -> Bool) -> Dispatcher a b
-hDispatch f match a handler _default = do
-  ctx <- getM f
-  if a `match` ctx
-    then handler
-    else _default
-
-hListDispatch :: Dispatcher a b -> ListDispatcher a b
-hListDispatch disp = flip $ foldr $ uncurry disp
-
diff --git a/src/Network/Salvia/Handlers/Error.hs b/src/Network/Salvia/Handlers/Error.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Error.hs
+++ /dev/null
@@ -1,47 +0,0 @@
-module Network.Salvia.Handlers.Error (
-    hError
-  , hCustomError
-  , hIOError
-  , safeIO
-  ) where
-
-import Control.Monad.State
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Salvia.Httpd
-import System.IO.Error
-
-{- |
-The 'hError' handler enables the creation of a default style of error responses
-for the specified HTTP status code.
--}
-
-hError ::
-     Status     -- ^ The HTTP status code.
-  -> Handler ()
-hError e = hCustomError e
-  (concat ["[", show (codeFromStatus e), "] ", show e, "\n"])
-
-hCustomError ::
-     Status     -- ^ The HTTP status code.
-  -> String     -- ^ Custom error message.
-  -> Handler ()
-hCustomError e m = do
-  enterM response $ do
-    setM status e
-    setM contentType ("text/plain", Nothing)
-  sendStr m
-
--- | Map IO errors to a default style error response.
-hIOError :: IOError -> Handler ()
-hIOError e
-  | isDoesNotExistError e = hError NotFound
-  | isAlreadyInUseError e = hError ServiceUnavailable
-  | isPermissionError   e = hError Forbidden
-  | True                  = hError InternalServerError
-
--- | Execute an handler with the result of an IO action. When the IO actions
--- fails a default error handler will be executed.
-safeIO :: IO a -> (a -> Handler ()) -> Handler ()
-safeIO io h = lift (try io) >>= either hIOError h
-
diff --git a/src/Network/Salvia/Handlers/ExtensionDispatcher.hs b/src/Network/Salvia/Handlers/ExtensionDispatcher.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/ExtensionDispatcher.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-module Network.Salvia.Handlers.ExtensionDispatcher (
-    hExtension
-  , hExtensionRouter
-  ) where
-
-import Data.Record.Label
-import Network.Protocol.Http (uri)
-import Network.Protocol.Uri (extension, path)
-import Network.Salvia.Handlers.Dispatching
-import Network.Salvia.Httpd (request)
-
-hExtension :: Dispatcher (Maybe String) a
-hExtension = hDispatch (extension % path % uri % request) (==)
-
-hExtensionRouter :: ListDispatcher (Maybe String) a
-hExtensionRouter = hListDispatch hExtension
-
diff --git a/src/Network/Salvia/Handlers/Fallback.hs b/src/Network/Salvia/Handlers/Fallback.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Fallback.hs
+++ /dev/null
@@ -1,25 +0,0 @@
-module Network.Salvia.Handlers.Fallback (hOr, hEither) where
-
-import Control.Applicative
-import Control.Monad.State
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Salvia.Httpd
-
-{- |
-Until I figure out how to do this correctly, this handler somewow implements
-the MonadPlus/Alternative instance using a custom function. When the first
-handler fails the response will be reset and the second handler is executed.
--}
-
-hOr :: Handler a -> Handler a -> Handler a
-hOr h0 h1 = do
-  a  <- h0
-  st <- getM (status % response)
-  if statusFailure st
-    then reset >> h1
-    else return a
-
-hEither :: Handler a -> Handler b -> Handler (Either a b)
-hEither h0 h1 = (Left <$> h0) `hOr` (Right <$> h1)
-
diff --git a/src/Network/Salvia/Handlers/File.hs b/src/Network/Salvia/Handlers/File.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/File.hs
+++ /dev/null
@@ -1,69 +0,0 @@
-module Network.Salvia.Handlers.File (
-    hFile
-  , hFileResource
-
-  , hFileFilter
-  , hFileResourceFilter
-
-  , hResource
-  , hUri
-  ) where
-
-import Control.Monad.State
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Protocol.Mime
-import Network.Protocol.Uri (mimetype, path, parseURI)
-import Network.Salvia.Handlers.Error
-import Network.Salvia.Httpd
-import System.IO
-
-hFile :: Handler ()
-hFile = hResource hFileResource
-
-hFileFilter :: (String -> String) -> Handler ()
-hFileFilter = hResource . hFileResourceFilter
-
-{-
-Turn a resource handler into a regular handler that utilizes the path part
-of the request URI as the resource identifier.
--}
-
-hResource :: ResourceHandler a -> Handler a
-hResource rh = getM (path % uri % request) >>= rh
-
-{-
-Turn a URI handler into a regular handler that utilizes the request URI as the
-resource identifier.
--}
-
-hUri :: UriHandler a -> Handler a
-hUri rh = getM (uri % request) >>= rh
-
--------- HTTP deamon implementation -------------------------------------------
-
--- Create a response message containing the file contents.
--- TODO: what to do with encoding?
-hFileResource :: ResourceHandler ()
-hFileResource file = do
-  let m = maybe defaultMime id $ (parseURI file >>= mimetype . lget path)
-  safeIO (openBinaryFile file ReadMode)
-    $ \fd -> do
-      fs <- lift $ hFileSize fd
-      enterM response $ do
-        setM contentType (m, Just "utf-8")
-        setM contentLength (Just fs)
-        setM status OK
-      spoolBs id fd
-
--- TODO: what to do with encoding?
-hFileResourceFilter :: (String -> String) -> ResourceHandler ()
-hFileResourceFilter fFilter file = do  -- TODO... this should be a more general hFilter
-  let m = maybe defaultMime id $ (parseURI file >>= mimetype . lget path)
-  safeIO (openBinaryFile file ReadMode)
-    $ \fd -> do
-      enterM response $ do
-        setM contentType (m, Just "utf-8")
-        setM status OK
-      spool fFilter fd
-
diff --git a/src/Network/Salvia/Handlers/FileSystem.hs b/src/Network/Salvia/Handlers/FileSystem.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/FileSystem.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-module Network.Salvia.Handlers.FileSystem (
-    hFileSystem
-  , hFileSystemNoIndexes
-  , hFileTypeDispatcher
-  ) where
-
-import Control.Monad.State
-import Data.Record.Label
-import Misc.Misc (bool)
-import Network.Protocol.Http
-import Network.Protocol.Uri (jail, path)
-import Network.Salvia.Handlers.Directory
-import Network.Salvia.Handlers.Error
-import Network.Salvia.Handlers.File
-import Network.Salvia.Httpd
-import System.Directory (doesDirectoryExist)
-
--- Show file contents and show directory indexes.
-hFileSystem :: ResourceHandler ()
-hFileSystem dir = hFileTypeDispatcher dir hDirectoryResource hFileResource
-
--- Show file contents, do not show directory indexes.
-hFileSystemNoIndexes :: ResourceHandler ()
-hFileSystemNoIndexes dir = hFileTypeDispatcher dir (const $ hError Forbidden) hFileResource
-
--- Dispatch based on file type, regular files or directories.
-hFileTypeDispatcher :: FilePath -> ResourceHandler () -> ResourceHandler () -> Handler () 
-hFileTypeDispatcher dir hdir hfile =
-  getM (path % uri % request) >>=
-  hJailedDispatch dir hdir hfile . (dir ++)
-
-hJailedDispatch :: FilePath -> ResourceHandler () -> ResourceHandler () -> ResourceHandler () 
-hJailedDispatch dir hdir hfile file = do
-  case jail dir file of
-    Nothing -> hError Forbidden
-    Just f  -> bool (hdir file) (hfile file)
-           =<< lift (doesDirectoryExist f) 
-
diff --git a/src/Network/Salvia/Handlers/Head.hs b/src/Network/Salvia/Handlers/Head.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Head.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-module Network.Salvia.Handlers.Head (hHead) where
-
-import Control.Applicative
-import Control.Monad.State (put)
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Salvia.Httpd
-
-{- |
-The 'hHead' handler makes sure no response body is sent to the client when the
-request is an HTTP 'HEAD' request. In the case of a 'HEAD' request the
-specified sub handler will be executed under the assumption that the request
-was a 'GET' request, otherwise this handler will act as the identify function.
--}
-
-hHead :: Handler a -> Handler a
-hHead handler = do
-  m <- getM (method % request)
-  case m of
-    HEAD -> withM (method % request) (put GET) $
-              handler <* emptyQueue
-    _    -> handler
-
diff --git a/src/Network/Salvia/Handlers/Log.hs b/src/Network/Salvia/Handlers/Log.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Log.hs
+++ /dev/null
@@ -1,31 +0,0 @@
-module Network.Salvia.Handlers.Log (hLog) where
-
-import Control.Concurrent.STM
-import Control.Monad.State
-import Data.Record.Label
-import Misc.Terminal (red, green, reset)
-import Network.Protocol.Http
-import Network.Salvia.Httpd  (Handler, request, response, address)
-import System.IO
-
--- TODO: handle should be tvar as well
-hLog :: TVar Int -> Handle -> Handler Handle
-hLog count handle = do
-  c    <- lift $ atomically $ readTVar count
-  mt   <- getM (method % request)
-  ur   <- getM (uri    % request)
-  st   <- getM (status % response)
-  addr <- getM address
-  let code = codeFromStatus st
-      clr  = if code >= 400 then red else green
-  lift $ hPutStrLn handle $ concat [
-      concat ["[", show addr, "] ", show c, "\t"]
-    , show mt, "\t"
-    , show ur, " -> "
-    , clr
-    , show code, " "
-    , show st
-    , reset
-    ]
-  return handle
-
diff --git a/src/Network/Salvia/Handlers/Login.hs b/src/Network/Salvia/Handlers/Login.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Login.hs
+++ /dev/null
@@ -1,272 +0,0 @@
-module Network.Salvia.Handlers.Login (
-
-  -- * Basic types.
-    Username
-  , Password
-  , Action
-  , Actions
-  , User (..)
-  , Users
-  , UserDatabase
-  , TUserDatabase
-
-  -- * User Sessions.
-  , UserPayload (..)
-  , UserSession
-  , TUserSession
-  , UserSessionHandler
-
-  -- * Handlers.
-  , hSignup
-  , hLogin
-  , hLogout
-  , hLoginfo
-
-  , hAuthorized
-  , hAuthorizedUser
-
-  -- * Helper functions.
-  , readUserDatabase
-
-  ) where
-
-import Control.Concurrent.STM (TVar, atomically, readTVar, writeTVar, newTVar)
-import Control.Monad.State (lift, liftM)
-import Data.Digest.OpenSSL.MD5 (md5sum)
-import Data.List (intercalate)
-import Data.Maybe (catMaybes)
-import Data.Record.Label
-import Misc.Misc (atomModTVar, safeHead)
-import Network.Protocol.Http (Status (Unauthorized, OK), status)
-import Network.Salvia.Handlers.Error (hCustomError, hError)
-import Network.Salvia.Handlers.Session
-import Network.Salvia.Httpd (sendStrLn, Handler, uriEncodedPostParamsUTF8, response)
-import Network.Protocol.Uri
-import Data.ByteString.UTF8 (fromString)
-
--------------------------------------------------------------------------------
-
-type Username = String
-type Password = String
-type Action   = String
-type Actions  = [Action]
-
-{- |
-User containg a username, password and a list of actions this user is allowed
-to perform within the system.
--}
-
-data User = User {
-    username :: Username
-  , password :: Password
-  , email    :: String
-  , actions  :: Actions
-  } deriving (Eq, Show)
-
-type Users = [User]
-
-{- |
-A user database containing a list of users, a list of default actions the guest
-or `no-user' user is allowed to perform and a polymorphic reference to the
-place the database originates from. This source field cab be used by update
-functions synchronizing changes back to the database.
--}
-
-data UserDatabase src =
-  UserDatabase {
-    dbUsers  :: Users
-  , dbGuest  :: Actions
-  , dbSource :: src
-  } deriving Show
-
-type TUserDatabase src = TVar (UserDatabase src)
-
-{- |
-A user payload instance contains user related session information and can be
-used as the payload for regular sessions. It contains a reference to the user
-it is bound to, a flag to indicate whether the user is logged in or not and a
-possible user specific session payload.
--}
-
-data UserPayload a = UserPayload {
-    upUser     :: User
-  , upLoggedIn :: Bool
-  , upPayload  :: Maybe a
-  } deriving (Eq, Show)
-
-type UserSession  a = Session  (UserPayload a)
-type TUserSession a = TSession (UserPayload a)
-
--- A handler that requires a session with a user specific payload.
-type UserSessionHandler a b = SessionHandler (UserPayload a) b
-
--------------------------------------------------------------------------------
-
--- Read user data file.
--- Format: username password action*
-
-readUserDatabase :: FilePath -> IO (TUserDatabase FilePath)
-readUserDatabase file = do
-
-  -- First line contains the default `guest` actions, tail lines contain users.
-  gst:ls <- lines `liftM` readFile file
-
-  atomically $ newTVar $ UserDatabase
-    (catMaybes $ map parseUserLine ls)
-    (words gst)
-    file
-  where
-    parseUserLine line =
-      case words line of
-        user:pass:mail:acts -> Just (User user pass mail acts)
-        _                   -> Nothing
-
-printUserLine :: User -> String
-printUserLine u = intercalate " " ([
-    username u
-  , password u
-  , email u
-  ] ++ actions u)
-
-{- |
-The signup handler is used to create a new entry in the user database. It reads
-a new username and password from the HTTP POST parameters and adds a new entry
-in the database when no user with such name exists. The user gets the specified
-initial set of actions assigned. On failure an `Unauthorized' error will be
-produced.
--}
-
-hSignup :: TUserDatabase FilePath -> Actions -> Handler ()
-hSignup tdb acts = do
-  db <- lift . atomically $ readTVar tdb
-  params <- uriEncodedPostParamsUTF8
-  case freshUserInfo params (dbUsers db) acts of
-    Nothing -> hCustomError Unauthorized "signup failed"
-    Just u  -> do
-      lift $ do
-        atomically
-          $ writeTVar tdb
-          $ UserDatabase (u : dbUsers db) (dbGuest db) (dbSource db)
-        appendFile (dbSource db) (printUserLine u)
-
-freshUserInfo :: Maybe Parameters -> Users -> Actions -> Maybe User
-freshUserInfo params us acts = do
-  p <- params
-  user <- "username" `lookup` p >>= id
-  pass <- "password" `lookup` p >>= id
-  mail <- "email"    `lookup` p >>= id
-  case safeHead $ filter ((==user).username) us of
-    Nothing -> return $ User user (md5sum $ fromString pass) mail acts
-    Just _  -> Nothing
-
--------------------------------------------------------------------------------
-
-{- |
-The login handler. Read the username and password values from the post data and
-use that to authenticate the user. When the user can be found in the database
-the user is logged in and stored in the session payload. Otherwise a
-`Unauthorized' response will be sent and the user has not logged in.
--}
-
-hLogin :: UserDatabase b -> UserSessionHandler a ()
-hLogin db session = do
-  params <- uriEncodedPostParamsUTF8
-  maybe
-    (hCustomError Unauthorized "login failed")
-    (loginSuccessful session)
-    (authenticate params db)
-
-authenticate :: Maybe Parameters -> UserDatabase a -> Maybe User
-authenticate params db = do
-  p <- params
-  user <- "username" `lookup` p >>= id
-  pass <- "password" `lookup` p >>= id
-  case safeHead $ filter ((==user).username) (dbUsers db) of
-    Nothing -> Nothing
-    Just u  ->
-      if password u == md5sum (fromString pass)
-      then return u
-      else Nothing
-
--- Login user and create `Ok' response on successful user.
-loginSuccessful :: TUserSession a -> User -> Handler ()
-loginSuccessful session user = do
-  lift $ atomModTVar (\s -> s {payload = Just (UserPayload user True Nothing)}) session
-  setM (status % response) OK
-  sendStrLn "login successful"
-
--------------------------------------------------------------------------------
-
-hLogout :: TUserSession a -> Handler ()
-hLogout session = do
-  lift $ atomModTVar (\s -> s {payload = Nothing}) session
-  return ()
-
--------------------------------------------------------------------------------
-
-{- |
-The `loginfo' handler exposes the current user session to the world using a
-simple text based file. The file contains information about the current session
-identifier, session start and expiration date and the possible user payload
-that is included.
--}
-
-hLoginfo :: UserSessionHandler a ()
-hLoginfo session = do
-  s' <- lift $ atomically $ readTVar session
-
-  sendStrLn $ "sID="     ++ show (sID     s')
-  sendStrLn $ "start="   ++ show (start   s')
-  sendStrLn $ "expire="  ++ show (expire  s')
-
-  case payload s' of
-    Nothing -> return ()
-    Just (UserPayload (User uname _ mail acts) _ _) -> do
-      sendStrLn $ "username=" ++ uname
-      sendStrLn $ "email="    ++ mail
-      sendStrLn $ "actions="  ++ intercalate " " acts
-
--------------------------------------------------------------------------------
-
-{- |
-Execute a handler only when the user for the current session is authorized to
-do so. The user must have the specified action contained in its actions list in
-order to be authorized. Otherwise an `Unauthorized' error will be produced.
-When no user can be found in the current session or this user is not logged in
-the guest account from the user database is used for authorization.
--}
-
-hAuthorized ::
-     UserDatabase b              -- ^ The user database to read guest account from.
-  -> Action                      -- ^ The actions that should be authorized.
-  -> (Maybe User -> Handler ())  -- ^ The handler to perform when authorized.
-  -> UserSessionHandler a ()     -- ^ This handler requires a user session.
-
-hAuthorized db action handler session = do
-  load <- liftM payload (lift $ atomically $ readTVar session)
-  case load of
-    Just (UserPayload user _ _)
-      | action `elem` actions user -> handler (Just user)
-    Nothing
-      | action `elem` dbGuest db   -> handler Nothing
-    _                              -> hError Unauthorized
-
-{- |
-Execute a handler only when the user for the current session is authorized to
-do so. The user must have the specified action contained in its actions list in
-order to be authorized. Otherwise an `Unauthorized' error will be produced. The
-guest user will not be used in any case.
--}
-
-hAuthorizedUser ::
-     Action                      -- ^ The actions that should be authorized.
-  -> (User -> Handler ())        -- ^ The handler to perform when authorized.
-  -> UserSessionHandler a ()     -- ^ This handler requires a user session.
-
-hAuthorizedUser action handler session = do
-  load <- liftM payload (lift $ atomically $ readTVar session)
-  case load of
-    Just (UserPayload user _ _)
-      | action `elem` actions user -> handler user
-    _                              -> hError Unauthorized
-
diff --git a/src/Network/Salvia/Handlers/MethodRouter.hs b/src/Network/Salvia/Handlers/MethodRouter.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/MethodRouter.hs
+++ /dev/null
@@ -1,42 +0,0 @@
-module Network.Salvia.Handlers.MethodRouter (
-    hMethod
-  , hMethodRouter
-
-  , hOPTIONS
-  , hGET
-  , hHEAD
-  , hPOST
-  , hPUT
-  , hDELETE
-  , hTRACE
-  , hCONNECT
-  ) where
-
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Salvia.Handlers.Dispatching
-import Network.Salvia.Handlers.Error
-import Network.Salvia.Httpd
-
-hMethod' :: Dispatcher Method a
-hMethod' = hDispatch (method % request) (==)
-
-hMethodRouter :: ListDispatcher Method a
-hMethodRouter = hListDispatch hMethod'
-
-hMethod :: Method -> Handler () -> Handler ()
-hMethod m k = hMethod' m k (hError NotFound)
-
--- Shortcut handlers that only work for specific method types or fail.
-hOPTIONS, hGET, hHEAD, hPOST, hPUT, hDELETE, hTRACE, hCONNECT
-  :: Handler () -> Handler ()
-
-hOPTIONS = hMethod OPTIONS
-hGET     = hMethod GET
-hHEAD    = hMethod HEAD
-hPOST    = hMethod POST
-hPUT     = hMethod PUT
-hDELETE  = hMethod DELETE
-hTRACE   = hMethod TRACE
-hCONNECT = hMethod CONNECT
-
diff --git a/src/Network/Salvia/Handlers/Parser.hs b/src/Network/Salvia/Handlers/Parser.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Parser.hs
+++ /dev/null
@@ -1,41 +0,0 @@
-module Network.Salvia.Handlers.Parser (hParser) where
-
-import Control.Monad.State
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Salvia.Httpd
-import System.IO
-import Text.ParserCombinators.Parsec (parse)
-
-{- |
-The 'hParser' handler is used to parse the raw request message into the
-'Message' data type. This handler is generally used as (one of) the first
-handlers in a configuration. The first handler argument is executed when the
-request is invalid, possibly due to parser errors. The second handler argument
-is executed when the request is valid.
--}
-
-hParser ::
-     (String -> Handler a) -- ^ The fail handler.
-  -> Handler a             -- ^ The succeed handler.
-  -> Handler a
-
-hParser onfail onsuccess = do
-  h <- getM sock
-  -- TODO use try and fail with bad request or reject silently.
-  msg <- lift $ readHeader h `catch` error "AAAAp"
-  case parse pRequest "" (msg "") of
-    Left err -> onfail (show err)
-    Right x -> do
-      setM request x
-      onsuccess
-
--- Read all lines until the first empty line.
-readHeader :: Handle -> IO (String -> String)
-readHeader h = do
-  l <- hGetLine h
-  let lf = showChar '\n'
-  if l `elem` ["", "\r"]
-    then return lf
-    else liftM ((showString l . lf) .) (readHeader h)
-
diff --git a/src/Network/Salvia/Handlers/PathRouter.hs b/src/Network/Salvia/Handlers/PathRouter.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/PathRouter.hs
+++ /dev/null
@@ -1,37 +0,0 @@
-module Network.Salvia.Handlers.PathRouter (
-    hPath
-  , hPathRouter
-  , hPrefix
-  , hPrefixRouter
-
-  , hParameters
-  ) where
-
-import Control.Monad.State
-import Data.List (isPrefixOf)
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Protocol.Uri (path, queryParams, Parameters)
-import Network.Salvia.Handlers.Dispatching
-import Network.Salvia.Httpd
-
-chop :: String -> Handler a -> Handler a
-chop a = withM (path % uri % request) (modify (drop $ length a))
-
-hPath :: Dispatcher String a
-hPath p h = hDispatch (path % uri % request) (==) p (chop p h)
-
-hPathRouter :: ListDispatcher String b
-hPathRouter = hListDispatch hPath
-
-hPrefix :: Dispatcher String a
-hPrefix p h = hDispatch (path % uri % request) isPrefixOf p (chop p h)
-
-hPrefixRouter :: ListDispatcher String b
-hPrefixRouter = hListDispatch hPrefix
-
--- Path related utilities.
-
-hParameters :: Handler Parameters
-hParameters = getM (uri % request) >>= return . queryParams 
-
diff --git a/src/Network/Salvia/Handlers/Printer.hs b/src/Network/Salvia/Handlers/Printer.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Printer.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-module Network.Salvia.Handlers.Printer (hPrinter) where
-
-import Control.Monad.State
-import Data.Record.Label (getM)
-import Network.Salvia.Httpd
-
-{- |
-The 'hPrinter' handler print the entire response including the headers to the
-client. This handler is generally used as (one of) the last handler in a
-configuration.
--}
-
-hPrinter :: Handler ()
-hPrinter = do
-
-  -- Send the entire HTTP response header.
-  sendHeaders
-
-  -- Process all send actions in queue.
-  s <- getM sock
-  q <- getM queue
-  lift $ mapM_ ($ s) q
-
diff --git a/src/Network/Salvia/Handlers/Put.hs b/src/Network/Salvia/Handlers/Put.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Put.hs
+++ /dev/null
@@ -1,26 +0,0 @@
-module Network.Salvia.Handlers.Put (hPut) where
-
-import Control.Monad.State
-import Network.Protocol.Http
-import Network.Salvia.Handlers.Error
-import Network.Salvia.Httpd
-import System.IO
-import qualified Data.ByteString.Lazy as B
-
-{- |
-First naive handler for the PUT request. This probably does not handle all the
-quirks that the HTTP protocol specifies, but it does the job for now. When a
-'ContentLength' header field is available only this fixed number of bytes will
-be spooled from socket to the resource. When both the KeepAlive' and
-'ContentLength' header fields are not available the entire payload of the
-request is spooled to the resource.
---}
-
-hPut :: ResourceHandler ()
-hPut name =
-  safeIO (openBinaryFile name WriteMode)
-    $ (contents >>=) . maybe putError . putOk
-  where
-    putError   = hError NotImplemented
-    putOk fd c = lift (B.hPut fd c >> hClose fd)
-
diff --git a/src/Network/Salvia/Handlers/Redirect.hs b/src/Network/Salvia/Handlers/Redirect.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Redirect.hs
+++ /dev/null
@@ -1,13 +0,0 @@
-module Network.Salvia.Handlers.Redirect (hRedirect) where
-
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Protocol.Uri (URI)
-import Network.Salvia.Httpd
-
-hRedirect :: URI -> Handler ()
-hRedirect u =
-  enterM response $ do
-    setM location (Just u)
-    setM status MovedPermanently
-
diff --git a/src/Network/Salvia/Handlers/Rewrite.hs b/src/Network/Salvia/Handlers/Rewrite.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Rewrite.hs
+++ /dev/null
@@ -1,38 +0,0 @@
-module Network.Salvia.Handlers.Rewrite (
-    hRewrite
-  , hRewritePath
-  , hRewriteHost
-  , hRewriteExt
-  , hWithDir
-  , hWithoutDir
-  ) where
-
-import Control.Monad.State
-import Data.List (isPrefixOf)
-import Data.Record.Label
-import Network.Protocol.Http
-import Network.Protocol.Uri (URI, host, path, extension)
-import Network.Salvia.Httpd
-
-hRewrite :: (URI -> URI) -> Handler a -> Handler a
-hRewrite f = withM (uri % request) (modify f)
-
--- express these below in terms of the above?
-
-hRewriteHost :: (String -> String) -> Handler a -> Handler a
-hRewriteHost f = withM (host % uri % request) (modify f)
-
-hRewritePath :: (String -> String) -> Handler a -> Handler a
-hRewritePath f = withM (path % uri % request) (modify f)
-
-hRewriteExt :: (Maybe String -> Maybe String) -> Handler a -> Handler a
-hRewriteExt f = withM (extension % path % uri % request) (modify f)
-
-hWithDir :: String -> Handler a -> Handler a
-hWithDir d = hRewritePath (d++)
-
-hWithoutDir :: String -> Handler a -> Handler a
-hWithoutDir d h = do
-  p <- getM (path % uri % request)
-  (if d `isPrefixOf` p then hRewritePath (drop $ length d) else id) h
-
diff --git a/src/Network/Salvia/Handlers/Session.hs b/src/Network/Salvia/Handlers/Session.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/Session.hs
+++ /dev/null
@@ -1,180 +0,0 @@
-module Network.Salvia.Handlers.Session (
-    hSession
-
-  , SessionID
-  , Session (..)
-  , TSession
-  , SessionHandler
-  , Sessions
-
-  , mkSessions
-  ) where
-
-import Control.Applicative hiding (empty)
-import Control.Concurrent.STM
-import Control.Monad.State
-import Data.Time.LocalTime
-import Misc.Misc (safeRead, atomModTVar, atomReadTVar, now, later)
-import Network.Protocol.Cookie hiding (empty)
-import Network.Salvia.Handlers.Cookie
-import Network.Salvia.Httpd hiding (start)
-import Prelude hiding (lookup)
-import System.Random
-import qualified Data.Map as M
-
--------------------------------------------------------------------------------
-
--- A session identifier. Should be unique for every session.
-newtype SessionID = SID Integer
-  deriving (Eq, Ord)
-
--- The session data type with polymorph payload.
-data Session a = Session {
-    sID     :: SessionID
-  , start   :: LocalTime
-  , expire  :: LocalTime
-  , payload :: Maybe a
-  } deriving Show
-
--- A shared session.
-type TSession a = TVar (Session a)
-
--- A handler that expects a session.
-type SessionHandler a b = TSession a -> Handler b
-
--- Create a new, empty, shared session.
-mkSession :: SessionID -> LocalTime -> IO (TSession a)
-mkSession sid e = do
-  s <- now
-  atomically $ newTVar $ Session sid s e Nothing
-
--------------------------------------------------------------------------------
-
-type Sessions a = TVar (M.Map SessionID (TSession a))
-
-instance Show SessionID where
-  show (SID sid) = show sid
-
-mkSessions :: IO (Sessions a)
-mkSessions = atomically $ newTVar M.empty
-
--------------------------------------------------------------------------------
-
-{-
-The session handler. This handler will try to return an existing session from
-the sessions map based on a session identifier found in the HTTP cookie. When
-such a session can be found the expiration date will be updated to a number of
-seconds in the future. When no session can be found a new one will be created.
-A cookie will be set that informs the client of the current session.
--}
-
-hSession :: Sessions a -> Integer -> Handler (TSession a)
-hSession smap expiration = do
-
-  -- Get the session identifier from an existing cookie or create a new one.
-  prev <- getSessionID <$> hGetCookies
-
-  -- Compute current time and expiration time.
-  (n, ex) <- lift $ liftM2 (,) now (later expiration)
-
-  -- Either create a new session or try to reuse current one.
-  tsession <- 
-    maybe
-    (newSession smap ex)
-    (existingSession smap ex n)
-    prev
-
-  setSessionCookie tsession ex
-  return tsession
-
--------------------------------------------------------------------------------
-
-{-
-Given the (possible wrong) request cookie, try to recover the existing --
-session identifier.
--}
-
-getSessionID :: Maybe Cookies -> Maybe SessionID
-getSessionID prev = do
-  ck  <- prev
-  sid <- cookie "sid" ck
-  sid' <- safeRead $ value sid
-  return (SID sid')
-  
-{-
-Generate a fresh, random session identifier using the default system random
-generator.
--}
-
-genSessionID :: IO SessionID
-genSessionID = do
-  g <- getStdGen
-  let (sid, g') = random g
-  setStdGen g'
-  return (SID (abs sid))
-
-
-{-
-This handler sets the HTTP cookie for the specified session. It will use a
-default cookie with an additional `sid' attribute with the session identifier
-as value. The session expiration date will be used as the cookie expire field.
--}
-
-setSessionCookie :: TSession a -> LocalTime -> Handler ()
-setSessionCookie tsession ex = do
-  ck <- newCookie ex
-  sid <- lift $ liftM sID $ atomReadTVar tsession
-  hSetCookies $
-    cookies [ck {
-      name  = "sid"
-    , value = show sid
-    }]
-
-{-
-Handler when no (valid) session is available. Create a new session with a
-specified expiration date. The session will be stored in the session map.
--}
-
-newSession :: Sessions a -> LocalTime -> Handler (TSession a)
-newSession sessions ex = lift $ do
-
-  -- Fresh session identifier.
-  sid <- genSessionID
-
-  -- Fresh session.
-  session <- mkSession sid ex
-
-  -- Place in session mapping usinf session identifier as key.
-  atomModTVar (M.insert sid session) sessions
-  return session
-
-{-
-Handler for existing sessions. Given an existing session identifier lookup a
-session from the session map. When no session is available, or the session is
-expired, create a new one using the `newSession' function. Otherwise the
-expiration date of the existing session is updated.
--}
-
-existingSession ::
-     Sessions a -> LocalTime -> LocalTime
-  -> SessionID -> Handler (TSession a)
-existingSession sessions ex n sid = do
-
-  -- Lookup the session in the session map given the session identifier.
-  mtsession <- lift $ liftM (M.lookup sid) (atomReadTVar sessions)
-  case mtsession of
-
-    -- Unrecognized session identifiers are penalised by a fresh session.
-    Nothing -> newSession sessions ex
-    Just tsession -> do
-      expd <- lift $ liftM expire (atomReadTVar tsession)
-      if expd < n
-
-        -- Session is expired, create a new one.
-        then newSession sessions ex
-
-        -- Existing session, update expiration date.
-        else lift $ do
-          atomModTVar (\s -> s {expire = ex}) tsession
-          return tsession
-
diff --git a/src/Network/Salvia/Handlers/VirtualHosting.hs b/src/Network/Salvia/Handlers/VirtualHosting.hs
deleted file mode 100644
--- a/src/Network/Salvia/Handlers/VirtualHosting.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-module Network.Salvia.Handlers.VirtualHosting (hVirtualHosting) where
-
-import Data.List (isPrefixOf)
-import Data.Record.Label
-import Network.Protocol.Http (hostname)
-import Network.Salvia.Handlers.Dispatching (Dispatcher, ListDispatcher, hListDispatch, hDispatch)
-import Network.Salvia.Httpd (request)
-
--- isSuffixOf?
-
-hVirtualHosting1 :: Dispatcher String a
-hVirtualHosting1 = hDispatch (hostname % request) isPrefixOf
-
-hVirtualHosting :: ListDispatcher String a
-hVirtualHosting = hListDispatch hVirtualHosting1
-
diff --git a/src/Network/Salvia/Httpd.hs b/src/Network/Salvia/Httpd.hs
--- a/src/Network/Salvia/Httpd.hs
+++ b/src/Network/Salvia/Httpd.hs
@@ -5,7 +5,7 @@
   , defaultConfig
 
   -- Httpd.Core.Handler
-  , Context (..)
+  , Context
   , config
   , request
   , response
@@ -20,15 +20,17 @@
   , UriHandler
 
   -- Httpd.Core.IO
-  , sendHeaders
   , send
   , sendStr
   , sendStrLn
   , sendBs
+
   , spool
   , spoolBs
-  , spoolAll
-  , spoolN
+
+  , flushHeaders
+  , flushQueue
+
   , emptyQueue
   , reset
 
@@ -42,6 +44,7 @@
   ) where
 
 import Network.Salvia.Core.Config
+import Network.Salvia.Core.Context
 import Network.Salvia.Core.Handler
 import Network.Salvia.Core.IO
 import Network.Salvia.Core.Main
