使用Heist模板中不是来自应用程序monad的值

我试图用Happstack,Heist和web-routes编写一个应用服务器,但在解决如何让拼接访问不是源自我的应用程序的monad堆栈的值时遇到困难。

出现这种情况有两种情况:

  • 通过网络路由从URL路径中提取参数。 当将请求路由到正确的处理程序时,这些来自类型安全URL上的模式匹配。
  • 会话信息。 如果请求是针对全新会话的,则我无法从请求中的cookie中读取会话标识符(因为尚未存在此类cookie),并且如果需要,我无法使用拼接创建新会话,从那以后,如果有多个拼接尝试去做,我会为单个请求创建多个新会话。 但是,如果我在进入网络路由之前创建会话,会话将存在于应用程序monad之外。
  • 考虑下面的示例程序,尝试提供以下URL:

  • /阶乘/ n输出n的阶乘
  • / reverse / str向后输出str
  • 由于该参数出现在URL路径而不是查询字符串中,因此它将通过Web路由而不是来自ServerPartT monad提取。 但是,从那里,没有明确的方法可以将参数放置在拼接可以看到的地方,因为它们只能访问应用程序monad。

    将ReaderT粘贴在monad堆栈上的明显解决方案有两个问题:

  • 让ReaderT在ServerPartT之上隐藏Monad堆栈的Happstack部分,因为ReaderT不会实现ServerMonad,FilterMonad等。
  • 它假设我所服务的所有页面都采用相同类型的参数,但在此示例中,/ factorial需要一个Int,但/ reverse需要一个String。 但是,对于两个页面处理程序使用相同的TemplateDirectory,ReaderT需要携带相同类型的值。
  • 从Snap文档中查看,它看起来像Snap通过有效地将它们复制到查询字符串中来处理URL路径中的参数,这避开了问题。 但这不是Happstack和web-routes的选项,另外,有两种不同的方式来指定相同的值,这在安全方面是一个坏主意。

    那么,是否有一种“适当”的方式将非应用程序 - monad请求数据暴露给拼接,还是我需要放弃Heist并使用Blaze-HTML之类的东西来代替这个问题? 我觉得我错过了一些明显的东西,但无法弄清楚它可能是什么。

    示例代码:

    {-# LANGUAGE TemplateHaskell #-}
    
    import Prelude hiding ((.))
    
    import Control.Category ((.))
    import Happstack.Server (Response, ServerPartT, nullConf, ok, simpleHTTP)
    import Happstack.Server.Heist (render)
    import Text.Boomerang.TH (derivePrinterParsers)
    import Text.Templating.Heist (Splice, bindSplices, emptyTemplateState, getParamNode)
    import Text.Templating.Heist.TemplateDirectory (TemplateDirectory, newTemplateDirectory')
    import Web.Routes (RouteT, Site, runRouteT)
    import Web.Routes.Boomerang (Router, anyString, boomerangSite, int, lit, (<>), (</>))
    import Web.Routes.Happstack (implSite)
    
    import qualified Data.ByteString.Char8 as C
    import qualified Data.Text as T
    import qualified Text.XmlHtml as X
    
    data Sitemap = Factorial Int
                 | Reverse String
    
    $(derivePrinterParsers ''Sitemap)
    
    -- Conversion between type-safe URLs and URL strings.
    sitemap :: Router Sitemap
    sitemap = rFactorial . (lit "factorial" </> int)
           <> rReverse . (lit "reverse" </> anyString)
    
    -- Serve a page for each type-safe URL.
    route :: TemplateDirectory (RouteT Sitemap (ServerPartT IO)) -> Sitemap -> RouteT Sitemap (ServerPartT IO) Response
    route templates url = case url of
                            Factorial _num -> render templates (C.pack "factorial") >>= ok
                            Reverse _str   -> render templates (C.pack "reverse") >>= ok
    
    site :: TemplateDirectory (RouteT Sitemap (ServerPartT IO)) -> Site Sitemap (ServerPartT IO Response)
    site templates = boomerangSite (runRouteT $ route templates) sitemap
    
    -- <factorial>n</factorial> --> n!
    factorialSplice :: (Monad m) => Splice m
    factorialSplice = do input <- getParamNode
                         let n = read . T.unpack $ X.nodeText input :: Int
                         return [X.TextNode . T.pack . show $ product [1 .. n]]
    
    -- <reverse>text</reverse> --> reversed text
    reverseSplice :: (Monad m) => Splice m
    reverseSplice = do input <- getParamNode
                       return [X.TextNode . T.reverse $ X.nodeText input]
    
    main :: IO ()
    main = do templates <- newTemplateDirectory' path . bindSplices splices $ emptyTemplateState path
              simpleHTTP nullConf $ implSite "http://localhost:8000" "" $ site templates
        where splices = [(T.pack "factorial", factorialSplice), (T.pack "reverse", reverseSplice)]
              path = "."
    

    factorial.tpl:

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8"/>
            <title>Factorial</title>
        </head>
        <body>
            <p>The factorial of 6 is <factorial>6</factorial>.</p>
            <p>The factorial of ??? is ???.</p>
        </body>
    </html>
    

    reverse.tpl:

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="utf-8"/>
            <title>Reverse</title>
        </head>
        <body>
            <p>The reverse of "<tt>hello world</tt>" is "<tt><reverse>hello world</reverse></tt>".</p>
            <p>The reverse of "<tt>???</tt>" is "<tt>???</tt>".</p>
        </body>
    </html>
    

    考虑一下具有以下形式的函数:

    func :: a -> m b
    

    由于Haskell是纯粹的,并且具有强大的静态类型系统,因此此函数中使用的数据只能来自三个位置:范围或导入的全局符号,参数('a')和monad上下文'm'。 所以你描述的问题并不是Heist所特有的,这是使用Haskell的一个事实。

    这表明了解决您的问题的几种方法。 一种是将需要的数据作为参数传递给拼接功能。 像这样的东西:

    factorialSplice :: Int -> TemplateMonad (RouteT Sitemap (ServerPartT IO)) [X.Node]
    factorialSplice n = return [X.TextNode . T.pack . show $ product [1 .. n]]
    

    在Snap中,我们有一个名为renderWithSplices的函数,可以让您在渲染模板之前绑定一些拼接。 您可以使用像这样的函数来绑定当前具有“渲染模板”的行上的右侧拼接。

    第二种方法是使用底层monad。 你说:“没有明确的方法可以将参数放置在拼接件可以看到的地方,因为它们只能访问应用程序monad。” 在我看来,访问“应用程序monad”正是你需要将这些东西放在拼接中的东西。 所以我的第二个建议是使用它。 如果您使用的应用程序monad没有该数据,那么这是monad的缺陷,而不是海斯特问题。

    正如您在上面的类型签名中看到的那样,TemplateMonad是monad变换器,其下面的monad是(RouteT Sitemap(ServerPartT IO))。 这可以通过简单的提升来让拼接访问底层monad中的所有内容。 我从来没有使用网络路由,但在我看来,应该有一个RouteT功能来获得该网站地图。 我们假设存在以下功能:

    getUrlData :: RouteT url m url
    

    那么你应该可以写:

    factorialSplice :: TemplateMonad (RouteT Sitemap (ServerPartT IO)) [X.Node]
    factorialSplice = do
        url <- lift getUrlData
        return $ case url of
          Factorial n -> [X.TextNode . T.pack . show $ product [1 .. n]]
          _ -> []
    

    或者推广一下,你可以这样做:

    factorialArgSplice :: TemplateMonad (RouteT Sitemap (ServerPartT IO)) [X.Node]
    factorialArgSplice = do
        url <- lift getUrlData
        return $ case url of
          Factorial n -> [X.TextNode . T.pack . show $ n]
          _ -> []
    

    然后,您可以将它绑定到<factorialArg>标记,然后在模板中执行以下操作。

    <p>The factorial of <factorialArg> is <factorial><factorialArg/></factorial>.</p>
    
    链接地址: http://www.djcxy.com/p/55817.html

    上一篇: Using values not from the application monad with Heist templates

    下一篇: MSMQ listener with WCF?