Main.hs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. {-# LANGUAGE EmptyDataDecls #-}
  2. {-# LANGUAGE FlexibleContexts #-}
  3. {-# LANGUAGE FlexibleInstances #-}
  4. {-# LANGUAGE GADTs #-}
  5. {-# LANGUAGE GeneralizedNewtypeDeriving #-}
  6. {-# LANGUAGE MultiParamTypeClasses #-}
  7. {-# LANGUAGE OverloadedStrings #-}
  8. {-# LANGUAGE QuasiQuotes #-}
  9. {-# LANGUAGE RankNTypes #-}
  10. {-# LANGUAGE RecordWildCards #-}
  11. {-# LANGUAGE TemplateHaskell #-}
  12. {-# LANGUAGE TypeFamilies #-}
  13. {-# LANGUAGE ViewPatterns #-}
  14. {-# LANGUAGE LambdaCase #-}
  15. {-# LANGUAGE DeriveGeneric #-}
  16. {-# OPTIONS_GHC -fno-warn-orphans #-}
  17. module Main (main, resourcesApp, Widget, WorldId) where
  18. import Blaze.ByteString.Builder
  19. import Control.Applicative (liftA2)
  20. import Control.Concurrent (runInUnboundThread)
  21. import Control.Monad (replicateM, forM)
  22. import Control.Monad.Logger (runNoLoggingT)
  23. import Control.Monad.Primitive (PrimState)
  24. import Control.Monad.Reader (ReaderT)
  25. import Control.Monad.Trans.Resource (InternalState)
  26. import Data.Aeson (encode)
  27. import qualified Data.ByteString as BS
  28. import qualified Data.ByteString.Char8 as C8
  29. import Data.Pool (Pool, createPool)
  30. import Data.Int (Int64)
  31. import Data.IORef (newIORef)
  32. import Data.Function (on)
  33. import Data.List (sortBy)
  34. import Data.Pool (withResource)
  35. import Data.Text (Text)
  36. import Database.Persist (Key, PersistEntity,
  37. PersistEntityBackend,
  38. PersistStore, get, update,
  39. (=.))
  40. import qualified Database.Persist.Postgresql as Pg
  41. import Database.Persist.Sql
  42. import Database.Persist.TH (mkPersist, mpsGeneric,
  43. persistLowerCase, sqlSettings)
  44. import Network (PortID (PortNumber))
  45. import Network.HTTP.Types
  46. import Network.Wai
  47. import qualified Network.Wai.Handler.Warp as Warp
  48. import System.Environment (getArgs)
  49. import System.IO.Unsafe (unsafePerformIO)
  50. import qualified System.Random.MWC as R
  51. import Text.Blaze.Html.Renderer.Utf8 (renderHtmlBuilder)
  52. import Text.Blaze.Html
  53. import Yesod
  54. import Data.Text.Read
  55. import Data.Maybe (fromJust)
  56. mkPersist sqlSettings { mpsGeneric = True } [persistLowerCase|
  57. World sql=World
  58. randomNumber Int sql=randomnumber
  59. |]
  60. mkPersist sqlSettings { mpsGeneric = True } [persistLowerCase|
  61. Fortune sql=Fortune
  62. message Text sql=message
  63. |]
  64. instance ToJSON (Entity World) where
  65. toJSON (Entity wId wRow) = object [
  66. "id" .= wId
  67. ,"randomNumber" .= (worldRandomNumber wRow)
  68. ]
  69. instance ToMarkup FortuneId where
  70. toMarkup = toMarkup . fromSqlKey
  71. data App = App
  72. { appGen :: !(R.Gen (PrimState IO))
  73. , appDbPool :: !(Pool Pg.SqlBackend)
  74. }
  75. -- | Not actually using the non-raw mongoDB.
  76. -- persistent-mongoDB expects a field of '_id', not 'id'
  77. -- mkYesod "App" [parseRoutes|
  78. -- /json JsonR GET
  79. -- /db DbR GET
  80. -- /dbs/#Int DbsR GET
  81. -- !/dbs/#Text DbsDefaultR GET
  82. -- /mongo/raw/db MongoRawDbR GET
  83. -- /mongo/raw/dbs/#Int MongoRawDbsR GET
  84. -- !/mongo/raw/dbs/#Text MongoRawDbsDefaultR GET
  85. -- /updates/#Int UpdatesR GET
  86. -- !/updates/#Text UpdatesDefaultR GET
  87. -- /fortunes FortunesR GET
  88. -- /plaintext PlaintextR GET
  89. -- |]
  90. mkYesod "App" [parseRoutes|
  91. /json JsonR GET
  92. /plaintext PlaintextR GET
  93. /db DbR GET
  94. /queries/#Int QueriesR GET
  95. !/queries/#Text DefaultQueriesR GET
  96. /fortunes FortunesR GET
  97. /updates/#Int UpdatesR GET
  98. !/updates/#Text DefaultUpdatesR GET
  99. |]
  100. fakeInternalState :: InternalState
  101. fakeInternalState = unsafePerformIO $ newIORef $ error "fakeInternalState forced"
  102. {-# NOINLINE fakeInternalState #-}
  103. instance Yesod App where
  104. makeSessionBackend _ = return Nothing
  105. {-# INLINE makeSessionBackend #-}
  106. shouldLog _ _ _ = False
  107. {-# INLINE shouldLog #-}
  108. yesodMiddleware = id
  109. {-# INLINE yesodMiddleware #-}
  110. cleanPath _ = Right
  111. {-# INLINE cleanPath #-}
  112. yesodWithInternalState _ _ = ($ fakeInternalState)
  113. {-# INLINE yesodWithInternalState #-}
  114. maximumContentLength _ _ = Nothing
  115. {-# INLINE maximumContentLength #-}
  116. getJsonR :: Handler Value
  117. getJsonR = returnJson $ object ["message" .= ("Hello, World!" :: Text)]
  118. runPg dbAction = do
  119. app <- getYesod
  120. runSqlPool dbAction (appDbPool app)
  121. getRandomRow = do
  122. app <- getYesod
  123. randomNumber <- liftIO $ ((R.uniformR (1, 10000) (appGen app)) :: IO Int)
  124. let wId = (toSqlKey $ fromIntegral randomNumber) :: WorldId
  125. get wId >>= \case
  126. Nothing -> return Nothing
  127. Just x -> return $ Just (Entity wId x)
  128. getDbR :: Handler Value
  129. getDbR = do
  130. (runPg getRandomRow) >>= \case
  131. -- TODO: Throw appropriate HTTP response
  132. Nothing -> error "This shouldn't be happening"
  133. Just worldE -> returnJson worldE
  134. getQueriesR :: Int -> Handler Value
  135. getQueriesR cnt = do
  136. resultMaybe <- (runPg $ forM [1..sanitizedCnt] (\_ -> getRandomRow))
  137. let result = map fromJust resultMaybe
  138. returnJson result
  139. where
  140. sanitizedCnt
  141. | cnt<1 = 1
  142. | cnt>500 = 500
  143. | otherwise = cnt
  144. getDefaultQueriesR :: Text -> Handler Value
  145. getDefaultQueriesR txt = getQueriesR 1
  146. getFortunesR :: Handler Html
  147. getFortunesR = do
  148. fortunesFromDb <- runPg $ selectList [] []
  149. let fortunes = sortBy (compare `on` fortuneMessage . entityVal) $ (Entity (toSqlKey 0) Fortune{fortuneMessage="Additional fortune added at request time."}):fortunesFromDb
  150. defaultLayout $ do
  151. setTitle "Fortunes"
  152. [whamlet|
  153. <table>
  154. <tr>
  155. <th>id
  156. <th>message
  157. $forall fortune <- fortunes
  158. <tr>
  159. <td>#{entityKey fortune}
  160. <td>#{fortuneMessage $ entityVal fortune}
  161. |]
  162. getUpdatesR :: Int -> Handler Value
  163. getUpdatesR cnt = do
  164. worldRows <- runPg $ forM [1..sanitizedCount] (\_ -> fmap fromJust getRandomRow)
  165. app <- getYesod
  166. updatedWorldRows <- runPg $ mapM (replaceWorldRow app) worldRows
  167. returnJson updatedWorldRows
  168. where
  169. sanitizedCount
  170. | cnt<1 = 1
  171. | cnt>500 = 500
  172. | otherwise = cnt
  173. replaceWorldRow app (Entity wId wRow) = do
  174. randomNumber <- liftIO $ ((R.uniformR (1, 10000) (appGen app)) :: IO Int)
  175. -- TODO: Should I be using replace, or update, or updateGet -- which is
  176. -- idiomatic Yesod code for this operation?
  177. let newRow =wRow{worldRandomNumber=randomNumber}
  178. replace wId newRow
  179. return (Entity wId newRow)
  180. getDefaultUpdatesR :: Text -> Handler Value
  181. getDefaultUpdatesR _ = getUpdatesR 1
  182. -- Getmongorawdbr :: Handler Value
  183. -- getMongoRawDbR = getDb rawMongoIntQuery
  184. -- getDbsR :: Int -> Handler Value
  185. -- getDbsR cnt = do
  186. -- App {..} <- getYesod
  187. -- multiRandomHandler randomNumber (intQuery runMySQL My.toSqlKey) cnt'
  188. -- where
  189. -- cnt' | cnt < 1 = 1
  190. -- | cnt > 500 = 500
  191. -- | otherwise = cnt
  192. -- getDbsDefaultR :: Text -> Handler Value
  193. -- getDbsDefaultR _ = getDbsR 1
  194. -- getMongoRawDbsR :: Int -> Handler Value
  195. -- getMongoRawDbsR cnt = multiRandomHandler randomNumber rawMongoIntQuery cnt'
  196. -- where
  197. -- cnt' | cnt < 1 = 1
  198. -- | cnt > 500 = 500
  199. -- | otherwise = cnt
  200. -- getMongoRawDbsDefaultR :: Text -> Handler Value
  201. -- getMongoRawDbsDefaultR _ = getMongoRawDbsR 1
  202. -- getUpdatesR :: Int -> Handler Value
  203. -- getUpdatesR cnt = multiRandomHandler randomPair go cnt'
  204. -- where
  205. -- cnt' | cnt < 1 = 1
  206. -- | cnt > 500 = 500
  207. -- | otherwise = cnt
  208. -- go = uncurry (intUpdate runMySQL My.toSqlKey)
  209. -- getUpdatesDefaultR :: Text -> Handler Value
  210. -- getUpdatesDefaultR _ = getUpdatesR 1
  211. -- randomNumber :: R.Gen (PrimState IO) -> IO Int64
  212. -- randomNumber appGen = R.uniformR (1, 10000) appGen
  213. -- randomPair :: R.Gen (PrimState IO) -> IO (Int64, Int64)
  214. -- randomPair appGen = liftA2 (,) (randomNumber appGen) (randomNumber appGen)
  215. -- getDb :: (Int64 -> Handler Value) -> Handler Value
  216. -- getDb query = do
  217. -- app <- getYesod
  218. -- i <- liftIO (randomNumber (appGen app))
  219. -- value <- query i
  220. -- sendWaiResponse
  221. -- $ responseBuilder
  222. -- status200
  223. -- [("Content-Type", simpleContentType typeJson)]
  224. -- $ copyByteString
  225. -- $ L.toSfortfortunestrict
  226. -- $ encode value
  227. -- runMongoDB :: Mongo.Action Handler b -> Handler b
  228. -- runMongoDB f = do
  229. -- App {..} <- getYesod
  230. -- withResource mongoDBPool $ \pipe ->
  231. -- Mongo.access pipe Mongo.ReadStaleOk "hello_world" f
  232. -- runMySQL :: My.SqlPersistT Handler b -> Handler b
  233. -- runMySQL f = do
  234. -- App {..} <- getYesod
  235. -- My.runSqlPool f mySqlPool
  236. -- intQuery :: (MonadIO m, PersistEntity val, PersistStore backend
  237. -- , backend ~ PersistEntityBackend val
  238. -- ) =>
  239. -- (ReaderT backend m (Maybe val) -> m (Maybe (WorldGeneric backend)))
  240. -- -> (Int64 -> Key val)
  241. -- -> Int64 -> m Value
  242. -- intQuery db toKey i = do
  243. -- Just x <- db $ get $ toKey i
  244. -- return $ jsonResult (worldRandomNumber x)
  245. -- where
  246. -- jsonResult :: Int -> Value
  247. -- jsonResult n = object ["id" .= i, "randomNumber" .= n]
  248. -- rawMongoIntQuery :: Mongo.Val v => v -> Handler Value
  249. -- rawMongoIntQuery i = do
  250. -- Just x <- runMongoDB $ Mongo.findOne (Mongo.select ["id" =: i] "World")
  251. -- return $ documentToJson x
  252. -- intUpdate :: (Functor m, Monad m, MonadIO m
  253. -- , PersistStore backend) =>
  254. -- (ReaderT backend m (Maybe (WorldGeneric backend))
  255. -- -> m (Maybe (WorldGeneric backend)))
  256. -- -> (Int64 -> Key (WorldGeneric backend))
  257. -- -> Int64 -> Int64 -> m Value
  258. -- intUpdate db toKey i v = do
  259. -- Just x <- db $ get k
  260. -- _ <- db $ fmap (const Nothing) $
  261. -- update k [WorldRandomNumber =. fromIntegral v]
  262. -- return $ object ["id" .= i, "randomNumber" .= v]
  263. -- where
  264. -- k = toKey i
  265. -- multiRandomHandler :: ToJSON a
  266. -- => (R.Gen (PrimState IO) -> IO b)
  267. -- -> (b -> Handler a)
  268. -- -> Int
  269. -- -> Handler Value
  270. -- multiRandomHandler rand operation cnt = do
  271. -- App {..} <- getYesod
  272. -- nums <- liftIO $ replicateM cnt (rand appGen)
  273. -- return . array =<< mapM operation nums
  274. -- documentToJson :: [Field] -> Value
  275. -- documentToJson = object . map toAssoc
  276. -- where
  277. -- toAssoc :: Field -> (Text, Value)
  278. -- toAssoc ("_id" := v) = ("id", toJSON v)
  279. -- toAssoc (l := v) = (l, toJSON v)
  280. -- instance ToJSON Mongo.Value where
  281. -- toJSON (Mongo.Int32 i) = toJSON i
  282. -- toJSON (Mongo.Int64 i) = toJSON i
  283. -- toJSON (Mongo.Float f) = toJSON f
  284. -- toJSON (Mongo.Doc d) = documentToJson d
  285. -- toJSON s = error $ "no convert for: " ++ show s
  286. -- getFortunesR :: Handler ()
  287. -- getFortunesR = do
  288. -- es <- runMySQL $ My.selectList [] []
  289. -- sendWaiResponse
  290. -- $ responseBuilder status200 [("Content-type", typeHtml)]
  291. -- $ fortuneTemplate (messages es)
  292. -- where
  293. -- messages es = sortBy (compare `on` snd)
  294. -- ((0, "Additional fortune added at request time.") : map stripEntity es)
  295. -- stripEntity e =
  296. -- (My.fromSqlKey (My.entityKey e), fortuneMessage . My.entityVal $ e)
  297. getPlaintextR :: Handler Text
  298. getPlaintextR = return "Hello, World!"
  299. -- sendWaiResponse
  300. -- $ responseBuilder
  301. -- status200
  302. -- [("Content-Type", simpleContentType typePlain)]
  303. -- $ copyByteString
  304. -- fortuneTemplate :: [(Int64, Text)] -> Builder
  305. -- fortuneTemplate messages = renderHtmlBuilder $ [shamlet|
  306. -- $doctype 5
  307. -- <html>
  308. -- <head>
  309. -- <title>Fortunes
  310. -- <body>
  311. -- <table>
  312. -- <tr>
  313. -- <th>id
  314. -- <th>message
  315. -- $forall message <- messages
  316. -- <tr>
  317. -- <td>#{fst message}
  318. -- <td>#{snd message}
  319. -- |]
  320. main :: IO ()
  321. main = R.withSystemRandom $ \gen -> do
  322. [cores, host] <- getArgs
  323. let connString = ("host=" ++ host ++ " port=5432 user=benchmarkdbuser password=benchmarkdbpass dbname=hello_world")
  324. dbPool <- runNoLoggingT $ Pg.createPostgresqlPool (C8.pack connString) 256
  325. app <- toWaiAppPlain App
  326. { appGen = gen
  327. , appDbPool = dbPool
  328. }
  329. runInUnboundThread $ Warp.runSettings
  330. ( Warp.setPort 8000
  331. $ Warp.setHost "*"
  332. $ Warp.setOnException (\_ _ -> return ())
  333. Warp.defaultSettings
  334. ) app