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,7 +73,13 @@ import PostgREST.SchemaCache (SchemaCache (..),
7073 showSummary )
7174import PostgREST.SchemaCache.Identifiers (quoteQi )
7275
73- import Protolude
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 Protolude
82+ import System.IO.Error (IOError , userError )
7483
7584data AppState = AppState
7685 -- | Database connection pool
@@ -109,16 +118,56 @@ newtype SchemaCacheStatus = SchemaCacheStatus
109118 { getSCStatusTMVar :: TMVar Bool
110119 }
111120
112- init :: AppConfig -> IO () -> IO AppState
113- init conf@ AppConfig {configLogLevel, configDbPoolSize} appKiller = do
121+ init :: AppConfig -> IO () -> Maybe Text -> IO AppState
122+ init conf@ AppConfig {configLogLevel, configDbPoolSize} appKiller schemaCacheLoadUri = do
114123 loggerState <- Logger. init
115124 metricsState <- Metrics. init configDbPoolSize
116125 let observer = liftA2 (>>) (Logger. observationLogger loggerState configLogLevel) (Metrics. observationMetrics metricsState)
117126
118127 observer $ AppStartObs prettyVersion
119128
120129 pool <- initPool conf observer
121- initWithPool pool conf loggerState metricsState observer appKiller
130+ appState <- initWithPool pool conf loggerState metricsState observer appKiller
131+
132+ runInitialSchemaCacheLoader observer schemaCacheLoadUri appState
133+
134+ pure appState
135+
136+ runInitialSchemaCacheLoader :: ObservationHandler -> Maybe Text -> AppState -> IO ()
137+ runInitialSchemaCacheLoader observer schemaCacheLoadUri AppState {stateSchemaCache, stateSCacheStatus= SchemaCacheStatus {getSCStatusTMVar}} = do
138+ void $ forkIO $
139+ traverse (fetchInitialSchemaCache observer) schemaCacheLoadUri >>= foldMap setInitialSchemaCache . join
140+ where
141+ setInitialSchemaCache sc =
142+ whenM (atomically $ tryPutTMVar getSCStatusTMVar True ) $
143+ atomicModifyIORef stateSchemaCache $ (, () ) . maybe (Just sc) Just
144+
145+ fetchInitialSchemaCache :: ObservationHandler -> Text -> IO (Maybe SchemaCache )
146+ fetchInitialSchemaCache observer uri = flip catches [
147+ Handler (handleError @ IOError ),
148+ Handler (handleError @ HC. HttpException ),
149+ Handler (handleError @ JSON. AesonException )
150+ ] $ maybe (throwIO $ userError " Invalid schema cache dump URI" ) pure =<< traverse fetchURI (parseURI $ toS uri)
151+ where
152+ handleError :: Show e => e -> IO (Maybe a )
153+ handleError = (Nothing <$ ) . observer . SchemaCacheDumpFailureObs uri . show
154+
155+ fetchURI URI {uriScheme, .. }
156+ | uriScheme == " file:" = do
157+ path <- fileURIPath uriAuthority uriPath
158+ Just <$> (JSON. throwDecode =<< LBS. readFile path)
159+ | uriScheme `elem` [" http:" , " https:" ] = do
160+ request <- HC. parseUrlThrow $ toS uri
161+ manager <- HC. newManager HC. defaultManagerSettings
162+ HC. withResponse request manager $ \ response ->
163+ Just <$> (JSON. throwDecode . LBS. fromChunks =<< HC. brConsume (HC. responseBody response))
164+ | otherwise =
165+ throwIO $ userError $ " Unsupported schema cache dump URI scheme: " <> uriScheme
166+
167+ fileURIPath Nothing path = pure $ unEscapeString path
168+ fileURIPath (Just URIAuth {uriRegName= " " }) path = pure $ unEscapeString path
169+ fileURIPath (Just URIAuth {uriRegName= " localhost" }) path = pure $ unEscapeString path
170+ fileURIPath _ _ = throwIO $ userError " Only local file URIs are supported"
122171
123172initWithPool :: SQL. Pool -> AppConfig -> Logger. LoggerState -> Metrics. MetricsState -> ObservationHandler -> IO () -> IO AppState
124173initWithPool pool conf loggerState metricsState observer appKiller = mdo
0 commit comments