【问题标题】:How do I maintain a server-side state with Snap Framework?如何使用 Snap Framework 维护服务器端状态?
【发布时间】:2011-04-23 14:59:13
【问题描述】:

服务器端会话 [还] 不是 Snap 框架的一部分。有没有办法添加某种服务器端状态?

假设我想为每个 HTTP 请求增加一个计数器。我该怎么做?

【问题讨论】:

    标签: haskell session-state haskell-snap-framework


    【解决方案1】:

    我找到了两个会话相关的包:

    snap-auth 由 Snap 框架团队制作,或至少由其中一位作者/贡献者 (Ozgun Ataman) 制作。它针对身份验证和会话管理。会话管理是使用 ByteString 到 ByteString 的映射完成的,这意味着您只能存储已经序列化为 ByteString 的数据:

    type Session = Map ByteString ByteString
    

    另一方面,mysnapsession 允许您使用任意类型来为会话建模。然而,Map 类型的会话有一些辅助函数。更多详情here。作者 Chris Smith 也是 Snap 框架项目的成员。

    【讨论】:

      【解决方案2】:

      就目前而言,上述答案是正确的,但它并没有解决一些实际问题。

      首先是服务器重启。如果您的存储不仅仅是缓存,它还需要在服务器重启时保持持久性。

      其次是代码重载。 Snap 的未来版本,从 0.3 开始(可能在 12 月初到期)将在开发使用中动态重新加载代码。就开发速度而言,这是一个巨大的优势,但它使服务器本地状态成为一种有趣的心理锻炼。如果程序员更改了服务器本地状态的类型/初始化/任何内容,则需要重新初始化。那里有一些巨大的工程挑战。

      当我为 0.3 编写动态重新加载代码时,我曾为这个问题苦苦挣扎了一段时间。然后我看了看其他平台。 php?在外部存储所有内容(数据库、内存缓存等)。完全没有内存中的交叉请求存储。 Ruby on Rails?一样。

      结合第一个问题中固有的挑战,我得出的结论是,除了可能的缓存优化之外,服务器应该是无状态的。将持久性问题留给为其设计的库/外部流程。

      所以我设计了生产和开发加载器使用的通用接口(一个使用静态加载,另一个使用动态加载)来获取 3 个函数:一个初始化函数、一个清理函数和一个使用由初始化函数。在生产模式下,编译为在服务器启动时调用初始化,并在服务器关闭时进行清理。在开发模式下,它编译为:对于每个请求,动态加载所有 3 个,然后运行 ​​init、handler、cleanup。显然,没有任何状态能够以这种方式在交叉请求中幸存下来。

      然后我的答案就变成了:通过某种具有内置持久性的机制进行交叉请求存储,并让服务器状态成为其接口。如果您想在进程内工作,请使用 happstack-state 或 sqlite 之类的东西,如果您想在本地进程之外工作,请使用数据库或其他一些外部存储。

      作为补充说明,由于添加了 MonadSnap 接口,在 Snap 0.3 中管理“全局”资源(如连接池等)也容易得多。

      【讨论】:

      • 我很想了解更多关于动态代码重新加载的信息。我知道这意味着在重新启动之间恢复服务器状态的生命周期,但它是否还针对同时运行多个版本(à la Erlang)?它是否涉及动态链接加载器(dlopen)?我想我会密切关注 Snap 博客。
      • 动态重新加载目前完全使用 ghc api 完成。无论如何,它都不是很 erlang-esque。它更接近 ruby​​ 或 php,只是根据每个请求的需要重新解释操作。 (嗯,已经进行了一些优化以减少它的频率,但它基本上是每个请求。)
      【解决方案3】:

      最简单的方法是将状态放在 mvar 后面:

      fooHandler :: MVar Int -> Snap ()
      fooHandler mvar = do
          x <- liftIO $ modifyMVar mvar $ \y -> let y'=y+1 in (y',y')
          writeBS $ S.pack $ "Incremented counter to: " ++ show x
      

      在站点初始化时初始化 mvar。希望这会有所帮助。

      【讨论】:

      • 当心,您可能需要改写\y -&gt; let y'=y+1 in y' `seq` (y',y')。否则,您可能会在 mvar 中存储未评估的 thunk 而不是值。或者使用 strict-concurrency 包中的严格 mvar。
      • 鉴于 modifyMVar 的签名是modifyMVar :: MVar a -&gt; (a -&gt; IO (a, b)) -&gt; IO b,应该是modifyMVar mvar $ \y -&gt; let y' = y+1 in y' ``seq`` return (y',y')吧?
      • gawi:您在质疑modifyMVar ... 代码是否类型正确?只需手动解析类型。你已经说过modifyMVar :: MVar a -&gt; (a -&gt; IO (a, b)) -&gt; IO b,所以最好是mvar :: MVar a,我们从fooHandler(mvar :: MVar Int,所以a ~ Int和lambda(\y -&gt; let y' ... :: TypeOf y in y' `seq` return (y',y') :: a -&gt; IO (a,b)知道这一点。所以(y',y')强制a和b是一样的。使用hoogle 你应该知道seq :: a -&gt; b -&gt; b b 是return (y',y') :: IO (a,a) 所以这是正确的。a 和Int 统一起来,事情看起来不错。
      • @TomMD 感谢 Hindley-Milner 演练。
      • @gawi 你认识 Hindley-Milner 的事实似乎表明我不需要我的评论。哦,好吧;-)
      猜你喜欢
      • 1970-01-01
      • 2010-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多