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 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