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>