Using non-monad values ​​with Heist templates

I am trying to write an application server using Happstack, Heist and web routes, but it is difficult for me to figure out how to allow connection access values ​​that do not come from my monad stack application.

There are two situations when this happens:

  • Parameters retrieved from the URL path via web routes. They come from pattern matching on type-safe URLs when routing the request to the appropriate handler.
  • Session Information If the request is for a completely new session, I cannot read the session identifier from the cookie in the request (since such a cookie does not exist yet), and I cannot use splice to create a new session, if necessary, since if more trying to do this, I am completing the creation of several new sessions for one request. But if I create a session before entering the web route stuff, the session exists outside the application monad.

Consider the following sample program that tries to serve the following URLs:

  • / factorial / n displays factorial n
  • / reverse / str displays str back

Because the parameter appears in the URL path instead of the query string, it is retrieved via web routes, and does not exit the ServerPartT monad. From there, however, there is no clear way to put the parameter somewhere where it can be seen, since they have access only to the application monad.

The obvious solution to stick ReaderT somewhere on the monad stack has two problems:

  • Having ReaderT over ServerPartT hides parts of the Happstack in the monad stack since ReaderT does not implement ServerMonad, FilterMonad, etc.
  • It is assumed that all the pages that I serve use the same parameter, but in this example / factorial wants Int, but / reverse wants String. But for both page handlers to use the same TemplateDirectory, ReaderT must have a value of the same type.

From peering into the Snap documentation, it looks like Snap is processing parameters in the URL path, effectively copying them to the query string, which wraps the problem. But this is not an option with Happstack and web routes, and besides, having two different URL methods to specify the same value, it seems to me that this is a bad security idea.

So, is there a “right” way to expose non-monad application request data for splicing, or do I need to abandon Heist and use something like Blaze-HTML instead if this is not a problem? I feel that I am missing something obvious, but I can’t understand what it can be.

Code example:

{-# 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> 
+7
source share
1 answer

Consider a function with the following form:

 func :: a -> mb 

Since Haskell is clean and has a strong static type system, the data used in this function can come from only three places: global characters that are in scope or imported, parameters ("a") and "monad context", m So the problem you are describing is not unique to Heist, it is the fact of using Haskell.

This offers a couple of ways to solve your problem. One of them is to pass the data you need as arguments to your splice functions. Something like that:

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

In Snap, we have a function called renderWithSplices that allows you to bind some splices right before rendering the template. You can use such a function to associate the correct splicing in a string where you currently have “visualization patterns”.

The second approach is to use the main monad. You say that "there is no clear way to put a parameter somewhere where splices see it, because they have access only to the application monad." In my opinion, access to the “application monad” is exactly what you need to get this material inside the splice. So my second suggestion is to use this. If the monad of the application you are using does not have this data, then this is the flaw of this monad, not the Heist problem.

As you can see from the above type signature, TemplateMonad is the monad transformer that contains the main monad (RouteT Sitemap (ServerPartT IO)). This makes it possible to merge into everything in the main monad with a simple lift. I have never used web routes, but it seems to me that there must be a RouteT function for this Sitemap. Suppose the following function exists:

 getUrlData :: RouteT url m url 

Then you can write:

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

Or, to summarize this a bit, you can do this:

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

You can then associate this with <factorialArg> and follow these steps in your template.

 <p>The factorial of <factorialArg> is <factorial><factorialArg/></factorial>.</p> 
+4
source

All Articles