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

Pau*_*icz 7 haskell happstack heist

我正在尝试使用Happstack,Heist和Web路由编写应用程序服务器,但是我很难弄清楚如何让拼接访问不是源自我的应用程序的monad堆栈的值.

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

  • 通过Web路由从URL路径中提取的参数.当将请求路由到适当的处理程序时,这些来自类型安全URL上的模式匹配.
  • 会话信息.如果请求是针对一个全新的会话,我无法从请求中的cookie读取会话标识符(因为还没有这样的cookie),并且如果需要,我不能使用拼接来创建新会话,从那时起如果不止一个接头尝试这样做,我最终会为一个请求创建多个新会话.但是如果我在输入web-routes之前创建会话,则会话存在于应用程序monad之外.

请考虑以下尝试提供以下URL的示例程序:

  • /阶乘/ Ñ输出的阶乘Ñ
  • / reverse/str输出str向后

由于参数显示在URL路径而不是查询字符串中,因此它将通过Web路由而不是来自ServerPartT monad提取.但是,从那里开始,没有明确的方法将参数放在拼接可以看到的位置,因为它们只能访问应用程序monad.

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

  • 在ServerPartT上面有一个ReaderT隐藏了monad堆栈的Happstack部分,因为ReaderT没有实现ServerMonad,FilterMonad等.
  • 它假设我所服务的所有页面都使用相同类型的参数,但在此示例中,/ factorial需要Int但//reverse需要String.但是对于两个页面处理程序使用相同的TemplateDirectory,ReaderT需要携带相同类型的值.

从Snap文档中窥视,看起来Snap通过有效地将它们复制到查询字符串中来处理URL路径中的参数,从而回避问题.但这不是Happstack和网络路由的选择,此外,有两种不同的方式让URL指定相同的值会让我觉得安全性是一个坏主意.

那么,是否有一种"正确"的方式将非应用程序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 = "."
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

mig*_*yte 4

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

func :: a -> m b
Run Code Online (Sandbox Code Playgroud)

因为 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]]
Run Code Online (Sandbox Code Playgroud)

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

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

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

getUrlData :: RouteT url m url
Run Code Online (Sandbox Code Playgroud)

那么你应该能够写:

func :: a -> m b
Run Code Online (Sandbox Code Playgroud)

或者概括一下,你可以这样做:

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

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

getUrlData :: RouteT url m url
Run Code Online (Sandbox Code Playgroud)