208 lines
7.9 KiB
Haskell
208 lines
7.9 KiB
Haskell
|
{-# LANGUAGE CPP #-}
|
||
|
{-# LANGUAGE RankNTypes #-}
|
||
|
{-# LANGUAGE NoImplicitPrelude #-}
|
||
|
{-# LANGUAGE OverloadedStrings #-}
|
||
|
{-# LANGUAGE TemplateHaskell #-}
|
||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||
|
{-# LANGUAGE TypeFamilies #-}
|
||
|
{-# LANGUAGE RecordWildCards #-}
|
||
|
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||
|
module Application
|
||
|
( getApplicationDev
|
||
|
, appMain
|
||
|
, develMain
|
||
|
, makeFoundation
|
||
|
, makeLogWare
|
||
|
-- * for DevelMain
|
||
|
, getApplicationRepl
|
||
|
, shutdownApp
|
||
|
-- * for GHCI
|
||
|
, handler
|
||
|
, db
|
||
|
) where
|
||
|
|
||
|
#if MIN_VERSION_base(4,9,0)
|
||
|
import Control.Concurrent (forkOSWithUnmask)
|
||
|
#else
|
||
|
import GHC.IO (unsafeUnmask)
|
||
|
#endif
|
||
|
import Control.Monad.Logger (liftLoc, runLoggingT)
|
||
|
import Database.Persist.MySQL (createMySQLPool, myConnInfo,
|
||
|
myPoolSize, runSqlPool)
|
||
|
import qualified Database.MySQL.Base as MySQL
|
||
|
import Import
|
||
|
import Language.Haskell.TH.Syntax (qLocation)
|
||
|
import Network.Wai (Middleware)
|
||
|
import Network.Wai.Handler.Warp (Settings, defaultSettings,
|
||
|
defaultShouldDisplayException,
|
||
|
runSettings, setHost,
|
||
|
setFork, setOnOpen, setOnClose,
|
||
|
setOnException, setPort, getPort)
|
||
|
import Network.Wai.Middleware.RequestLogger (Destination (Logger),
|
||
|
IPAddrSource (..),
|
||
|
OutputFormat (..), destination,
|
||
|
mkRequestLogger, outputFormat)
|
||
|
import System.Log.FastLogger (defaultBufSize, newStdoutLoggerSet,
|
||
|
toLogStr)
|
||
|
|
||
|
-- Import all relevant handler modules here.
|
||
|
-- Don't forget to add new modules to your cabal file!
|
||
|
import Handler.Common
|
||
|
import Handler.Home
|
||
|
|
||
|
-- This line actually creates our YesodDispatch instance. It is the second half
|
||
|
-- of the call to mkYesodData which occurs in Foundation.hs. Please see the
|
||
|
-- comments there for more details.
|
||
|
mkYesodDispatch "App" resourcesApp
|
||
|
|
||
|
-- | This function allocates resources (such as a database connection pool),
|
||
|
-- performs initialization and returns a foundation datatype value. This is also
|
||
|
-- the place to put your migrate statements to have automatic database
|
||
|
-- migrations handled by Yesod.
|
||
|
makeFoundation :: AppSettings -> IO App
|
||
|
makeFoundation appSettings = do
|
||
|
-- Some basic initializations: HTTP connection manager, logger, and static
|
||
|
-- subsite.
|
||
|
appHttpManager <- newManager
|
||
|
appLogger <- newStdoutLoggerSet defaultBufSize >>= makeYesodLogger
|
||
|
appStatic <-
|
||
|
(if appMutableStatic appSettings then staticDevel else static)
|
||
|
(appStaticDir appSettings)
|
||
|
|
||
|
-- See http://www.yesodweb.com/blog/2016/11/use-mysql-safely-in-yesod
|
||
|
MySQL.initLibrary
|
||
|
|
||
|
-- We need a log function to create a connection pool. We need a connection
|
||
|
-- pool to create our foundation. And we need our foundation to get a
|
||
|
-- logging function. To get out of this loop, we initially create a
|
||
|
-- temporary foundation without a real connection pool, get a log function
|
||
|
-- from there, and then create the real foundation.
|
||
|
let mkFoundation appConnPool = App {..}
|
||
|
-- The App {..} syntax is an example of record wild cards. For more
|
||
|
-- information, see:
|
||
|
-- https://ocharles.org.uk/blog/posts/2014-12-04-record-wildcards.html
|
||
|
tempFoundation = mkFoundation $ error "connPool forced in tempFoundation"
|
||
|
logFunc = messageLoggerSource tempFoundation appLogger
|
||
|
|
||
|
-- Create the database connection pool
|
||
|
pool <- flip runLoggingT logFunc $ createMySQLPool
|
||
|
(myConnInfo $ appDatabaseConf appSettings)
|
||
|
(myPoolSize $ appDatabaseConf appSettings)
|
||
|
|
||
|
-- Perform database migration using our application's logging settings.
|
||
|
runLoggingT (runSqlPool (runMigration migrateAll) pool) logFunc
|
||
|
|
||
|
-- Return the foundation
|
||
|
return $ mkFoundation pool
|
||
|
|
||
|
-- | Convert our foundation to a WAI Application by calling @toWaiAppPlain@ and
|
||
|
-- applying some additional middlewares.
|
||
|
makeApplication :: App -> IO Application
|
||
|
makeApplication foundation = do
|
||
|
logWare <- makeLogWare foundation
|
||
|
-- Create the WAI application and apply middlewares
|
||
|
appPlain <- toWaiAppPlain foundation
|
||
|
return $ logWare $ defaultMiddlewaresNoLogging appPlain
|
||
|
|
||
|
makeLogWare :: App -> IO Middleware
|
||
|
makeLogWare foundation =
|
||
|
mkRequestLogger def
|
||
|
{ outputFormat =
|
||
|
if appDetailedRequestLogging $ appSettings foundation
|
||
|
then Detailed True
|
||
|
else Apache
|
||
|
(if appIpFromHeader $ appSettings foundation
|
||
|
then FromFallback
|
||
|
else FromSocket)
|
||
|
, destination = Logger $ loggerSet $ appLogger foundation
|
||
|
}
|
||
|
|
||
|
#if ! MIN_VERSION_base(4,9,0)
|
||
|
forkOSWithUnmask :: ((forall a . IO a -> IO a) -> IO ()) -> IO ThreadId
|
||
|
forkOSWithUnmask io = forkOS (io unsafeUnmask)
|
||
|
#endif
|
||
|
|
||
|
-- | Warp settings for the given foundation value.
|
||
|
-- Use bound threads for thread-safe use of MySQL, and initialise and finalise
|
||
|
-- them: see http://www.yesodweb.com/blog/2016/11/use-mysql-safely-in-yesod
|
||
|
warpSettings :: App -> Settings
|
||
|
warpSettings foundation =
|
||
|
setPort (appPort $ appSettings foundation)
|
||
|
$ setHost (appHost $ appSettings foundation)
|
||
|
$ setOnException (\_req e ->
|
||
|
when (defaultShouldDisplayException e) $ messageLoggerSource
|
||
|
foundation
|
||
|
(appLogger foundation)
|
||
|
$(qLocation >>= liftLoc)
|
||
|
"yesod"
|
||
|
LevelError
|
||
|
(toLogStr $ "Exception from Warp: " ++ show e))
|
||
|
$ setFork (\x -> void $ forkOSWithUnmask x)
|
||
|
$ setOnOpen (const $ MySQL.initThread >> return True)
|
||
|
$ setOnClose (const MySQL.endThread)
|
||
|
defaultSettings
|
||
|
|
||
|
-- | For yesod devel, return the Warp settings and WAI Application.
|
||
|
getApplicationDev :: IO (Settings, Application)
|
||
|
getApplicationDev = do
|
||
|
settings <- getAppSettings
|
||
|
foundation <- makeFoundation settings
|
||
|
wsettings <- getDevSettings $ warpSettings foundation
|
||
|
app <- makeApplication foundation
|
||
|
return (wsettings, app)
|
||
|
|
||
|
getAppSettings :: IO AppSettings
|
||
|
getAppSettings = loadYamlSettings [configSettingsYml] [] useEnv
|
||
|
|
||
|
-- | main function for use by yesod devel
|
||
|
develMain :: IO ()
|
||
|
develMain = develMainHelper getApplicationDev
|
||
|
|
||
|
-- | The @main@ function for an executable running this site.
|
||
|
appMain :: IO ()
|
||
|
appMain = do
|
||
|
-- Get the settings from all relevant sources
|
||
|
settings <- loadYamlSettingsArgs
|
||
|
-- fall back to compile-time values, set to [] to require values at runtime
|
||
|
[configSettingsYmlValue]
|
||
|
|
||
|
-- allow environment variables to override
|
||
|
useEnv
|
||
|
|
||
|
-- Generate the foundation from the settings
|
||
|
foundation <- makeFoundation settings
|
||
|
|
||
|
-- Generate a WAI Application from the foundation
|
||
|
app <- makeApplication foundation
|
||
|
|
||
|
-- Run the application with Warp
|
||
|
runSettings (warpSettings foundation) app
|
||
|
|
||
|
|
||
|
--------------------------------------------------------------
|
||
|
-- Functions for DevelMain.hs (a way to run the app from GHCi)
|
||
|
--------------------------------------------------------------
|
||
|
getApplicationRepl :: IO (Int, App, Application)
|
||
|
getApplicationRepl = do
|
||
|
settings <- getAppSettings
|
||
|
foundation <- makeFoundation settings
|
||
|
wsettings <- getDevSettings $ warpSettings foundation
|
||
|
app1 <- makeApplication foundation
|
||
|
return (getPort wsettings, foundation, app1)
|
||
|
|
||
|
shutdownApp :: App -> IO ()
|
||
|
shutdownApp _ = return ()
|
||
|
|
||
|
|
||
|
---------------------------------------------
|
||
|
-- Functions for use in development with GHCi
|
||
|
---------------------------------------------
|
||
|
|
||
|
-- | Run a handler
|
||
|
handler :: Handler a -> IO a
|
||
|
handler h = getAppSettings >>= makeFoundation >>= flip unsafeHandler h
|
||
|
|
||
|
-- | Run DB queries
|
||
|
db :: ReaderT SqlBackend (HandlerT App IO) a -> IO a
|
||
|
db = handler . runDB
|