/yesod-core/Yesod/Dispatch.hs

https://github.com/maxcan/yesod · Haskell · 190 lines · 144 code · 21 blank · 25 comment · 10 complexity · 498a3c85f5ac8acbb8e082f86b30ef73 MD5 · raw file

  1. {-# LANGUAGE TemplateHaskell #-}
  2. {-# LANGUAGE CPP #-}
  3. {-# LANGUAGE OverloadedStrings #-}
  4. module Yesod.Dispatch
  5. ( -- * Quasi-quoted routing
  6. parseRoutes
  7. , parseRoutesNoCheck
  8. , parseRoutesFile
  9. , parseRoutesFileNoCheck
  10. , mkYesod
  11. , mkYesodSub
  12. -- ** More fine-grained
  13. , mkYesodData
  14. , mkYesodSubData
  15. , mkYesodDispatch
  16. , mkYesodSubDispatch
  17. -- ** Path pieces
  18. , PathPiece (..)
  19. , PathMultiPiece (..)
  20. , Texts
  21. -- * Convert to WAI
  22. , toWaiApp
  23. , toWaiAppPlain
  24. ) where
  25. import Data.Functor ((<$>))
  26. import Prelude hiding (exp)
  27. import Yesod.Internal.Core
  28. import Yesod.Handler hiding (lift)
  29. import Yesod.Widget (GWidget)
  30. import Web.PathPieces
  31. import Language.Haskell.TH.Syntax
  32. import qualified Network.Wai as W
  33. import Network.Wai.Middleware.Gzip
  34. import Network.Wai.Middleware.Autohead
  35. import Data.ByteString.Lazy.Char8 ()
  36. import Web.ClientSession
  37. import Data.Text (Text)
  38. import Data.Text.Encoding (decodeUtf8With)
  39. import Data.Text.Encoding.Error (lenientDecode)
  40. import Data.Monoid (mappend)
  41. import qualified Data.ByteString as S
  42. import qualified Blaze.ByteString.Builder
  43. import Network.HTTP.Types (status301)
  44. import Yesod.Routes.TH
  45. import Yesod.Content (chooseRep)
  46. import Yesod.Routes.Parse
  47. type Texts = [Text]
  48. -- | Generates URL datatype and site function for the given 'Resource's. This
  49. -- is used for creating sites, /not/ subsites. See 'mkYesodSub' for the latter.
  50. -- Use 'parseRoutes' to create the 'Resource's.
  51. mkYesod :: String -- ^ name of the argument datatype
  52. -> [Resource String]
  53. -> Q [Dec]
  54. mkYesod name = fmap (uncurry (++)) . mkYesodGeneral name [] [] False
  55. -- | Generates URL datatype and site function for the given 'Resource's. This
  56. -- is used for creating subsites, /not/ sites. See 'mkYesod' for the latter.
  57. -- Use 'parseRoutes' to create the 'Resource's. In general, a subsite is not
  58. -- executable by itself, but instead provides functionality to
  59. -- be embedded in other sites.
  60. mkYesodSub :: String -- ^ name of the argument datatype
  61. -> Cxt
  62. -> [Resource String]
  63. -> Q [Dec]
  64. mkYesodSub name clazzes =
  65. fmap (uncurry (++)) . mkYesodGeneral name' rest clazzes True
  66. where
  67. (name':rest) = words name
  68. -- | Sometimes, you will want to declare your routes in one file and define
  69. -- your handlers elsewhere. For example, this is the only way to break up a
  70. -- monolithic file into smaller parts. Use this function, paired with
  71. -- 'mkYesodDispatch', to do just that.
  72. mkYesodData :: String -> [Resource String] -> Q [Dec]
  73. mkYesodData name res = mkYesodDataGeneral name [] False res
  74. mkYesodSubData :: String -> Cxt -> [Resource String] -> Q [Dec]
  75. mkYesodSubData name clazzes res = mkYesodDataGeneral name clazzes True res
  76. mkYesodDataGeneral :: String -> Cxt -> Bool -> [Resource String] -> Q [Dec]
  77. mkYesodDataGeneral name clazzes isSub res = do
  78. let (name':rest) = words name
  79. (x, _) <- mkYesodGeneral name' rest clazzes isSub res
  80. let rname = mkName $ "resources" ++ name
  81. eres <- lift res
  82. let y = [ SigD rname $ ListT `AppT` (ConT ''Resource `AppT` ConT ''String)
  83. , FunD rname [Clause [] (NormalB eres) []]
  84. ]
  85. return $ x ++ y
  86. -- | See 'mkYesodData'.
  87. mkYesodDispatch :: String -> [Resource String] -> Q [Dec]
  88. mkYesodDispatch name = fmap snd . mkYesodGeneral name [] [] False
  89. mkYesodSubDispatch :: String -> Cxt -> [Resource String] -> Q [Dec]
  90. mkYesodSubDispatch name clazzes = fmap snd . mkYesodGeneral name' rest clazzes True
  91. where (name':rest) = words name
  92. mkYesodGeneral :: String -- ^ foundation type
  93. -> [String]
  94. -> Cxt -- ^ classes
  95. -> Bool -- ^ is subsite?
  96. -> [Resource String]
  97. -> Q ([Dec], [Dec])
  98. mkYesodGeneral name args clazzes isSub resS = do
  99. let args' = map mkName args
  100. arg = foldl AppT (ConT name') $ map VarT args'
  101. let res = map (fmap parseType) resS
  102. renderRouteDec <- mkRenderRouteInstance arg res
  103. disp <- mkDispatchClause [|yesodRunner|] [|yesodDispatch|] [|fmap chooseRep|] res
  104. let master = mkName "master"
  105. let ctx = if isSub
  106. then ClassP (mkName "Yesod") [VarT master] : clazzes
  107. else []
  108. let ytyp = if isSub
  109. then ConT ''YesodDispatch `AppT` arg `AppT` VarT master
  110. else ConT ''YesodDispatch `AppT` arg `AppT` arg
  111. let yesodDispatch' =
  112. InstanceD ctx ytyp [FunD (mkName "yesodDispatch") [disp]]
  113. return (renderRouteDec : masterTypSyns, [yesodDispatch'])
  114. where
  115. name' = mkName name
  116. masterTypSyns
  117. | isSub = []
  118. | otherwise =
  119. [ TySynD
  120. (mkName "Handler")
  121. []
  122. (ConT ''GHandler `AppT` ConT name' `AppT` ConT name')
  123. , TySynD
  124. (mkName "Widget")
  125. []
  126. (ConT ''GWidget `AppT` ConT name' `AppT` ConT name' `AppT` TupleT 0)
  127. ]
  128. -- | Convert the given argument into a WAI application, executable with any WAI
  129. -- handler. This is the same as 'toWaiAppPlain', except it includes two
  130. -- middlewares: GZIP compression and autohead. This is the
  131. -- recommended approach for most users.
  132. toWaiApp :: ( Yesod master
  133. , YesodDispatch master master
  134. ) => master -> IO W.Application
  135. toWaiApp y = gzip (gzipSettings y) . autohead <$> toWaiAppPlain y
  136. -- | Convert the given argument into a WAI application, executable with any WAI
  137. -- handler. This differs from 'toWaiApp' in that it uses no middlewares.
  138. toWaiAppPlain :: ( Yesod master
  139. , YesodDispatch master master
  140. ) => master -> IO W.Application
  141. toWaiAppPlain a = toWaiApp' a <$> encryptKey a
  142. toWaiApp' :: ( Yesod master
  143. , YesodDispatch master master
  144. )
  145. => master
  146. -> Maybe Key
  147. -> W.Application
  148. toWaiApp' y key' env =
  149. case cleanPath y $ W.pathInfo env of
  150. Left pieces -> sendRedirect y pieces env
  151. Right pieces ->
  152. yesodDispatch y y id app404 handler405 method pieces key' env
  153. where
  154. app404 = yesodRunner notFound y y Nothing id
  155. handler405 route = yesodRunner badMethod y y (Just route) id
  156. method = decodeUtf8With lenientDecode $ W.requestMethod env
  157. sendRedirect :: Yesod master => master -> [Text] -> W.Application
  158. sendRedirect y segments' env =
  159. return $ W.responseLBS status301
  160. [ ("Content-Type", "text/plain")
  161. , ("Location", Blaze.ByteString.Builder.toByteString dest')
  162. ] "Redirecting"
  163. where
  164. dest = joinPath y (approot y) segments' []
  165. dest' =
  166. if S.null (W.rawQueryString env)
  167. then dest
  168. else (dest `mappend`
  169. Blaze.ByteString.Builder.fromByteString (W.rawQueryString env))