我正在使用 Scotty 编写一个 Web 服务器。服务器应该有一个登录路由,一旦用户登录,就会调度令牌并记录在服务器中。显然,记录的令牌应该处于全局状态(不使用 Redis 之类的东西,因为服务器在单机上运行并且视图计数较小)。
现在我知道 Scotty 的示例已经展示了如何在路由中定义和访问全局状态,但我不知道如何为中间件做同样的事情。
我尝试使用与官方示例相同的方式,但中间件函数和应用程序函数都没有 WebM monad 上下文,所以我不能真正做到这一点:
app = do
tokensList <- webM $ gets tokens
middleware $ authMiddleware tokensList
那个“官方例子”并不是那么好。我的意思是,这是使用自定义 monad 的一个很好的例子,但它是如何提供全局状态的一个糟糕的例子。您根本不需要自定义 monad。您只需要
TVar
。
最终,您需要做的就是在
newTVar
中创建 main
,然后将其传递给 app
函数来构建应用程序和中间件,应用程序和中间件可以通过提升 TVar
操作直接访问 IO
在应用程序中和中间件中的IO
操作中。它看起来像下面这样:
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
为了说明这一点,这里重写了官方示例,没有直接在处理程序中对
TVar
进行操作的 monad。 (还没有中间件,但请参阅下面的示例。)
{-# 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 "/"
这是一个使用中间件来计算页面访问和查询并重置处理程序中的值的版本,所有这些都直接通过
TVar
进行,无需任何自定义 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