I've read Happstack crashcourse. My web server has almost exact way described in the section Passing multiple AcidState handles around transparently
Problem I have is that, I have value which is non-acidic, but want to access within the Happstack application. Specifically speaking, "PushManager" from push-notify-general library,
What I wanted is:
data Acid = Acid
{ acidCountState :: AcidState CountState
, acidGreetingState :: AcidState GreetingState
, acidPushManager :: AcidState PushManager
}
I couldn't make this work, because 1) PushManager use so many data types internally, and it is not realistic/robust to make underlying data type SafeCopy compatible by calling $(deriveSafeCopy ...). 2) PushManager not only contains simple value, but also function which is SafeCopy compatible.
Other thing I tried is to "Acid" data declaration to carry not only AcidState, but also non-AcidState data. By looking at the definition of runApp, "Acid" is just used for Reading, so I thought that rewriting with State monad may be able to achive my need. - but it turns out that it was not so simple. My tentative code is:
{-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving,
TemplateHaskell, TypeFamilies, DeriveDataTypeable,
FlexibleContexts, ScopedTypeVariables,
NamedFieldPuns, DeriveFunctor, StandaloneDeriving, OverloadedStrings #-}
import Control.Applicative ( Applicative, Alternative, (<$>))
import Control.Monad ( MonadPlus )
import Control.Monad.State.Strict ( MonadState, StateT, get, put, evalStateT )
import Control.Monad.Trans ( MonadIO )
import Data.Acid
import Data.Data ( Data, Typeable )
import Happstack.Server
newtype Simple a = Simple { unSimple :: a }
deriving (Show)
data CountState = CountState { count :: Integer }
deriving (Eq, Ord, Data, Typeable, Show)
-- This data is equivalent to the one previously called "Acid"
data States = States {
simpleState :: Simple Int
, acidCountState :: AcidState CountState
}
initialStates :: States
initialStates = States { simpleState = Simple 1, acidCountState = undefined }
newtype App a = App { unApp :: ServerPartT (StateT States IO) a }
deriving ( Functor, Alternative, Applicative, Monad
, MonadPlus, MonadIO, HasRqData, ServerMonad
, WebMonad Response, FilterMonad Response
, Happstack, MonadState States )
class HasSimple m st where
getSimple :: m (Simple st)
putSimple :: (Simple st) -> m ()
instance HasSimple App Int where
getSimple = simpleState <$> get
putSimple input = do
whole <- get
put $ whole {simpleState = input}
simpleQuery :: ( Functor m
, HasSimple m a
, MonadIO m
, Show a
) =>
m a
simpleQuery = do
(Simple a) <- getSimple
return a
simpleUpdate :: ( Functor m
, HasSimple m a
, MonadIO m
, Show a
) =>
a
-> m ()
simpleUpdate a = putSimple (Simple a)
runApp :: States -> App a -> ServerPartT IO a
runApp states (App sp) = do
mapServerPartT (flip evalStateT states) sp
rootDir :: App Response
rootDir = do
intVal <- simpleQuery
let newIntVal :: Int
newIntVal = intVal + 1
simpleUpdate newIntVal
ok $ toResponse $ ("hello number:" ++ (show newIntVal))
main :: IO ()
main = do
simpleHTTP nullConf $ runApp initialStates rootDir
It compiled, but every time web page is requested, the page display same number. Looking at my code again, and I felt that evalStateT in runApp is wrong, because it never use updated state value.
Now, I am reading mapServerPartT and ServerPartT, but that is too complex. Appreciate if anybody can answer the title line: "How to carry non-Acidic value in Happstack?"
The mapServerPartT
would not help you either. The issue here is that the handler function you pass to simpleHTTP
gets called in a new thread for each request that comes in. And each time it is going to be calling runApp
with the initialStates
argument. So not only is the value lost at the end of the request, but if multiple threads are handling requests, they will each have their own separate copy of the state.
Once we realize that we want state that is shared between multiple threads, we realize that the answer must rely on one of the tools for doing interthread communication. A good choice would probably be a TVar
, http://hackage.haskell.org/package/stm-2.4.3/docs/Control-Concurrent-STM-TVar.html
main :: IO ()
main = do
states <- atomically $ newTVar initialStates
simpleHTTP nullConf $ runApp states rootDir
Note that we create the TVar
before we start listening for incoming connections. We pass the TVar
to all the request handling threads, and STM takes care of synchronizing the values between threads.
a TVar
is a bit like acid-state
without the (D)urability. Since the data does not need to be saved, there is no need for SafeCopy
instances, etc.