簡體   English   中英

Haskell:使用 Scotty 跟蹤和修改 state HTTP API

[英]Haskell: Keeping track and modifying state with Scotty HTTP API

我可以通過 Scotty API 使用 IO 修改我的 state 嗎? 目前,我在 IO monad 中有一個 state 轉換器,可以根據用戶輸入修改 state。 但我想通過 Scotty API 來實現。

這是我目前擁有的 state 轉換器類型,我用它來不斷修改 state 類型並允許 IO 操作。

-- State Transformer type inside IO monad
type STIO st a = STM st IO a

-- State transformer inside a monad
newtype STM st m a = S (st -> m (a, st))

...並使用此提升功能將動作提升到 IO monad 中:

lift :: Monad m => m a -> STM st m a
lift mx = S (\s -> do
    x <- mx
    return (x, s))

這是我目前的基本 Scotty 服務器:

port = 8080

main = server

server :: IO ()
server = do
    print ("Starting Server at port " ++ show port)
    scotty port $ do
        get "/start" $ do
            json ("{starting: "++"True"++"}")

在我的腦海里,我想要一些類似的東西,但不確定如何實現它:

type State = Int

server :: STIO State ()
server = do
    print ("Starting Server at port " ++ show port)
    lift $ scotty port $ do
        get "/start" $ do
            updateCounterByOneInState
            counter <- getCounterFromState
            json $ "{count: " ++ counter ++ "}"

這樣的事情甚至可能嗎,還是我只是感到困惑?

與所有 WAI 應用程序一樣,Scotty 需要准備好在單獨的線程中處理多個並發請求。 這給您的STIO monad 帶來了一些問題,因為它不直接支持對 state 的並發訪問。您需要安排加載 state,運行處理程序,並以並發方式保存 state-安全的。

從技術上講,您可以借助Web.Scotty.Trans中的函數來完成此操作,但這不是一個好主意。 例如,以下自包含示例將 state 存儲在並發安全的MVar中:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns #-}

import Web.Scotty.Trans as W
import Control.Monad.State as S
import Control.Concurrent.MVar
import Network.Wai.Handler.Warp (Port)
import Data.Text.Lazy (Text)

type STIO s = StateT s IO

scottySTIO :: MVar s -> Port -> ScottyT Text (STIO s) () -> (STIO s) ()
scottySTIO sref p = scottyT p $ \act -> do
  s <- takeMVar sref
  (r, !s') <- runStateT act s
  putMVar sref s'
  return r

server :: STIO Int ()
server = do
  let port = 8080
  liftIO $ print ("Starting Server at port " ++ show port)
  s <- S.get
  sref <- liftIO $ newMVar s
  scottySTIO sref port $ do
    W.get "/start" $ do
      modify (+1)
      counter <- S.get
      json $ "{count: " ++ show counter ++ "}"

main :: IO ()
main = evalStateT server 0

這將“有效”,但它會表現出可怕的並發性能,因為它最終會完整地序列化所有請求,而不僅僅是保護關鍵代碼部分。 只有當您已經在STIO monad 中運行了大量代碼,您不想修改任何代碼,並且您願意承受性能損失時,它才真正有意義。

大多數時候,重構您的設計以以並發安全的方式存儲 state 並直接從IO訪問它會容易得多。 例如,計數器可以存儲在單個MVar中並在短臨界區中安全訪問:

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE BangPatterns #-}

import Web.Scotty
import Control.Concurrent.MVar
import Control.Monad.IO.Class

main :: IO ()
main = do
  let port = 8080
  print ("Starting Server at port " ++ show port)
  sref <- newMVar (0 :: Int)
  scotty port $ do
    get "/start" $ do
      -- start of critical section
      counter <- liftIO $ takeMVar sref
      let !counter' = counter + 1
      liftIO $ putMVar sref counter'
      -- end of critical section
      json $ "{count: " ++ show counter' ++ "}"

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM