I want to write a web server which stores its state in a State
monad with wai
/warp
. Something like this:
{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.Wai.Handler.Warp
import Network.HTTP.Types
import Control.Monad.State
import Data.ByteString.Lazy.Char8
main = run 3000 app
text x = responseLBS
status200
[("Content-Type", "text/plain")]
x
app req = return $ text "Hello World"
app1 req = modify (+1) >>= return . text . pack . show
-- main1 = runStateT (run 3000 app1) 0
The commented line doesn't work, of course. The intent is to store a counter in a state monad and display its increasing value on every request.
Also, how do I get thread safety? Does warp run my middleware sequentially or in parallel?
What options are available for the state - is there anything at all besides IORef
I can use in this scenario?
I understand that State gives safety but it seems wai doesn't allow State.
I only need a dead-simple single-threaded RPC I can call from somewhere else. Haxr
package requires a separate web server which is an overkill. See Calling Haskell from Node.JS - it didn't have any suggestions so I wrote a simple server using Wai/Warp and Aeson. But it seems that WAI was designed to support concurrent implementatons so it complicates things.
If it is in the State
monad, it is thread-safe by design. There is no concurrent IO actions to shared state possible. Either it is threadsafe, or it won't compile.
If you have truly parallel access to shared state as part of your design (i.e. separate forkIO threads updating a global counter), then you'll need to use and MVar
or TVar
in the STM
monad (or some other transactional barrier) to ensure atomicity.
If your interaction with the state can be expressed with a single call to atomicModifyIORef
, you can use that, and you don't need to explicitly serialise access to the state.
import Data.IORef
main = do state <- newIORef 42
run 3000 (app' state)
app' :: IORef Int -> Application
app' ref req
= return . text . pack . show `liftM` atomicModifyIORef ref (\st -> (st + 1, st + 1))
If your interaction is more complex and you need to enforce full serialisation of requests, you can use an MVar
in conjunction with StateT
.
import Control.Concurrent.MVar
import Control.Monad.State.Strict
main = do state <- newMVar 42
run 3000 (app' state)
app' :: MVar Int -> Application
app' ref request
= do state <- takeMVar ref
(response, newState) <- runStateT (application request) state
putMVar newState --TODO: ensure putMVar happens even if an exception is thrown
return response
application :: Request -> StateT Int (ResourceT IO) Response
application request = modify (+1) >>= return . text . pack . show
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With