I am writing a web server using Scotty. The server should have a login route that once user logs in, a token is dispatched and recorded in the server. Obviously the recorded tokens should be in a global state (not using Redis kind of thing because the server is run on single machine and has small view counts).
Now I know the Scotty examples have shown how to define and access global states in routes, but I can't find out how to do the same thing for middlewares.
I tried using the same way from the official example but neither the middleware function nor the app function has a WebM monad context so I can't really do this:
app = do
tokensList <- webM $ gets tokens
middleware $ authMiddleware tokensList
That "official example" isn't so great. I mean, it's a good example of using a custom monad, but it's a lousy example of how to provide global state. You don't need the custom monad at all. You just need the TVar.
Ultimately, all you need to do is create the newTVar in main, and then pass it to the app function to construct the application and middleware, which can access the TVar directly with lifted IO operations in the application and IO operations in the middleware. It'll look something like the following:
main :: IO ()
main = do
state <- newTVar startState
scotty 3000 $ app state
app :: TVar AppState -> ScottyM ()
app state = do
middleware (authMiddleware state)
get "..." $ do
x <- liftIO $ readTVarIO state
...
authMiddleware :: TVar State -> MiddleWare
authMiddleware state app req resp = do
x <- readTVarIO state
...
app req resp
To illustrate, here's a rewrite of that official example without the monad that operates on the TVar directly in the handler. (No middleware yet, but see the example further below.)
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Web.Scotty
import Control.Concurrent.STM
import Control.Monad.IO.Class
import Data.String
newtype AppState = AppState { tickCount :: Int }
main :: IO ()
main = do
state <- newTVarIO (AppState 0)
scotty 3000 $ app state
app :: TVar AppState -> ScottyM ()
app state = do
get "/" $ do
c <- liftIO $ tickCount <$> readTVarIO state
text $ fromString $ show c
get "/plusone" $ do
liftIO . atomically $ modifyTVar' state (\(AppState n) -> AppState (n+1))
redirect "/"
get "/plustwo" $ do
liftIO . atomically $ modifyTVar' state (\(AppState n) -> AppState (n+2))
redirect "/"
and here's a version that uses middleware to count page accesses and queries and resets the value from the handlers, all directly through the TVar without requiring any custom monad:
{-# LANGUAGE OverloadedStrings #-}
module Main (main) where
import Web.Scotty
import Control.Concurrent.STM
import Control.Monad.IO.Class
import Data.String
import Network.Wai
newtype AppState = AppState { tickCount :: Int }
main = do
state <- newTVarIO (AppState 0)
scotty 3000 $ app state
app :: TVar AppState -> ScottyM ()
app state = do
middleware $ countMiddleware state
get "/" $ do
c <- liftIO $ tickCount <$> readTVarIO state
text $ fromString $ show c
get "/reset" $ do
liftIO . atomically $ writeTVar state (AppState 0)
redirect "/"
countMiddleware :: TVar AppState -> Middleware
countMiddleware state appl req resp = do
liftIO . atomically $ modifyTVar' state (\(AppState n) -> AppState (n+1))
appl req resp
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