2011-11-05 9 views
7

Sto provando a scrivere un server applicazioni utilizzando Happstack, Heist e percorsi web, ma sto avendo problemi a capire come consentire alle giunzioni di accedere a valori che non provengono da lo stack monad della mia applicazione.Utilizzo dei valori non dalla monade dell'applicazione con i modelli di Heist

ci sono due situazioni in cui questo viene up:

  • parametri estratti dal sentiero URL tramite web-percorsi. Questi provengono da pattern matching su un URL di sicurezza del tipo quando si esegue il routing della richiesta al gestore corretto.
  • Informazioni sulla sessione. Se la richiesta è per una sessione nuova di zecca, non posso leggere l'identificatore di sessione da un cookie nella richiesta (poiché non esiste ancora tale cookie), e non posso usare le giunzioni per creare una nuova sessione se necessario, da allora se più di uno splice tenta di farlo, finisco per creare più sessioni nuove per una singola richiesta. Ma se creo la sessione prima di entrare nella roba del web-routing, la sessione esiste al di fuori della monade dell'applicazione.

Si consideri il seguente esempio di programma che cerca di servire i seguenti URL:

  • /fattoriale/n uscite il fattoriale di n
  • /indietro/str uscite str indietro

Poiché il parametro viene visualizzato nel percorso URL anziché nella stringa di query, viene estratto tramite percorsi Web anziché venire dalla monade ServerPartT. Da lì, però, non esiste un modo chiaro per posizionare il parametro da qualche parte dove le giunzioni possono vederlo, poiché hanno solo accesso alla monade dell'applicazione.

La soluzione ovvia di attaccare un ReaderT qualche parte in pila monade ha due problemi:

  • Having a ReaderT sopra ServerPartT nasconde le parti Happstack della pila Monade, poiché ReaderT non implementa ServerMonad, FilterMonad, ecc.
  • Presuppone che tutte le pagine che sto servendo abbiano lo stesso tipo di parametro, ma in questo esempio,/factorial vuole un Int ma/reverse vuole una String. Ma per entrambi i gestori di pagine utilizzare la stessa TemplateDirectory, il ReaderT dovrebbe avere un valore dello stesso tipo.

dal dare una occhiata alla documentazione di Snap, sembra che Snap gestisce i parametri nel percorso URL copiandoli in modo efficace nella stringa di query, che elude il problema. Ma questa non è un'opzione con Happstack e le rotte del web, e inoltre, avere due modi diversi in cui un URL per specificare lo stesso valore mi sembra una cattiva idea per quanto riguarda la sicurezza.

Quindi, c'è un modo "corretto" per esporre i dati delle richieste di monade non dell'applicazione alle giunzioni, o devo abbandonare Heist e usare qualcosa come Blaze-HTML invece dove questo non è un problema? Mi sento come se mi mancasse qualcosa di ovvio, ma non riesco a capire cosa potrebbe essere.

codice Esempio:

{-# 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 = "." 

fattoriale.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> 

risposta

4

Consideriamo una funzione con la seguente forma:

func :: a -> m b 

Poiché Haskell è puro e ha un forte sistema di tipo statico, dati utilizzati in questa funzione può venire solo da tre punti: i simboli globali che sono in ambito o importati, i parametri (il 'a') e il contesto monade 'm'. Quindi il problema che descrivi non è unico per Heist, è un fatto dell'uso di Haskell.

Questo suggerisce un paio di modi per risolvere il problema. Uno è quello di passare i dati necessari come argomenti alle funzioni di splicing. Qualcosa di simile a questo:

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

In Snap abbiamo una funzione chiamata renderWithSplices che consente di associare alcuni giunti a destra prima di rendere il modello. Potresti usare una funzione come questa per legare la giusta giunzione sulla linea in cui attualmente hai "modelli di rendering".

Il secondo approccio utilizza la monade sottostante. Dici che "non esiste un modo chiaro per mettere il parametro da qualche parte dove gli splicing possono vederlo, dal momento che hanno solo accesso alla monade dell'applicazione". Nella mia mente, avere accesso alla "applicazione monade" è esattamente ciò di cui hai bisogno per ottenere questa roba all'interno delle giunzioni. Quindi il mio secondo suggerimento è di usarlo. Se la monade dell'applicazione che stai usando non ha quei dati, allora è una carenza di quella monade, non un problema di Heist.

Come si può vedere nella firma del tipo di cui sopra, TemplateMonad è un trasformatore monad in cui si trova la monade sottostante (RouteT Sitemap (ServerPartT IO)). Ciò fornisce alla giuntura l'accesso a tutto nella monade sottostante tramite un semplice sollevamento. Non ho mai usato percorsi web, ma mi sembra che ci dovrebbe essere una funzione RouteT per ottenere in quella Sitemap. Supponiamo che la seguente funzione esiste:

getUrlData :: RouteT url m url 

Poi si dovrebbe essere in grado di scrivere:

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 generalizzare un po ', si potrebbe fare questo:

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

Poi si potrebbe collegarlo al tag > fattorialee fare quanto segue nel modello.

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

Ho usato variazioni happstack personalizzato sul Snap 'rendWithSplices' qui: https://github.com/aslatter/blog/blob/master/Blog/Templates.hs In questo esempio, il 'appTemplates 'funzione se di tipo' App TemplateState '. –