7

我正在尝试使用 Happstack、Heist 和 web-routes 编写应用程序服务器,但在弄清楚如何让拼接访问不是源自我的应用程序的 monad 堆栈的值时遇到了麻烦。

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

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

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

  • /factorial/ n输出 n 的阶乘
  • /reverse/ str向后输出str

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

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

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

从 Snap 文档看,Snap 似乎通过有效地将 URL 路径中的参数复制到查询字符串中来处理参数,从而回避了问题。但这不是 Happstack 和 web-routes 的选项,此外,有两种不同的方式让 URL 指定相同的值让我觉得安全方面是个坏主意。

那么,是否有一种“正确”的方式将非应用程序单子请求数据公开给拼接,或者我是否需要放弃 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 = "."

阶乘.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>

反向.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>
4

1 回答 1

4

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

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 的缺陷,而不是 Heist 问题。

正如您在上面的类型签名中看到的那样,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>
于 2011-11-07T17:36:30.117 回答