module Web.Wheb.Internal where
import qualified Data.ByteString.Char8 as B
import Control.Monad (void)
import Control.Monad.Except (ExceptT, runExceptT)
import Control.Monad.Reader (ReaderT(runReaderT))
import Control.Monad.State (evalStateT, StateT(runStateT))
import qualified Data.Map as M (toList)
import qualified Data.Text.Lazy as T (fromStrict, Text, toStrict, pack)
import Network.HTTP.Types.Method (parseMethod, StdMethod(GET))
import Network.Wai (Application, Request(pathInfo, requestMethod), Response)
import Network.Wai.Parse (lbsBackEnd, parseRequestBody)
import Network.Wai.Handler.WebSockets (websocketsOr)
import qualified Network.WebSockets as W
import Web.Wheb.Routes (findUrlMatch, findSiteMatch, findSocketMatch)
import Web.Wheb.Types (EResponse, HandlerData(HandlerData, postData, routeParams),
HandlerResponse(HandlerResponse), InternalState(..),
WhebContent(toResponse), WhebError(Error404), WhebHandlerT,
WhebMiddleware, WhebOptions(..), WhebT(runWhebT))
import Web.Wheb.Utils (uhOh)
optsToApplication :: WhebOptions g s m ->
(forall a. m a -> IO a) ->
Application
optsToApplication opts@(WhebOptions {..}) runIO r respond = do
if ((length appWhebSockets) > 0)
then websocketsOr W.defaultConnectionOptions socketHandler handleMain r respond
else handleMain r respond
where socketHandler pc = do
case (findSocketMatch pathChunks appWhebSockets) of
Just (h, params) -> do
c <- W.acceptRequest pc
void $ runIO $ do
(mRes, st) <- runMiddlewares opts whebMiddlewares baseData
runDebugHandler (opts {startingState = st}) (h c) (baseData { routeParams = params })
Nothing -> W.rejectRequest pc (B.pack "No socket for path.")
handleMain r respond = do
pData <- parseRequestBody lbsBackEnd r
res <- runIO $ do
let mwData = baseData { postData = pData }
(mRes, st) <- runMiddlewares opts whebMiddlewares mwData
case mRes of
Just resp -> return $ Right resp
Nothing -> do
case (findSiteMatch appSites pathChunks) of
Just h -> do
runWhebHandler opts h st mwData
Nothing -> do
case (findUrlMatch stdMthd pathChunks appRoutes) of
Just (h, params) -> do
let hData = mwData { routeParams = params }
runWhebHandler opts h st hData
Nothing -> return $ Left Error404
finished <- either handleError return res
respond finished
baseData = HandlerData startingCtx r ([], []) [] opts
pathChunks = fmap T.fromStrict $ pathInfo r
stdMthd = either (\_-> GET) id $ parseMethod $ requestMethod r
runErrorHandler eh = runWhebHandler opts eh startingState baseData
handleError err = do
errRes <- runIO $ runErrorHandler (defaultErrorHandler err)
either (return . (const uhOh)) return errRes
runWhebHandler :: Monad m =>
WhebOptions g s m ->
WhebHandlerT g s m ->
InternalState s ->
HandlerData g s m ->
m EResponse
runWhebHandler (WhebOptions {..}) handler st hd = do
(resp, InternalState {..}) <- flip runStateT st $ do
flip runReaderT hd $
runExceptT $
runWhebT handler
return $ fmap (convertResponse respHeaders) resp
where convertResponse hds (HandlerResponse status resp) =
toResponse status (M.toList hds) resp
runDebugHandler :: Monad m =>
WhebOptions g s m ->
WhebT g s m a ->
HandlerData g s m ->
m (Either WhebError a)
runDebugHandler opts@(WhebOptions {..}) handler hd = do
flip evalStateT startingState $ do
flip runReaderT hd $
runExceptT $
runWhebT handler
runMiddlewares :: Monad m =>
WhebOptions g s m ->
[WhebMiddleware g s m] ->
HandlerData g s m ->
m (Maybe Response, InternalState s)
runMiddlewares opts mWs hd = loop mWs (startingState opts)
where loop [] st = return (Nothing, st)
loop (mw:mws) st = do
mwResult <- (runWhebMiddleware opts st hd mw)
case mwResult of
(Just _, _) -> return mwResult
(Nothing, nst) -> loop mws nst
runWhebMiddleware :: Monad m =>
WhebOptions g s m ->
InternalState s ->
HandlerData g s m ->
WhebMiddleware g s m ->
m (Maybe Response, InternalState s)
runWhebMiddleware (WhebOptions {..}) st hd mW = do
(eresp, is@InternalState {..}) <- flip runStateT st $ do
flip runReaderT hd $
runExceptT $
runWhebT mW
return $ (convertResponse respHeaders eresp, is)
where convertResponse hds (Right (Just (HandlerResponse status resp))) =
Just (toResponse status (M.toList hds) resp)
convertResponse _ _ = Nothing