Hawk-0.0.2: src/Hawk/Controller/Server.hs
-- --------------------------------------------------------------------------
{- |
Module : $Header$
Copyright : Copyright (C) 2009 Björn Peemöller, Stefan Roggensack
License : BSD3
Maintainer : {inf6254, inf6509}fh-wedel.de
Stability : experimental
Portability : portable
Version : $Id: Main.hs 57 2009-05-29 11:33:59Z inf6254 $
The main dispatcher.
-}
-- --------------------------------------------------------------------------
{-# LANGUAGE TemplateHaskell, Rank2Types #-}
module Hawk.Controller.Server ( requestHandler ) where
import Control.Exception ( SomeException )
import Control.Monad.CatchIO ( catch )
import Control.Monad.State
import Control.Monad.Reader
import Control.Monad.Either
import Control.Monad.Trans()
import Data.ByteString.Lazy.UTF8 ( fromString )
import Data.Default
import qualified Data.Map as M
( null
, toList
)
import Database.HDBC ( ConnWrapper )
import Prelude hiding ( catch )
import qualified System.Log.Logger as Logger
import System.Log.Logger.TH ( deriveLoggers )
-- Hwak imports
import Hawk.Controller.Cookies
import Hawk.Controller.Responses
import Hawk.Controller.StateAccess
( setSessionValue
, getSessionValue
, deleteSessionKey
, getSession
, setSession
)
import Hawk.Controller.Static
import Hawk.Controller.Types
import Hawk.Controller.Util.Read ( maybeRead )
$(deriveLoggers "Logger" [Logger.DEBUG])
-- --------------------------------------------------------------------------
-- The request handler
-- --------------------------------------------------------------------------
-- | Runs the 'EnvController' from "Types" with the dispatching function
-- to process all requests "Hack" sends to the application
requestHandler :: (forall a m. (AppConfiguration a, MonadIO m) => m a) -- ^ 'AppConfiguration' Type you defined in your application, $a$ is not known to Hawk
-> ConnWrapper -- ^ Database Connection
-> BasicConfiguration -- ^ Hawk configuration type for its modules, e.g. Session, Authentication, etc. as well as base directories
-> Options -- ^
-> Application
requestHandler app conn conf opts env = runReaderT (runController dispatch) (RequestEnv conn conf env opts app)
-- --------------------------------------------------------------------------
-- Private request handling functions
-- --------------------------------------------------------------------------
-- | Dispatch a 'Request' and return the 'Response'
dispatch :: EnvController Response
dispatch = liftM addDefaultHeaders
$ handleExceptions
$ staticRequest
$ trace
$ tryDynamic error404
-- | Catch any 'Exception's and return a server error response
handleExceptions :: EnvController Response -> EnvController Response
handleExceptions contr = contr `catch` handler
where
handler :: SomeException -> EnvController Response
handler e = return $ errorResponse $ fromString $ "Internal server error: " ++ show e
-- | try to run dynamic content
tryDynamic :: EnvController Response -> EnvController Response
tryDynamic contr = do
rtng <- asks $ routing . configuration
req <- asks request
case rtng req of
Nothing -> contr
Just dynContr -> executeController dynContr
-- Just (dynContr, [m a -> m a]) ->
executeController :: StateController ByteString -> EnvController Response
executeController = (\s -> evalStateT s def)
. manageHeaders
. withCookies
. withSession
. sessionFlash
. liftM (either id pageResponse)
. runEitherT
manageHeaders :: HasState m => m Response -> m Response
manageHeaders contr = do
hdrs <- M.toList `liftM` gets responseHeaders
addCustomHeaders hdrs `liftM` contr
-- | Trace the incoming request and the calculated response
trace :: EnvController Response -> EnvController Response
trace contr = do
asks request >>= debugM . show
res <- contr
debugM $ show res
return res
-- | Provide a session for the invoked controller
withSession :: (MonadIO m, HasState m) => m a -> m a
withSession contr = do
config <- asks configuration
let store = sessionStore config
let opts = sessionOpts config
sess <- readSession store opts
debugM $ "Incoming session: " ++ show sess
setSession sess
res <- contr
sess' <- getSession
debugM $ "Outgoing session: " ++ show sess'
saveSession store opts sess'
return res
-- | Handle a flash persisted inside a session in case of redirects
sessionFlash :: HasState m => m Response -> m Response
sessionFlash contr = do
-- get flash out of the session
flashInSession <- getSessionValue flashKey
case flashInSession >>= maybeRead of
Nothing -> return ()
Just f -> do
modify $ \s -> s { flash = f }
deleteSessionKey flashKey
-- run the controller
response <- contr
-- save flash to session if we have a redirect
when (isRedirect response) $ do
f <- gets flash
unless (M.null f) (setSessionValue flashKey $ show f)
return response
where flashKey = "_flash"