7

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>
4

1 に答える 1

4

次の形式の関数について考えてみます。

func :: a -> m b

Haskellは純粋で、強力な静的型システムを備えているため、この関数で使用されるデータは、スコープ内またはインポートされたグローバルシンボル、パラメーター('a')、およびモナドコンテキスト'm'の3か所からのみ取得できます。つまり、あなたが説明する問題はHeistに固有のものではなく、Haskellを使用しているという事実です。

これは、問題を解決するためのいくつかの方法を示唆しています。1つは、必要なデータを引数としてスプライス関数に渡すことです。このようなもの:

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

Snapには、テンプレートをレンダリングする直前にいくつかのスプライスをバインドできるrenderWithSplicesという関数があります。このような関数を使用して、現在「レンダリングテンプレート」がある行に正しいスプライスをバインドできます。

2番目のアプローチは、基礎となるモナドを使用することです。「スプライスはアプリケーションモナドにしかアクセスできないため、スプライスがパラメータを表示できる場所にパラメータを配置する明確な方法はありません」とあなたは言います。私の考えでは、「アプリケーションモナド」にアクセスできることは、まさにこのようなものをスプライス内に取り込むために必要なものです。したがって、私の2番目の提案はそれを使用することです。使用しているアプリケーションモナドにそのデータがない場合、それはそのモナドの欠陥であり、Heistの問題ではありません。

上記の型シグネチャでわかるように、TemplateMonadは、基になるモナドが(RouteTサイトマップ(ServerPartT IO))であるモナド変換子です。これにより、スプライスは簡単なリフトで下にあるモナドのすべてにアクセスできます。Webルートを使用したことはありませんが、そのサイトマップにアクセスするには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 に答える