我正在尝试使用 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>