@@ -11,6 +11,7 @@ Some of its functionality includes:
1111-}
1212{-# LANGUAGE RecordWildCards #-}
1313{-# LANGUAGE ScopedTypeVariables #-}
14+ {-# LANGUAGE TupleSections #-}
1415{-# LANGUAGE ViewPatterns #-}
1516module PostgREST.App
1617 ( postgrest
@@ -24,7 +25,6 @@ import System.IO.Error (ioeGetErrorType)
2425import Control.Monad.Except (liftEither )
2526import Control.Monad.Extra (whenJust )
2627import Data.Either.Combinators (mapLeft , whenLeft )
27- import Data.Maybe (fromJust )
2828import Data.String (IsString (.. ))
2929import Network.Wai.Handler.Warp (defaultSettings , setHost ,
3030 setOnException , setPort ,
@@ -33,6 +33,7 @@ import Network.Wai.Handler.Warp (defaultSettings, setHost,
3333import qualified Data.Text.Encoding as T
3434import qualified Network.Wai as Wai
3535import qualified Network.Wai.Handler.Warp as Warp
36+ import qualified Network.Wai.Header as WaiHeader
3637
3738import qualified PostgREST.Admin as Admin
3839import qualified PostgREST.ApiRequest as ApiRequest
@@ -41,7 +42,6 @@ import qualified PostgREST.Auth as Auth
4142import qualified PostgREST.Cors as Cors
4243import qualified PostgREST.Error as Error
4344import qualified PostgREST.Listener as Listener
44- import qualified PostgREST.Logger as Logger
4545import qualified PostgREST.MainTx as MainTx
4646import qualified PostgREST.Plan as Plan
4747import qualified PostgREST.Query as Query
@@ -51,7 +51,7 @@ import qualified PostgREST.Unix as Unix (installSignalHandlers)
5151import PostgREST.ApiRequest (ApiRequest (.. ))
5252import PostgREST.AppState (AppState )
5353import PostgREST.Auth.Types (AuthResult (.. ))
54- import PostgREST.Config (AppConfig (.. ), LogLevel ( .. ) )
54+ import PostgREST.Config (AppConfig (.. ))
5555import PostgREST.Error (Error )
5656import PostgREST.Network (resolveSocketToAddress )
5757import PostgREST.Observation (Observation (.. ))
@@ -72,11 +72,9 @@ import qualified Network.Socket as NS
7272import PostgREST.Unix (createAndBindDomainSocket )
7373import Protolude hiding (Handler )
7474
75- type Handler = ExceptT Error
76-
7775run :: AppState -> IO ()
7876run appState = do
79- conf@ AppConfig { .. } <- AppState. getConfig appState
77+ conf <- AppState. getConfig appState
8078
8179 AppState. schemaCacheLoader appState -- Loads the initial SchemaCache
8280 (mainSocket, adminSocket) <- initSockets conf
@@ -89,7 +87,7 @@ run appState = do
8987
9088 Admin. runAdmin appState adminSocket mainSocket (serverSettings conf)
9189
92- let app = postgrest configLogLevel appState (AppState. schemaCacheLoader appState)
90+ let app = postgrest appState (AppState. schemaCacheLoader appState)
9391
9492 do
9593 address <- resolveSocketToAddress mainSocket
@@ -122,93 +120,102 @@ serverSettings AppConfig{..} =
122120 & setServerName (" postgrest/" <> prettyVersion)
123121
124122-- | PostgREST application
125- postgrest :: LogLevel -> AppState. AppState -> IO () -> Wai. Application
126- postgrest logLevel appState connWorker =
123+ postgrest :: AppState. AppState -> IO () -> Wai. Application
124+ postgrest appState connWorker =
127125 traceHeaderMiddleware appState .
128- Cors. middleware appState .
129- Auth. middleware appState .
130- Logger. middleware logLevel Auth. getRole $
131- -- fromJust can be used, because the auth middleware will **always** add
132- -- some AuthResult to the vault.
126+ Cors. middleware appState $
133127 \ req respond -> do
134128 appConf@ AppConfig {.. } <- AppState. getConfig appState -- the config must be read again because it can reload
135- case fromJust $ Auth. getResult req of
136- Left err -> respond $ Error. errorResponseFor configClientErrorVerbosity err
137- Right authResult -> do
138- maybeSchemaCache <- AppState. getSchemaCache appState
139-
140- let
141- eitherResponse :: IO (Either Error Wai. Response )
142- eitherResponse =
143- runExceptT $ postgrestResponse appState appConf maybeSchemaCache authResult req
144-
145- response <- either (Error. errorResponseFor configClientErrorVerbosity) identity <$> eitherResponse
146- -- Launch the connWorker when the connection is down. The postgrest
147- -- function can respond successfully (with a stale schema cache) before
148- -- the connWorker is done. However, when there's an empty schema cache
149- -- postgrest responds with the error `PGRST002`; this means that the schema
150- -- cache is still loading, so we don't launch the connWorker here because
151- -- it would duplicate the loading process, e.g. https://github.com/PostgREST/postgrest/issues/3704
152- -- TODO: this process may be unnecessary when the Listener is enabled. Revisit once https://github.com/PostgREST/postgrest/issues/1766 is done
153- when (isServiceUnavailable response && isJust maybeSchemaCache) connWorker
154- resp <- do
155- delay <- AppState. getNextDelay appState
156- return $ addRetryHint delay response
157- respond resp
129+ maybeSchemaCache <- AppState. getSchemaCache appState
130+
131+ let observer = AppState. getObserver appState
132+
133+ (maybeUser, response) <-
134+ either
135+ (second $ Error. errorResponseFor configClientErrorVerbosity)
136+ (first Just )
137+ <$> runExceptT (postgrestResponse appState appConf maybeSchemaCache req)
138+
139+ observer $ genResponseObs maybeUser req response
140+
141+ -- Launch the connWorker when the connection is down. The postgrest
142+ -- function can respond successfully (with a stale schema cache) before
143+ -- the connWorker is done. However, when there's an empty schema cache
144+ -- postgrest responds with the error `PGRST002`; this means that the schema
145+ -- cache is still loading, so we don't launch the connWorker here because
146+ -- it would duplicate the loading process, e.g. https://github.com/PostgREST/postgrest/issues/3704
147+ -- TODO: this process may be unnecessary when the Listener is enabled. Revisit once https://github.com/PostgREST/postgrest/issues/1766 is done
148+ when (isServiceUnavailable response && isJust maybeSchemaCache) connWorker
149+ delay <- AppState. getNextDelay appState
150+ respond $ addRetryHint delay response
151+ where
152+ -- TODO WaiHeader.contentLength does a lookup everytime, see: https://hackage.haskell.org/package/wai-extra-3.1.17/docs/src/Network.Wai.Header.html#contentLength
153+ -- It might be possible to gain some perf by returning the response length from `postgrestResponse`. We calculate the length manually on Response.hs.
154+ genResponseObs :: Maybe ByteString -> Wai. Request -> Wai. Response -> Observation
155+ genResponseObs user req resp =
156+ ResponseObs user req (Wai. responseStatus resp) (WaiHeader. contentLength $ Wai. responseHeaders resp)
158157
159158postgrestResponse
160159 :: AppState. AppState
161160 -> AppConfig
162161 -> Maybe SchemaCache
163- -> AuthResult
164162 -> Wai. Request
165- -> Handler IO Wai. Response
166- postgrestResponse appState conf@ AppConfig {.. } maybeSchemaCache authResult @ AuthResult { .. } req = do
163+ -> ExceptT ( Maybe ByteString , Error ) IO ( ByteString , Wai. Response)
164+ postgrestResponse appState conf@ AppConfig {.. } maybeSchemaCache req = do
167165 let observer = AppState. getObserver appState
168166
167+ let bearerAuth = ApiRequest. userBearerAuth req
168+
169+ (jwtTime, authResult@ AuthResult {.. }) <- withExceptT (Nothing ,) $ -- no role at this stage
170+ withTiming $ liftIO (Auth. getAuthResult appState bearerAuth) >>= liftEither
171+
172+ let authRole' = Just authRole
173+
169174 sCache <-
170175 case maybeSchemaCache of
171176 Just sCache ->
172177 return sCache
173178 Nothing -> do
174179 lift $ observer SchemaCacheEmptyObs
175- throwError Error. NoSchemaCacheError
180+ throwError (authRole', Error. NoSchemaCacheError )
181+
182+ let prefs = ApiRequest. userPreferences conf req (dbTimezones sCache)
183+ withRoleError = withExceptT (authRole',)
176184
177185 body <- lift $ Wai. strictRequestBody req
178186
179- let jwtTime = if configServerTimingEnabled then Auth. getJwtDur req else Nothing
180- timezones = dbTimezones sCache
181- prefs = ApiRequest. userPreferences conf req timezones
187+ (parseTime, apiReq@ ApiRequest {.. }) <- withRoleError $
188+ withTiming $ liftEither . mapLeft Error. ApiRequestErr $ ApiRequest. userApiRequest conf prefs req body
182189
183- (parseTime, apiReq @ ApiRequest { .. } ) <- withTiming $ liftEither . mapLeft Error. ApiRequestErr $ ApiRequest. userApiRequest conf prefs req body
184- (planTime, plan) <- withTiming $ liftEither $ Plan. actionPlan iAction conf apiReq sCache
190+ (planTime, plan ) <- withRoleError $
191+ withTiming $ liftEither $ Plan. actionPlan iAction conf apiReq sCache
185192
186193 let mainQ = Query. mainQuery plan conf apiReq authResult configDbPreRequest
187194 tx = MainTx. mainTx mainQ conf authResult apiReq plan sCache
188195 obsQuery s = when configLogQuery $ observer $ QueryObs mainQ s
189196
190- (txTime, txResult) <- withTiming $ do
197+ (txTime, txResult) <- withRoleError . withTiming $ do
191198 case tx of
192199 MainTx. NoDbTx r -> pure r
193200 MainTx. DbTx {.. } -> do
194201 dbRes <- lift $ AppState. usePool appState (dqTransaction dqIsoLevel dqTxMode $ runExceptT dqDbHandler)
195- let eitherResp = join $ mapLeft (Error. PgErr . Error. PgError (Just authRole /= configDbAnonRole)) dbRes
202+ let eitherResp = join $ mapLeft (Error. PgErr . Error. PgError (authRole' /= configDbAnonRole)) dbRes
196203
197204 -- TODO: we use obsQuery twice, one here and one below because in case of an error with the usePool above, the request will finish here and return an error message.
198205 -- This is because of a combination of ExceptT + our Error module which has Wai.responseLBS.
199206 -- This needs refactoring so only the below obsQuery is used.
200207 lift $ whenLeft eitherResp $ obsQuery . Error. status
201208 liftEither eitherResp
202209
203- (respTime, resp) <- withTiming $ do
210+ (respTime, resp) <- withRoleError $ withTiming $ do
204211 let response = Response. actionResponse txResult apiReq (T. decodeUtf8 prettyVersion, docsVersion) conf sCache
205212 status' = either Error. status Response. pgrstStatus response
206213
207214 -- TODO: see above obsQuery, only this obsQuery should remain after refactoring (because the QueryObs depends on the status)
208215 lift $ obsQuery status'
209216 liftEither response
210217
211- return $ toWaiResponse (ServerTiming jwtTime parseTime planTime txTime respTime) resp
218+ pure (authRole, toWaiResponse (ServerTiming jwtTime parseTime planTime txTime respTime) resp)
212219
213220 where
214221 toWaiResponse :: ServerTiming -> Response. PgrstResponse -> Wai. Response
@@ -224,7 +231,7 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache authResult@AuthRe
224231 varyHeaderPresent :: [HTTP. Header ] -> Bool
225232 varyHeaderPresent = any (\ (h, _v) -> h == HTTP. hVary)
226233
227- withTiming :: Handler IO a -> Handler IO (Maybe Double , a )
234+ withTiming :: ExceptT e IO a -> ExceptT e IO (Maybe Double , a )
228235 withTiming f = if configServerTimingEnabled
229236 then do
230237 (t, r) <- timeItT f
0 commit comments