123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- {-# LANGUAGE EmptyDataDecls #-}
- {-# LANGUAGE FlexibleContexts #-}
- {-# LANGUAGE FlexibleInstances #-}
- {-# LANGUAGE GADTs #-}
- {-# LANGUAGE GeneralizedNewtypeDeriving #-}
- {-# LANGUAGE MultiParamTypeClasses #-}
- {-# LANGUAGE OverloadedStrings #-}
- {-# LANGUAGE QuasiQuotes #-}
- {-# LANGUAGE RankNTypes #-}
- {-# LANGUAGE RecordWildCards #-}
- {-# LANGUAGE TemplateHaskell #-}
- {-# LANGUAGE TypeFamilies #-}
- {-# LANGUAGE ViewPatterns #-}
- {-# LANGUAGE LambdaCase #-}
- {-# LANGUAGE DeriveGeneric #-}
- {-# OPTIONS_GHC -fno-warn-orphans #-}
- module Main (main, resourcesApp, Widget, WorldId) where
- import Control.Concurrent (runInUnboundThread)
- import Control.Monad (forM)
- import Control.Monad.Logger (runNoLoggingT)
- import Control.Monad.Primitive (PrimState)
- import Control.Monad.Trans.Resource (InternalState)
- import qualified Data.ByteString.Char8 as C8
- import Data.Pool (Pool)
- import Data.IORef (newIORef)
- import Data.Function (on)
- import Data.List (sortBy)
- import Data.Text (Text)
- import Database.Persist
- import qualified Database.Persist.Postgresql as Pg
- import Database.Persist.Sql
- import Database.Persist.TH (mkPersist, mpsGeneric,
- persistLowerCase, sqlSettings)
- import qualified Network.Wai.Handler.Warp as Warp
- import System.Environment (getArgs)
- import System.IO.Unsafe (unsafePerformIO)
- import qualified System.Random.MWC as R
- import Text.Blaze.Html
- import Yesod
- import Data.Maybe (fromJust)
- mkPersist sqlSettings { mpsGeneric = True } [persistLowerCase|
- World sql=World
- randomNumber Int sql=randomnumber
- |]
- mkPersist sqlSettings { mpsGeneric = True } [persistLowerCase|
- Fortune sql=Fortune
- message Text sql=message
- |]
- instance ToJSON (Entity World) where
- toJSON (Entity wId wRow) = object [
- "id" .= wId
- ,"randomNumber" .= (worldRandomNumber wRow)
- ]
- instance ToMarkup FortuneId where
- toMarkup = toMarkup . fromSqlKey
- data App = App
- { appGen :: !(R.Gen (PrimState IO))
- , appDbPool :: !(Pool Pg.SqlBackend)
- }
- mkYesod "App" [parseRoutes|
- /json JsonR GET
- /plaintext PlaintextR GET
- /db DbR GET
- /queries/#Int QueriesR GET
- !/queries/#Text DefaultQueriesR GET
- /fortunes FortunesR GET
- /updates/#Int UpdatesR GET
- !/updates/#Text DefaultUpdatesR GET
- |]
- fakeInternalState :: InternalState
- fakeInternalState = unsafePerformIO $ newIORef $ error "fakeInternalState forced"
- {-# NOINLINE fakeInternalState #-}
- instance Yesod App where
- makeSessionBackend _ = return Nothing
- {-# INLINE makeSessionBackend #-}
- shouldLog _ _ _ = False
- {-# INLINE shouldLog #-}
- yesodMiddleware = id
- {-# INLINE yesodMiddleware #-}
- cleanPath _ = Right
- {-# INLINE cleanPath #-}
- yesodWithInternalState _ _ = ($ fakeInternalState)
- {-# INLINE yesodWithInternalState #-}
- maximumContentLength _ _ = Nothing
- {-# INLINE maximumContentLength #-}
- getJsonR :: Handler Value
- getJsonR = returnJson $ object ["message" .= ("Hello, World!" :: Text)]
- runPg dbAction = do
- app <- getYesod
- runSqlPool dbAction (appDbPool app)
- getRandomRow = do
- app <- getYesod
- randomNumber <- liftIO $ ((R.uniformR (1, 10000) (appGen app)) :: IO Int)
- let wId = (toSqlKey $ fromIntegral randomNumber) :: WorldId
- get wId >>= \case
- Nothing -> return Nothing
- Just x -> return $ Just (Entity wId x)
- getDbR :: Handler Value
- getDbR = do
- (runPg getRandomRow) >>= \case
- -- TODO: Throw appropriate HTTP response
- Nothing -> error "This shouldn't be happening"
- Just worldE -> returnJson worldE
- getQueriesR :: Int -> Handler Value
- getQueriesR cnt = do
- result <- (runPg $ forM [1..sanitizedCnt] (\_ -> fmap fromJust getRandomRow))
- returnJson result
- where
- sanitizedCnt
- | cnt<1 = 1
- | cnt>500 = 500
- | otherwise = cnt
- getDefaultQueriesR :: Text -> Handler Value
- getDefaultQueriesR _ = getQueriesR 1
- getFortunesR :: Handler Html
- getFortunesR = do
- fortunesFromDb <- runPg $ selectList [] []
- let fortunes = sortBy (compare `on` fortuneMessage . entityVal) $ (Entity (toSqlKey 0) Fortune{fortuneMessage="Additional fortune added at request time."}):fortunesFromDb
- defaultLayout $ do
- setTitle "Fortunes"
- [whamlet|
- <table>
- <tr>
- <th>id
- <th>message
- $forall fortune <- fortunes
- <tr>
- <td>#{entityKey fortune}
- <td>#{fortuneMessage $ entityVal fortune}
- |]
- getUpdatesR :: Int -> Handler Value
- getUpdatesR cnt = do
- worldRows <- runPg $ forM [1..sanitizedCount] (\_ -> fmap fromJust getRandomRow)
- app <- getYesod
- updatedWorldRows <- runPg $ mapM (replaceWorldRow app) worldRows
- returnJson updatedWorldRows
- where
- sanitizedCount
- | cnt<1 = 1
- | cnt>500 = 500
- | otherwise = cnt
- replaceWorldRow app (Entity wId wRow) = do
- randomNumber <- liftIO $ ((R.uniformR (1, 10000) (appGen app)) :: IO Int)
- -- TODO: Should I be using replace, or update, or updateGet -- which is
- -- idiomatic Yesod code for this operation?
- let newRow = wRow{worldRandomNumber=randomNumber}
- replace wId newRow
- return (Entity wId newRow)
- getDefaultUpdatesR :: Text -> Handler Value
- getDefaultUpdatesR _ = getUpdatesR 1
- getPlaintextR :: Handler Text
- getPlaintextR = return "Hello, World!"
- main :: IO ()
- main = R.withSystemRandom $ \gen -> do
- [cores, host] <- getArgs
- let connString = ("host=" ++ host ++ " port=5432 user=benchmarkdbuser password=benchmarkdbpass dbname=hello_world")
- dbPool <- runNoLoggingT $ Pg.createPostgresqlPool (C8.pack connString) 256
- app <- toWaiAppPlain App
- { appGen = gen
- , appDbPool = dbPool
- }
- runInUnboundThread $ Warp.runSettings
- ( Warp.setPort 8000
- $ Warp.setHost "*"
- $ Warp.setOnException (\_ _ -> return ())
- Warp.defaultSettings
- ) app
|