172 lines
5.7 KiB
Haskell
172 lines
5.7 KiB
Haskell
{-# LANGUAGE TypeFamilies #-}
|
|
{-# LANGUAGE TemplateHaskell #-}
|
|
module Yesod.Dispatch
|
|
( -- * Quasi-quoted routing
|
|
parseRoutes
|
|
, mkYesod
|
|
, mkYesodSub
|
|
-- * Convert to WAI
|
|
, toWaiApp
|
|
, basicHandler
|
|
) where
|
|
|
|
import Yesod.Handler
|
|
import Yesod.Response
|
|
import Yesod.Definitions
|
|
import Yesod.Yesod
|
|
import Yesod.Request
|
|
|
|
import Web.Routes.Quasi
|
|
import Language.Haskell.TH.Syntax
|
|
|
|
import qualified Network.Wai as W
|
|
import Network.Wai.Middleware.CleanPath
|
|
import Network.Wai.Middleware.ClientSession
|
|
import Network.Wai.Middleware.Jsonp
|
|
import Network.Wai.Middleware.MethodOverride
|
|
import Network.Wai.Middleware.Gzip
|
|
|
|
import qualified Network.Wai.Handler.SimpleServer as SS
|
|
import qualified Network.Wai.Handler.CGI as CGI
|
|
import System.Environment (getEnvironment)
|
|
|
|
import qualified Data.ByteString.Char8 as B
|
|
import Data.Maybe (fromMaybe)
|
|
import Web.Encodings (parseHttpAccept)
|
|
import Web.Mime
|
|
import Data.List (intercalate)
|
|
import Web.Routes (encodePathInfo, decodePathInfo)
|
|
|
|
mkYesod :: String -> [Resource] -> Q [Dec]
|
|
mkYesod name = mkYesodGeneral name [] False
|
|
|
|
mkYesodSub :: String -> [Name] -> [Resource] -> Q [Dec]
|
|
mkYesodSub name clazzes = mkYesodGeneral name clazzes True
|
|
|
|
explodeHandler :: HasReps c
|
|
=> GHandler sub master c
|
|
-> (Routes master -> String)
|
|
-> Routes sub
|
|
-> (Routes sub -> Routes master)
|
|
-> master
|
|
-> (master -> sub)
|
|
-> YesodApp
|
|
-> String
|
|
-> YesodApp
|
|
explodeHandler a b c d e f _ _ = runHandler a b (Just c) d e f
|
|
|
|
mkYesodGeneral :: String -> [Name] -> Bool -> [Resource] -> Q [Dec]
|
|
mkYesodGeneral name clazzes isSub res = do
|
|
let name' = mkName name
|
|
let tySyn = TySynInstD ''Routes [ConT $ name'] (ConT $ mkName $ name ++ "Routes")
|
|
let site = mkName $ "site" ++ name
|
|
let gsbod = NormalB $ VarE site
|
|
let yes' = FunD (mkName "getSite") [Clause [] gsbod []]
|
|
let yes = InstanceD [] (ConT ''YesodSite `AppT` ConT name') [yes']
|
|
explode <- [|explodeHandler|]
|
|
CreateRoutesResult x _ z <- createRoutes $ CreateRoutesSettings
|
|
{ crRoutes = mkName $ name ++ "Routes"
|
|
, crApplication = ConT ''YesodApp
|
|
, crArgument = ConT $ mkName name
|
|
, crExplode = explode
|
|
, crResources = res
|
|
, crSite = site
|
|
}
|
|
let master = if isSub
|
|
then VarT (mkName "master")
|
|
else ConT (mkName name)
|
|
murl = ConT ''Routes `AppT` master
|
|
sub = ConT $ mkName name
|
|
surl = ConT ''Routes `AppT` sub
|
|
let yType = ConT ''QuasiSite
|
|
`AppT` ConT ''YesodApp
|
|
`AppT` surl
|
|
`AppT` sub
|
|
`AppT` murl
|
|
`AppT` master
|
|
let ctx = if isSub
|
|
then map (\c -> ClassP c [master]) clazzes
|
|
else []
|
|
tvs = if isSub then [PlainTV $ mkName "master"] else []
|
|
let y' = SigD site $ ForallT tvs ctx yType
|
|
return $ (if isSub then id else (:) yes) $ [y', z, tySyn, x]
|
|
|
|
toWaiApp :: Yesod y => y -> IO W.Application
|
|
toWaiApp a = do
|
|
key' <- encryptKey a
|
|
let mins = clientSessionDuration a
|
|
return $ gzip
|
|
$ jsonp
|
|
$ methodOverride
|
|
$ cleanPath
|
|
$ \thePath -> clientsession encryptedCookies key' mins
|
|
$ toWaiApp' a thePath
|
|
|
|
toWaiApp' :: Yesod y
|
|
=> y
|
|
-> [B.ByteString]
|
|
-> [(B.ByteString, B.ByteString)]
|
|
-> W.Request
|
|
-> IO W.Response
|
|
toWaiApp' y resource session env = do
|
|
let site = getSite
|
|
method = B.unpack $ W.methodToBS $ W.requestMethod env
|
|
types = httpAccept env
|
|
pathSegments = filter (not . null) $ cleanupSegments resource
|
|
eurl = quasiParse site pathSegments
|
|
render u = approot y ++ '/'
|
|
: encodePathInfo (fixSegs $ quasiRender site u)
|
|
rr <- parseWaiRequest env session
|
|
onRequest y rr
|
|
print pathSegments -- FIXME remove
|
|
let ya = case eurl of
|
|
Nothing -> runHandler (errorHandler y NotFound)
|
|
render
|
|
Nothing
|
|
id
|
|
y
|
|
id
|
|
Just url -> quasiDispatch site
|
|
render
|
|
url
|
|
id
|
|
y
|
|
id
|
|
(badMethodApp method)
|
|
method
|
|
let eh er = runHandler (errorHandler y er) render eurl id y id
|
|
unYesodApp ya eh rr types >>= responseToWaiResponse
|
|
|
|
cleanupSegments :: [B.ByteString] -> [String]
|
|
cleanupSegments = decodePathInfo . intercalate "/" . map B.unpack
|
|
|
|
httpAccept :: W.Request -> [ContentType]
|
|
httpAccept = map contentTypeFromBS
|
|
. parseHttpAccept
|
|
. fromMaybe B.empty
|
|
. lookup W.Accept
|
|
. W.requestHeaders
|
|
|
|
-- | Runs an application with CGI if CGI variables are present (namely
|
|
-- PATH_INFO); otherwise uses SimpleServer.
|
|
basicHandler :: Int -- ^ port number
|
|
-> W.Application -> IO ()
|
|
basicHandler port app = do
|
|
vars <- getEnvironment
|
|
case lookup "PATH_INFO" vars of
|
|
Nothing -> do
|
|
putStrLn $ "http://localhost:" ++ show port ++ "/"
|
|
SS.run port app
|
|
Just _ -> CGI.run app
|
|
|
|
badMethodApp :: String -> YesodApp
|
|
badMethodApp m = YesodApp $ \eh req cts
|
|
-> unYesodApp (eh $ BadMethod m) eh req cts
|
|
|
|
fixSegs :: [String] -> [String]
|
|
fixSegs [] = []
|
|
fixSegs [x]
|
|
| any (== '.') x = [x]
|
|
| otherwise = [x, ""] -- append trailing slash
|
|
fixSegs (x:xs) = x : fixSegs xs
|