2011-11-05 9 views
7

Estoy intentando escribir un servidor de aplicaciones usando Happstack, Heist y rutas web, pero tengo problemas para descubrir cómo permitir que los empalmes accedan a valores que no se originan en la pila de mónadas de mi aplicación.Uso de valores que no provienen de la mónada de aplicación con plantillas de Heist

Hay dos situaciones en las que no sabe cómo proseguir:

  • parámetros extraídos de la ruta URL desde la web-rutas. Estos provienen de la coincidencia de patrones en una URL segura para tipos al enrutar la solicitud al controlador adecuado.
  • Información de la sesión. Si la solicitud es para una sesión completamente nueva, no puedo leer el identificador de sesión de una cookie en la solicitud (ya que aún no existe esa cookie), y no puedo usar los empalmes para crear una nueva sesión si es necesario, desde entonces, si más de un empalme intenta hacerlo, termino creando múltiples sesiones nuevas para una sola solicitud. Pero si creo la sesión antes de ingresar a las rutas web, la sesión existe fuera de la mónada de la aplicación.

Consideremos el siguiente programa de ejemplo que trata de servir a las siguientes direcciones URL:

  • /factorial/n salidas el factorial de n
  • /atrás/str salidas str hacia atrás

Dado que el parámetro aparece en la ruta de la URL en lugar de la cadena de consulta, se extrae a través de rutas web en lugar de proceder de la mónada ServerPartT. A partir de ahí, sin embargo, no hay una forma clara de colocar el parámetro en algún lugar donde los empalmes puedan verlo, ya que solo tienen acceso a la mónada de la aplicación.

La solución obvia de pegarse un ReaderT algún lugar de la pila mónada tiene dos problemas:

  • Tener un ReaderT anterior ServerPartT oculta las partes Happstack de la pila mónada, ya ReaderT no implementa ServerMonad, FilterMonad, etc.
  • Supone que todas las páginas que estoy publicando toman el mismo tipo de parámetro, pero en este ejemplo,/factorial quiere un Int pero/reverse quiere una Cadena. Pero para que ambos manejadores de página utilicen el mismo TemplateDirectory, el ReaderT necesitaría tener un valor del mismo tipo.

Al echar un vistazo a la documentación de Snap, parece que Snap maneja los parámetros en la ruta de URL al copiarlos efectivamente en la cadena de consulta, lo que elude el problema. Pero esa no es una opción con Happstack y las rutas web, y además, tener dos maneras diferentes para que una URL especifique el mismo valor me parece una mala idea para la seguridad.

Entonces, ¿existe una forma "adecuada" de exponer los datos de solicitud de mónada de aplicación a los empalmes, o tengo que abandonar Heist y usar algo como Blaze-HTML en su lugar, donde esto no es un problema? Siento que me falta algo obvio, pero no puedo entender qué podría ser.

código Ejemplo:

{-# 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> 

Respuesta

4

Considérese una función con la siguiente forma:

func :: a -> m b 

Debido Haskell es pura y tiene un sistema de tipo estático fuerte, los datos utilizados en esta función solo puede provenir de tres lugares: símbolos globales que están en el alcance o importados, los parámetros (la 'a') y el contexto de la mónada 'm'. Entonces, el problema que describes no es exclusivo de Heist, es un hecho de usar Haskell.

Esto sugiere un par de maneras de resolver su problema. Una es pasar los datos que necesita como argumentos para sus funciones de empalme. Algo como esto:

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

En Snap tenemos una función llamada renderWithSplices que le permite enlazar algunos empalmes justo antes de renderizar la plantilla. Puede usar una función como esta para vincular el empalme correcto en la línea donde actualmente tiene "plantillas de renderizado".

El segundo enfoque es usar la mónada subyacente. Usted dice que "no hay una forma clara de colocar el parámetro en algún lugar donde los empalmes puedan verlo, ya que solo tienen acceso a la mónada de la aplicación". En mi opinión, tener acceso a la "mónada de la aplicación" es exactamente lo que necesitas para obtener esto dentro de los empalmes. Entonces mi segunda sugerencia es usar eso. Si la mónada de la aplicación que está usando no tiene esa información, entonces es una deficiencia de esa mónada, no un problema de Heist.

Como puede ver en el tipo de firma anterior, TemplateMonad es un transformador de mónada donde la mónada subyacente es (RouteT Sitemap (ServerPartT IO)). Esto le da al empalme acceso a todo en la mónada subyacente a través de un simple levantamiento. Nunca he usado rutas web, pero me parece que debería haber una función RouteT para llegar a ese Sitemap. Supongamos que la siguiente función existe:

getUrlData :: RouteT url m url 

Entonces usted debe ser capaz de escribir:

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]] 
     _ -> [] 

O generalizar un poco, usted puede hacer esto:

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] 
     _ -> [] 

A continuación, podría vincularlo a la etiqueta <factorialArg> y hacer lo siguiente en su plantilla.

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

He usado variaciones a medida happstack con el snap '' rendWithSplices aquí: https://github.com/aslatter/blog/blob/master/Blog/Templates.hs En este ejemplo, los 'appTemplates 'function if of type' App TemplateState '. –

Cuestiones relacionadas