1- {-# LANGUAGE LambdaCase #-}
2- {-# LANGUAGE NamedFieldPuns #-}
3- {-# LANGUAGE RecordWildCards #-}
4- {-# LANGUAGE RecursiveDo #-}
1+ {-# LANGUAGE LambdaCase #-}
2+ {-# LANGUAGE NamedFieldPuns #-}
3+ {-# LANGUAGE RecordWildCards #-}
4+ {-# LANGUAGE RecursiveDo #-}
5+ {-# LANGUAGE TupleSections #-}
6+ {-# LANGUAGE TypeApplications #-}
57
68module PostgREST.AppState
79 ( AppState
@@ -48,13 +50,14 @@ import Control.AutoUpdate (defaultUpdateSettings, mkAutoUpdate,
4850import Control.Retry (RetryPolicy , RetryStatus (.. ), capDelay ,
4951 exponentialBackoff , retrying ,
5052 rsPreviousDelay )
51- import Data.IORef (IORef , atomicWriteIORef , newIORef ,
52- readIORef )
53+ import Data.IORef (IORef , atomicModifyIORef , atomicWriteIORef ,
54+ newIORef , readIORef )
5355import Data.Time.Clock (UTCTime , getCurrentTime )
5456
5557import Control.Concurrent.STM (TMVar , newEmptyTMVarIO ,
5658 putTMVar , readTMVar ,
57- tryReadTMVar , tryTakeTMVar )
59+ tryPutTMVar , tryReadTMVar ,
60+ tryTakeTMVar )
5861import PostgREST.Auth.JwtCache (JwtCacheState , update )
5962import PostgREST.Config (AppConfig (.. ),
6063 readAppConfig ,
@@ -70,6 +73,13 @@ import PostgREST.SchemaCache (SchemaCache (..),
7073 showSummary )
7174import PostgREST.SchemaCache.Identifiers (quoteQi )
7275
76+ import qualified Data.Aeson as JSON
77+ import qualified Data.ByteString.Lazy as LBS
78+ import qualified Network.HTTP.Client as HC
79+ import Network.URI (URI (.. ), URIAuth (.. ),
80+ parseURI , unEscapeString )
81+ import System.IO.Error (userError )
82+
7383import Protolude
7484
7585data AppState = AppState
@@ -109,16 +119,56 @@ newtype SchemaCacheStatus = SchemaCacheStatus
109119 { getSCStatusTMVar :: TMVar Bool
110120 }
111121
112- init :: AppConfig -> IO () -> IO AppState
113- init conf@ AppConfig {configLogLevel, configDbPoolSize} appKiller = do
122+ init :: AppConfig -> IO () -> Maybe Text -> IO AppState
123+ init conf@ AppConfig {configLogLevel, configDbPoolSize} appKiller schemaCacheLoadUri = do
114124 loggerState <- Logger. init
115125 metricsState <- Metrics. init configDbPoolSize
116126 let observer = liftA2 (>>) (Logger. observationLogger loggerState configLogLevel) (Metrics. observationMetrics metricsState)
117127
118128 observer $ AppStartObs prettyVersion
119129
120130 pool <- initPool conf observer
121- initWithPool pool conf loggerState metricsState observer appKiller
131+ appState <- initWithPool pool conf loggerState metricsState observer appKiller
132+
133+ runInitialSchemaCacheLoader observer schemaCacheLoadUri appState
134+
135+ pure appState
136+
137+ runInitialSchemaCacheLoader :: ObservationHandler -> Maybe Text -> AppState -> IO ()
138+ runInitialSchemaCacheLoader observer schemaCacheLoadUri AppState {stateSchemaCache, stateSCacheStatus= SchemaCacheStatus {getSCStatusTMVar}} = do
139+ void $ forkIO $
140+ traverse (fetchInitialSchemaCache observer) schemaCacheLoadUri >>= foldMap setInitialSchemaCache . join
141+ where
142+ setInitialSchemaCache sc =
143+ whenM (atomically $ tryPutTMVar getSCStatusTMVar True ) $
144+ atomicModifyIORef stateSchemaCache $ (, () ) . maybe (Just sc) Just
145+
146+ fetchInitialSchemaCache :: ObservationHandler -> Text -> IO (Maybe SchemaCache )
147+ fetchInitialSchemaCache observer uri = flip catches [
148+ Handler (handleError @ IOException ),
149+ Handler (handleError @ HC. HttpException ),
150+ Handler (handleError @ JSON. AesonException )
151+ ] $ maybe (throwIO $ userError " Invalid schema cache dump URI" ) pure =<< traverse fetchURI (parseURI $ toS uri)
152+ where
153+ handleError :: Show e => e -> IO (Maybe a )
154+ handleError = (Nothing <$ ) . observer . SchemaCacheInitialLoadFailureObs uri . show
155+
156+ fetchURI URI {uriScheme, .. }
157+ | uriScheme == " file:" = do
158+ path <- fileURIPath uriAuthority uriPath
159+ Just <$> (JSON. throwDecode =<< LBS. readFile path)
160+ | uriScheme `elem` [" http:" , " https:" ] = do
161+ request <- HC. parseUrlThrow $ toS uri
162+ manager <- HC. newManager HC. defaultManagerSettings
163+ HC. withResponse request manager $ \ response ->
164+ Just <$> (JSON. throwDecode . LBS. fromChunks =<< HC. brConsume (HC. responseBody response))
165+ | otherwise =
166+ throwIO $ userError $ " Unsupported schema cache dump URI scheme: " <> uriScheme
167+
168+ fileURIPath Nothing path = pure $ unEscapeString path
169+ fileURIPath (Just URIAuth {uriRegName= " " }) path = pure $ unEscapeString path
170+ fileURIPath (Just URIAuth {uriRegName= " localhost" }) path = pure $ unEscapeString path
171+ fileURIPath _ _ = throwIO $ userError " Only local file URIs are supported"
122172
123173initWithPool :: SQL. Pool -> AppConfig -> Logger. LoggerState -> Metrics. MetricsState -> ObservationHandler -> IO () -> IO AppState
124174initWithPool pool conf loggerState metricsState observer appKiller = mdo
0 commit comments