Используйте StateT в Web.Scotty

Я пытаюсь сделать глупый веб-сервер, который хранит данные как State. Я использую Web.Scotty. Раньше я использовал ReaderT со Скотти для доступа к config, но тот же подход здесь не работает. Он сбрасывает состояние при каждом запросе.

Я хочу установить начальное состояние при запуске программы, а затем сохранить это состояние на всю жизнь программы.

Как я могу заставить это работать? (Следующее создает новое состояние при каждом запросе)

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty.Trans
import Control.Monad.State (StateT, evalStateT, lift)
import qualified Control.Monad.State as S
import Data.Text.Lazy (Text)

main :: IO ()
main = do
  let runner = flip evalStateT "message"
  scottyT 3000 runner runner routes

routes :: ScottyT Text (StateT Text IO) ()
routes = do

  get "/data" $ do
    val <- lift S.get
    text val

  put "/data/:val" $ do
    val <- param "val"
    lift $ S.put val
    text val

person Sean Clark Hess    schedule 16.12.2014    source источник
comment
Даже на веб-серверах, которые написаны на совершенно нефункциональных языках (apache, tomcat и т. д.), вы обычно не храните общее состояние в памяти... Вам нужно будет беспокоиться о блокировке, сохранении состояния при завершении работы, сопоставлении данных с пользователи и т. д. Вот почему существуют базы данных... Я предполагаю, что вы не можете делать то, что хотите, в Скотти, и авторы Скотти не хотели бы это реализовывать (но это только мое предположение) .   -  person jamshidh    schedule 16.12.2014
comment
Это имеет смысл. Единственная причина, по которой я это делаю, — научить некоторых людей haskell из node.js, и это казалось простым первым шагом, прежде чем мы добавим базу данных. Думаю, нет :)   -  person Sean Clark Hess    schedule 16.12.2014


Ответы (1)


Поведение, которое вы видите, определенно является ожидаемым: обратите внимание на примечание к третьему аргументу в документация для scottyT:

-> (m Response -> IO Response) -- Запустить монаду m в IO, вызываемую при каждом действии.

Что вы можете сделать, так это сохранить состояние, внешнее по отношению к монаде StateT, чтобы вы могли восстановить его в обработчике каждого действия. Самый наивный способ, который я могу придумать, был бы примерно таким:

main :: IO ()
main = do
    let s0 = "message"
    let transform = flip evalStateT s0
    runner <- restartableStateT s0
    scottyT 3000 transform runner routes

restartableStateT :: s -> IO (StateT s IO a -> IO a)
restartableStateT s0 = do
    r <- newIORef s0
    return $ \act -> do
        s <- readIORef r
        (x, s') <- runStateT act s
        atomicModifyIORef' r $ const (s', x)

но на самом деле это не касается того, что должно произойти, если два запроса поступают одновременно, это просто «последний закончивший выигрывает».

person Cactus    schedule 16.12.2014
comment
стоит отметить, что в документации Скотти есть пример именно для этого сценария github.com/scotty-web/scotty/blob/master/examples/ - person fommil; 26.09.2017