我正在尝试使用Happstack,Heist和Web路由编写应用程序服务器,但我很难弄清楚如何让拼接访问不是源自我的应用程序的monad堆栈的值。
有两种情况出现:
请考虑以下尝试提供以下网址的示例程序:
由于参数出现在URL路径而不是查询字符串中,因此它将通过Web路由提取,而不是来自ServerPartT monad。但是,从那里开始,没有明确的方法将参数放在拼接可以看到的位置,因为它们只能访问应用程序monad。
将一个ReaderT粘贴在monad堆栈上的明显解决方案有两个问题:
从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 = "."
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>
答案 0 :(得分:4)
考虑具有以下形式的函数:
func :: a -> m b
因为Haskell是纯粹的并且具有强大的静态类型系统,所以此函数中使用的数据只能来自三个位置:范围内或导入的全局符号,参数('a')和monad上下文' M”。所以你描述的问题并不是Heist独有的,这是使用Haskell的事实。
这表明有几种方法可以解决您的问题。一种是将您需要的数据作为参数传递给splice函数。像这样:
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的缺陷,而不是Heist问题。
正如您在上面的类型签名中所看到的,TemplateMonad是一个monad转换器,其中底层monad是(RouteT Sitemap(ServerPartT IO))。这使得拼接通过简单的提升访问底层monad中的所有内容。我从来没有使用过网络路线,但在我看来应该有一个RouteT功能来获取该Sitemap。我们假设存在以下函数:
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]
_ -> []
然后你可以将它绑定到&lt; factorialArg&gt;标记并在模板中执行以下操作。
<p>The factorial of <factorialArg> is <factorial><factorialArg/></factorial>.</p>