module Control.Monad.Apiary.Action.Internal where
import Control.Applicative
import Control.Monad
import Control.Monad.Trans
import Control.Monad.Base
import Control.Monad.Trans.Control
import Network.Wai
import Network.Mime
import Data.Default.Class
import Data.Monoid
import Network.HTTP.Types
import Blaze.ByteString.Builder
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Data.Text as T
import Data.Conduit
data ApiaryConfig = ApiaryConfig
{
notFound :: Application
, defaultStatus :: Status
, defaultHeader :: ResponseHeaders
, rootPattern :: [S.ByteString]
, mimeType :: FilePath -> S.ByteString
}
instance Default ApiaryConfig where
def = ApiaryConfig
{ notFound = \_ -> return $ responseLBS status404
[("Content-Type", "text/plain")] "404 Page Notfound.\n"
, defaultStatus = ok200
, defaultHeader = []
, rootPattern = ["", "/", "/index.html", "/index.htm"]
, mimeType = defaultMimeLookup . T.pack
}
data ActionState
= ActionState
{ actionStatus :: Status
, actionHeaders :: ResponseHeaders
, actionBody :: Body
}
data Body
= File FilePath (Maybe FilePart)
| Builder Builder
| LBS L.ByteString
| SRC (Source IO (Flush Builder))
actionStateToResponse :: ActionState -> Response
actionStateToResponse as = case actionBody as of
File f p -> responseFile st hd f p
Builder b -> responseBuilder st hd b
LBS l -> responseLBS st hd l
SRC s -> responseSource st hd s
where
st = actionStatus as
hd = actionHeaders as
data Act a
= Continue a
| Pass
| Stop Response
newtype Action a = Action { unAction :: forall b.
ApiaryConfig
-> Request
-> ActionState
-> (a -> ActionState -> IO (Act b))
-> IO (Act b)
}
instance Functor Action where
fmap f m = Action $ \conf req st cont ->
unAction m conf req st (\a s' -> s' `seq` cont (f a) s')
instance Applicative Action where
pure x = Action $ \_ _ st cont -> cont x st
mf <*> ma = Action $ \conf req st cont ->
unAction mf conf req st $ \f st' ->
unAction ma conf req st' $ \a st'' ->
st' `seq` st'' `seq` cont (f a) st''
instance Monad Action where
return x = Action $ \_ _ st cont -> cont x st
m >>= k = Action $ \conf req st cont ->
unAction m conf req st $ \a st' ->
st' `seq` unAction (k a) conf req st' cont
fail _ = Action $ \_ _ _ _ -> return Pass
instance MonadIO Action where
liftIO m = Action $ \_ _ st cont ->
liftIO m >>= \a -> cont a st
runAction :: Action a
-> ApiaryConfig -> Request -> ActionState
-> IO (Act (a, ActionState))
runAction m conf req st = unAction m conf req st $ \a st' ->
st' `seq` return (Continue (a, st'))
action :: (ApiaryConfig -> Request -> ActionState -> IO (Act (a, ActionState)))
-> Action a
action f = Action $ \conf req st cont -> f conf req st >>= \case
Pass -> return Pass
Stop s -> return $ Stop s
Continue (a,st') -> st' `seq` cont a st'
execAction :: ApiaryConfig -> Action () -> Application
execAction config m request = runAction m config request resp >>= \case
Pass -> notFound config request
Stop s -> return s
Continue (_,r) -> return $ actionStateToResponse r
where
resp = ActionState (defaultStatus config) (defaultHeader config) (LBS "")
instance Alternative Action where
empty = mzero
(<|>) = mplus
instance MonadPlus Action where
mzero = action $ \_ _ _ -> return Pass
mplus m n = action $ \c r s -> runAction m c r s >>= \case
Continue a -> return $ Continue a
Stop stp -> return $ Stop stp
Pass -> runAction n c r s
instance Monoid (Action ()) where
mempty = mzero
mappend = mplus
instance MonadBase IO Action where
liftBase = liftIO
instance MonadBaseControl IO Action where
newtype StM Action a = StMAction { unStMAction :: Act (a, ActionState) }
liftBaseWith f = action $ \c r s ->
liftM (\a -> Continue (a, s)) (f $ \t -> liftM StMAction $ runAction t c r s)
restoreM m = action $ \_ _ _ -> return (unStMAction m)
stop :: Action a
stop = Action $ \_ _ s _ -> return $ Stop (actionStateToResponse s)
stopWith :: Response -> Action a
stopWith a = Action $ \_ _ _ _ -> return $ Stop a
getRequest :: Action Request
getRequest = Action $ \_ r s c -> c r s
getConfig :: Action ApiaryConfig
getConfig = Action $ \c _ s cont -> cont c s
modifyState :: (ActionState -> ActionState) -> Action ()
modifyState f = Action $ \_ _ s c -> c () (f s)
getHeaders :: Action RequestHeaders
getHeaders = requestHeaders `liftM` getRequest
status :: Status -> Action ()
status st = modifyState (\s -> s { actionStatus = st } )
modifyHeader :: (ResponseHeaders -> ResponseHeaders) -> Action ()
modifyHeader f = modifyState (\s -> s {actionHeaders = f $ actionHeaders s } )
addHeader :: HeaderName -> S.ByteString -> Action ()
addHeader h v = modifyHeader ((h,v):)
setHeaders :: ResponseHeaders -> Action ()
setHeaders hs = modifyHeader (const hs)
contentType :: S.ByteString -> Action ()
contentType c = modifyHeader
(\h -> ("Content-Type", c) : filter (("Content-Type" /=) . fst) h)
redirectWith :: Status
-> S.ByteString
-> Action ()
redirectWith st url = do
status st
addHeader "location" url
redirectPermanently :: S.ByteString -> Action ()
redirectPermanently = redirectWith movedPermanently301
redirect :: S.ByteString -> Action ()
redirect to = do
v <- httpVersion <$> getRequest
if v == http11
then redirectWith seeOther303 to
else redirectWith status302 to
redirectTemporary :: S.ByteString -> Action ()
redirectTemporary to = do
v <- httpVersion <$> getRequest
if v == http11
then redirectWith temporaryRedirect307 to
else redirectWith status302 to
file :: FilePath -> Maybe FilePart -> Action ()
file f p = do
mime <- mimeType <$> getConfig
contentType (mime f)
file' f p
file' :: FilePath -> Maybe FilePart -> Action ()
file' f p = modifyState (\s -> s { actionBody = File f p } )
builder :: Builder -> Action ()
builder b = modifyState (\s -> s { actionBody = Builder b } )
lbs :: L.ByteString -> Action ()
lbs l = modifyState (\s -> s { actionBody = LBS l } )
source :: Source IO (Flush Builder) -> Action ()
source src = modifyState (\s -> s { actionBody = SRC src } )
redirectFound :: S.ByteString -> Action ()
redirectFound = redirectWith found302
redirectSeeOther :: S.ByteString -> Action ()
redirectSeeOther = redirectWith seeOther303