feat: implement server-timing-enabled config parameter (#3064)
This commit is contained in:
@@ -19,6 +19,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #2799, Add timezone in Prefer header - @taimoorzaeem
|
- #2799, Add timezone in Prefer header - @taimoorzaeem
|
||||||
- #3001, Add `statement_timeout` set on functions - @taimoorzaeem
|
- #3001, Add `statement_timeout` set on functions - @taimoorzaeem
|
||||||
- #3045, Apply superuser settings on impersonated roles if they have PostgreSQL 15 `GRANT SET ON PARAMETER` privilege - @steve-chavez
|
- #3045, Apply superuser settings on impersonated roles if they have PostgreSQL 15 `GRANT SET ON PARAMETER` privilege - @steve-chavez
|
||||||
|
- #3062, Add config for enabling the `Server-Timing` header - @develop7
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ postgrestResponse appState conf@AppConfig{..} maybeSchemaCache pgVer authResult@
|
|||||||
liftEither . mapLeft Error.ApiRequestError $
|
liftEither . mapLeft Error.ApiRequestError $
|
||||||
ApiRequest.userApiRequest conf req body sCache
|
ApiRequest.userApiRequest conf req body sCache
|
||||||
|
|
||||||
let jwtTiming = (SMJwt, if configDbPlanEnabled then Auth.getJwtDur req else Nothing)
|
let jwtTiming = (SMJwt, if configServerTimingEnabled then Auth.getJwtDur req else Nothing)
|
||||||
handleRequest authResult conf appState (Just authRole /= configDbAnonRole) configDbPreparedStatements pgVer apiRequest sCache jwtTiming
|
handleRequest authResult conf appState (Just authRole /= configDbAnonRole) configDbPreparedStatements pgVer apiRequest sCache jwtTiming
|
||||||
|
|
||||||
runDbHandler :: AppState.AppState -> SQL.IsolationLevel -> SQL.Mode -> Bool -> Bool -> DbHandler b -> Handler IO b
|
runDbHandler :: AppState.AppState -> SQL.IsolationLevel -> SQL.Mode -> Bool -> Bool -> DbHandler b -> Handler IO b
|
||||||
@@ -256,9 +256,9 @@ handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@A
|
|||||||
query
|
query
|
||||||
|
|
||||||
pgrstResponse :: ServerTimingData -> Response.PgrstResponse -> Wai.Response
|
pgrstResponse :: ServerTimingData -> Response.PgrstResponse -> Wai.Response
|
||||||
pgrstResponse timings (Response.PgrstResponse st hdrs bod) = Wai.responseLBS st (hdrs ++ ([renderServerTimingHeader timings | configDbPlanEnabled conf])) bod
|
pgrstResponse timings (Response.PgrstResponse st hdrs bod) = Wai.responseLBS st (hdrs ++ ([renderServerTimingHeader timings | configServerTimingEnabled conf])) bod
|
||||||
|
|
||||||
withTiming f = if configDbPlanEnabled conf
|
withTiming f = if configServerTimingEnabled conf
|
||||||
then do
|
then do
|
||||||
(t, r) <- timeItT f
|
(t, r) <- timeItT f
|
||||||
pure (Just t, r)
|
pure (Just t, r)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ middleware appState app req respond = do
|
|||||||
|
|
||||||
-- If DbPlanEnabled -> calculate JWT validation time
|
-- If DbPlanEnabled -> calculate JWT validation time
|
||||||
-- If JwtCacheMaxLifetime -> cache JWT validation result
|
-- If JwtCacheMaxLifetime -> cache JWT validation result
|
||||||
req' <- case (configDbPlanEnabled conf, configJwtCacheMaxLifetime conf) of
|
req' <- case (configServerTimingEnabled conf, configJwtCacheMaxLifetime conf) of
|
||||||
(True, 0) -> do
|
(True, 0) -> do
|
||||||
(dur, authResult) <- timeItT parseJwt
|
(dur, authResult) <- timeItT parseJwt
|
||||||
return $ req { Wai.vault = Wai.vault req & Vault.insert authResultKey authResult & Vault.insert jwtDurKey dur }
|
return $ req { Wai.vault = Wai.vault req & Vault.insert authResultKey authResult & Vault.insert jwtDurKey dur }
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ data AppConfig = AppConfig
|
|||||||
, configServerHost :: Text
|
, configServerHost :: Text
|
||||||
, configServerPort :: Int
|
, configServerPort :: Int
|
||||||
, configServerTraceHeader :: Maybe (CI.CI BS.ByteString)
|
, configServerTraceHeader :: Maybe (CI.CI BS.ByteString)
|
||||||
|
, configServerTimingEnabled :: Bool
|
||||||
, configServerUnixSocket :: Maybe FilePath
|
, configServerUnixSocket :: Maybe FilePath
|
||||||
, configServerUnixSocketMode :: FileMode
|
, configServerUnixSocketMode :: FileMode
|
||||||
, configAdminServerPort :: Maybe Int
|
, configAdminServerPort :: Maybe Int
|
||||||
@@ -169,6 +170,7 @@ toText conf =
|
|||||||
,("server-host", q . configServerHost)
|
,("server-host", q . configServerHost)
|
||||||
,("server-port", show . configServerPort)
|
,("server-port", show . configServerPort)
|
||||||
,("server-trace-header", q . T.decodeUtf8 . maybe mempty CI.original . configServerTraceHeader)
|
,("server-trace-header", q . T.decodeUtf8 . maybe mempty CI.original . configServerTraceHeader)
|
||||||
|
,("server-timing-enabled", T.toLower . show . configServerTimingEnabled)
|
||||||
,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket)
|
,("server-unix-socket", q . maybe mempty T.pack . configServerUnixSocket)
|
||||||
,("server-unix-socket-mode", q . T.pack . showSocketMode)
|
,("server-unix-socket-mode", q . T.pack . showSocketMode)
|
||||||
,("admin-server-port", maybe "\"\"" show . configAdminServerPort)
|
,("admin-server-port", maybe "\"\"" show . configAdminServerPort)
|
||||||
@@ -272,6 +274,7 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
|
|||||||
<*> (fromMaybe "!4" <$> optString "server-host")
|
<*> (fromMaybe "!4" <$> optString "server-host")
|
||||||
<*> (fromMaybe 3000 <$> optInt "server-port")
|
<*> (fromMaybe 3000 <$> optInt "server-port")
|
||||||
<*> (fmap (CI.mk . encodeUtf8) <$> optString "server-trace-header")
|
<*> (fmap (CI.mk . encodeUtf8) <$> optString "server-trace-header")
|
||||||
|
<*> (fromMaybe False <$> optBool "server-timing-enabled")
|
||||||
<*> (fmap T.unpack <$> optString "server-unix-socket")
|
<*> (fmap T.unpack <$> optString "server-unix-socket")
|
||||||
<*> parseSocketFileMode "server-unix-socket-mode"
|
<*> parseSocketFileMode "server-unix-socket-mode"
|
||||||
<*> optInt "admin-server-port"
|
<*> optInt "admin-server-port"
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ dbSettingsNames =
|
|||||||
,"openapi_server_proxy_uri"
|
,"openapi_server_proxy_uri"
|
||||||
,"raw_media_types"
|
,"raw_media_types"
|
||||||
,"server_trace_header"
|
,"server_trace_header"
|
||||||
|
,"server_timing_enabled"
|
||||||
]
|
]
|
||||||
|
|
||||||
queryPgVersion :: Bool -> Session PgVersion
|
queryPgVersion :: Bool -> Session PgVersion
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = ""
|
|||||||
server-host = "!4"
|
server-host = "!4"
|
||||||
server-port = 3000
|
server-port = 3000
|
||||||
server-trace-header = ""
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = ""
|
|||||||
server-host = "!4"
|
server-host = "!4"
|
||||||
server-port = 3000
|
server-port = 3000
|
||||||
server-trace-header = ""
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = ""
|
|||||||
server-host = "!4"
|
server-host = "!4"
|
||||||
server-port = 3000
|
server-port = 3000
|
||||||
server-trace-header = ""
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = ""
|
|||||||
server-host = "!4"
|
server-host = "!4"
|
||||||
server-port = 3000
|
server-port = 3000
|
||||||
server-trace-header = ""
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = "http://example.com"
|
|||||||
server-host = "0.0.0.0"
|
server-host = "0.0.0.0"
|
||||||
server-port = 80
|
server-port = 80
|
||||||
server-trace-header = "traceparent"
|
server-trace-header = "traceparent"
|
||||||
|
server-timing-enabled = true
|
||||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||||
server-unix-socket-mode = "777"
|
server-unix-socket-mode = "777"
|
||||||
admin-server-port = 3001
|
admin-server-port = 3001
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = "http://example.com"
|
|||||||
server-host = "0.0.0.0"
|
server-host = "0.0.0.0"
|
||||||
server-port = 80
|
server-port = 80
|
||||||
server-trace-header = "CF-Ray"
|
server-trace-header = "CF-Ray"
|
||||||
|
server-timing-enabled = true
|
||||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||||
server-unix-socket-mode = "777"
|
server-unix-socket-mode = "777"
|
||||||
admin-server-port = 3001
|
admin-server-port = 3001
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = "http://example.com"
|
|||||||
server-host = "0.0.0.0"
|
server-host = "0.0.0.0"
|
||||||
server-port = 80
|
server-port = 80
|
||||||
server-trace-header = "X-Request-Id"
|
server-trace-header = "X-Request-Id"
|
||||||
|
server-timing-enabled = true
|
||||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||||
server-unix-socket-mode = "777"
|
server-unix-socket-mode = "777"
|
||||||
admin-server-port = 3001
|
admin-server-port = 3001
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = ""
|
|||||||
server-host = "!4"
|
server-host = "!4"
|
||||||
server-port = 3000
|
server-port = 3000
|
||||||
server-trace-header = ""
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
server-unix-socket = ""
|
server-unix-socket = ""
|
||||||
server-unix-socket-mode = "660"
|
server-unix-socket-mode = "660"
|
||||||
admin-server-port = ""
|
admin-server-port = ""
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ PGRST_SERVER_CORS_ALLOWED_ORIGINS: "http://example.com"
|
|||||||
PGRST_SERVER_HOST: 0.0.0.0
|
PGRST_SERVER_HOST: 0.0.0.0
|
||||||
PGRST_SERVER_PORT: 80
|
PGRST_SERVER_PORT: 80
|
||||||
PGRST_SERVER_TRACE_HEADER: X-Request-Id
|
PGRST_SERVER_TRACE_HEADER: X-Request-Id
|
||||||
|
PGRST_SERVER_TIMING_ENABLED: true
|
||||||
PGRST_SERVER_UNIX_SOCKET: /tmp/pgrst_io_test.sock
|
PGRST_SERVER_UNIX_SOCKET: /tmp/pgrst_io_test.sock
|
||||||
PGRST_SERVER_UNIX_SOCKET_MODE: 777
|
PGRST_SERVER_UNIX_SOCKET_MODE: 777
|
||||||
PGRST_ADMIN_SERVER_PORT: 3001
|
PGRST_ADMIN_SERVER_PORT: 3001
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ server-cors-allowed-origins = "http://example.com"
|
|||||||
server-host = "0.0.0.0"
|
server-host = "0.0.0.0"
|
||||||
server-port = 80
|
server-port = 80
|
||||||
server-trace-header = "X-Request-Id"
|
server-trace-header = "X-Request-Id"
|
||||||
|
server-timing-enabled = true
|
||||||
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
server-unix-socket = "/tmp/pgrst_io_test.sock"
|
||||||
server-unix-socket-mode = "777"
|
server-unix-socket-mode = "777"
|
||||||
admin-server-port = 3001
|
admin-server-port = 3001
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ ALTER ROLE db_config_authenticator SET pgrst.db_extra_search_path = 'public, ext
|
|||||||
ALTER ROLE db_config_authenticator SET pgrst.not_existing = 'should be ignored';
|
ALTER ROLE db_config_authenticator SET pgrst.not_existing = 'should be ignored';
|
||||||
ALTER ROLE db_config_authenticator SET pgrst.server_cors_allowed_origins = 'http://example.com';
|
ALTER ROLE db_config_authenticator SET pgrst.server_cors_allowed_origins = 'http://example.com';
|
||||||
ALTER ROLE db_config_authenticator SET pgrst.server_trace_header = 'CF-Ray';
|
ALTER ROLE db_config_authenticator SET pgrst.server_trace_header = 'CF-Ray';
|
||||||
|
ALTER ROLE db_config_authenticator SET pgrst.server_timing_enabled = 'true';
|
||||||
|
|
||||||
-- override with database specific setting
|
-- override with database specific setting
|
||||||
ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.jwt_secret = 'OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE';
|
ALTER ROLE db_config_authenticator IN DATABASE :DBNAME SET pgrst.jwt_secret = 'OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE';
|
||||||
@@ -64,6 +65,7 @@ ALTER ROLE other_authenticator SET pgrst.openapi_security_active = 'false';
|
|||||||
ALTER ROLE other_authenticator SET pgrst.server_cors_allowed_origins = 'http://example.com';
|
ALTER ROLE other_authenticator SET pgrst.server_cors_allowed_origins = 'http://example.com';
|
||||||
ALTER ROLE other_authenticator SET pgrst.server_trace_header = 'traceparent';
|
ALTER ROLE other_authenticator SET pgrst.server_trace_header = 'traceparent';
|
||||||
ALTER ROLE other_authenticator SET pgrst.db_pre_config = 'postgrest.pre_config';
|
ALTER ROLE other_authenticator SET pgrst.db_pre_config = 'postgrest.pre_config';
|
||||||
|
ALTER ROLE other_authenticator SET pgrst.server_timing_enabled = 'true';
|
||||||
|
|
||||||
create schema postgrest;
|
create schema postgrest;
|
||||||
grant usage on schema postgrest to db_config_authenticator;
|
grant usage on schema postgrest to db_config_authenticator;
|
||||||
|
|||||||
+4
-4
@@ -1120,7 +1120,7 @@ def test_server_timing_jwt_should_decrease_on_subsequent_requests(defaultenv):
|
|||||||
|
|
||||||
env = {
|
env = {
|
||||||
**defaultenv,
|
**defaultenv,
|
||||||
"PGRST_DB_PLAN_ENABLED": "true",
|
"PGRST_SERVER_TIMING_ENABLED": "true",
|
||||||
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
||||||
"PGRST_JWT_SECRET": "@/dev/stdin",
|
"PGRST_JWT_SECRET": "@/dev/stdin",
|
||||||
"PGRST_DB_CONFIG": "false",
|
"PGRST_DB_CONFIG": "false",
|
||||||
@@ -1158,7 +1158,7 @@ def test_jwt_caching_works_with_db_plan_disabled(defaultenv):
|
|||||||
|
|
||||||
env = {
|
env = {
|
||||||
**defaultenv,
|
**defaultenv,
|
||||||
"PGRST_DB_PLAN_ENABLED": "false",
|
"PGRST_SERVER_TIMING_ENABLED": "true",
|
||||||
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
||||||
"PGRST_JWT_SECRET": "@/dev/stdin",
|
"PGRST_JWT_SECRET": "@/dev/stdin",
|
||||||
"PGRST_DB_CONFIG": "false",
|
"PGRST_DB_CONFIG": "false",
|
||||||
@@ -1180,7 +1180,7 @@ def test_server_timing_jwt_should_not_decrease_when_caching_disabled(defaultenv)
|
|||||||
|
|
||||||
env = {
|
env = {
|
||||||
**defaultenv,
|
**defaultenv,
|
||||||
"PGRST_DB_PLAN_ENABLED": "true",
|
"PGRST_SERVER_TIMING_ENABLED": "true",
|
||||||
"PGRST_JWT_CACHE_MAX_LIFETIME": "0", # cache disabled
|
"PGRST_JWT_CACHE_MAX_LIFETIME": "0", # cache disabled
|
||||||
"PGRST_JWT_SECRET": "@/dev/stdin",
|
"PGRST_JWT_SECRET": "@/dev/stdin",
|
||||||
"PGRST_DB_CONFIG": "false",
|
"PGRST_DB_CONFIG": "false",
|
||||||
@@ -1210,7 +1210,7 @@ def test_jwt_cache_with_no_exp_claim(defaultenv):
|
|||||||
|
|
||||||
env = {
|
env = {
|
||||||
**defaultenv,
|
**defaultenv,
|
||||||
"PGRST_DB_PLAN_ENABLED": "true",
|
"PGRST_SERVER_TIMING_ENABLED": "true",
|
||||||
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
"PGRST_JWT_CACHE_MAX_LIFETIME": "86400",
|
||||||
"PGRST_JWT_SECRET": "@/dev/stdin",
|
"PGRST_JWT_SECRET": "@/dev/stdin",
|
||||||
"PGRST_DB_CONFIG": "false",
|
"PGRST_DB_CONFIG": "false",
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
|
|||||||
, configRoleSettings = mempty
|
, configRoleSettings = mempty
|
||||||
, configRoleIsoLvl = mempty
|
, configRoleIsoLvl = mempty
|
||||||
, configInternalSCSleep = Nothing
|
, configInternalSCSleep = Nothing
|
||||||
|
, configServerTimingEnabled = True
|
||||||
}
|
}
|
||||||
|
|
||||||
testCfg :: AppConfig
|
testCfg :: AppConfig
|
||||||
|
|||||||
Reference in New Issue
Block a user