Happstack、Heist、および Web ルートを使用してアプリケーション サーバーを作成しようとしていますが、スプライスがアプリケーションのモナド スタックに由来しない値にアクセスできるようにする方法がわかりません。
これが発生する状況は 2 つあります。
- Web ルート経由で URL パスから抽出されたパラメーター。これらは、リクエストを適切なハンドラーにルーティングする際に、タイプ セーフな URL でパターン マッチングを行うことによって生成されます。
- セッション情報。リクエストが新しいセッションに対するものである場合、リクエスト内の Cookie からセッション ID を読み取ることができず (そのような Cookie はまだ存在しないため)、必要に応じてスプライスを使用して新しいセッションを作成することはできません。それ以来、複数のスプライスがそれを行おうとすると、1 つの要求に対して複数の新しいセッションを作成することになります。しかし、web-routes に入る前にセッションを作成すると、セッションはアプリケーションモナドの外に存在します。
次の URL を提供しようとする次のサンプル プログラムについて考えてみます。
- /factorial/ nはnの階乗を出力します
- /reverse/ str はstrを後方に出力します
パラメータはクエリ文字列ではなく URL パスに表示されるため、ServerPartT モナドからではなく、Web ルートを介して抽出されます。ただし、そこから、スプライスがアプリケーションモナドにしかアクセスできないため、パラメーターをどこかに置く明確な方法はありません。
モナド スタックのどこかに ReaderT を貼り付けるという明白な解決策には、2 つの問題があります。
- ReaderT は ServerMonad や FilterMonad などを実装していないため、ServerPartT の上に ReaderT を配置すると、モナド スタックの Happstack 部分が隠されます。
- 私が提供しているすべてのページが同じタイプのパラメーターを取ることを前提としていますが、この例では、/factorial は Int を必要としていますが、/reverse は String を必要としています。ただし、両方のページ ハンドラーが同じ TemplateDirectory を使用するには、ReaderT が同じ型の値を保持している必要があります。
Snap のドキュメントを覗いてみると、Snap は URL パスのパラメーターを効果的にクエリ文字列にコピーすることで処理しているように見えます。これにより、問題が回避されます。しかし、これは Happstack と Web ルートのオプションではありません。さらに、URL に同じ値を指定する 2 つの異なる方法があることは、セキュリティ的に悪い考えだと思います。
では、アプリケーションモナド以外のリクエストデータをスプライスに公開する「適切な」方法はありますか?それとも、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>
逆.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>