Использование значений не из монады приложения с шаблонами Heist - PullRequest
7 голосов
/ 06 ноября 2011

Я пытаюсь написать сервер приложений с использованием Happstack, Heist и веб-маршрутов, но не могу понять, как разрешить соединениям доступ к значениям, которые не происходят из стека монад моего приложения.

Есть две ситуации, когда это происходит:

  • Параметры, извлеченные из пути URL через веб-маршруты.Это происходит из-за сопоставления с образцом в безопасном типе URL-адреса при маршрутизации запроса к соответствующему обработчику.
  • Информация о сеансе.Если запрос относится к новому сеансу, я не могу прочитать идентификатор сеанса из файла cookie в запросе (поскольку такого файла cookie еще не существует), и я не могу использовать соединения для создания нового сеанса, если это необходимо,с тех пор, если несколько попыток сделать это, я создаю несколько новых сессий для одного запроса.Но если я создаю сеанс до ввода материала веб-маршрутов, этот сеанс существует вне монады приложения.

Рассмотрим следующий пример программы, которая пытается обслуживать следующие URL-адреса:

  • / factorial / n выводит факториал n
  • / реверс / стр вывод стр назад

Поскольку параметр отображается в пути URL-адреса, а не в строке запроса, он извлекается через веб-маршруты, а не из монады ServerPartT.Оттуда, однако, нет четкого способа поместить параметр в такое место, где сращивания могут его видеть, поскольку они имеют доступ только к монаде приложения.

Очевидное решение поместить ReaderT где-то в стек монады:две проблемы:

  • Наличие ReaderT выше ServerPartT скрывает части Happstack стека монад, поскольку ReaderT не реализует ServerMonad, FilterMonad и т. д.
  • Предполагается, что все страницыЯ использую параметр того же типа, но в этом примере / factorial хочет Int, а / reverse хочет String.Но для того, чтобы оба обработчика страниц использовали один и тот же TemplateDirectory, ReaderT должен иметь значение одного и того же типа.

При взгляде на документацию Snap, похоже, что Snap обрабатывает параметры в URL-адресе.путем эффективного копирования их в строку запроса, которая обходит проблему.Но это не вариант с Happstack и веб-маршрутами, и, кроме того, наличие двух разных способов указания 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 = "."

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>

1 Ответ

4 голосов
/ 07 ноября 2011

Рассмотрим функцию следующего вида:

func :: a -> m b

Поскольку Haskell является чистым и имеет сильную статическую систему типов, данные, используемые в этой функции, могут поступать только из трех мест: глобальные символы, которые находятся в области видимости или импортируются, параметры ('a') и контекст монады ' м. Так что проблема, которую вы описываете, не уникальна для Heist, это факт использования Haskell.

Это предлагает несколько способов решения вашей проблемы. Одним из них является передача нужных вам данных в качестве аргументов вашим функциям сплайсинга. Как то так:

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

В Snap у нас есть функция с именем renderWithSplices , которая позволяет связывать некоторые соединения непосредственно перед рендерингом шаблона. Вы можете использовать функцию, подобную этой, чтобы связать правильное соединение на линии, где у вас есть «шаблоны рендеринга».

Второй подход - использование базовой монады. Вы говорите, что «нет четкого способа поместить параметр в такое место, где сращивания могут его видеть, поскольку они имеют доступ только к монаде приложения». На мой взгляд, наличие доступа к «монаде приложения» - это как раз то, что вам нужно, чтобы получить этот материал внутри сплайсов. Поэтому мое второе предложение - использовать это. Если используемая вами монада приложения не имеет этих данных, то это недостаток этой монады, а не проблема Heist.

Как видно из приведенной выше сигнатуры типа, TemplateMonad - это преобразователь монад, в котором находится основная монада (RouteT Sitemap (ServerPartT IO)). Это дает сплайс-доступ ко всему в основной монаде через простой лифт. Я никогда не использовал веб-маршруты, но мне кажется, что должна быть функция 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]
      _ -> []

Затем вы можете привязать это к тегу и сделать следующее в вашем шаблоне.

<p>The factorial of <factorialArg> is <factorial><factorialArg/></factorial>.</p>
...