diff --git a/changelog.d/5-internal/WPB-1220-servantify-proxy-internal b/changelog.d/5-internal/WPB-1220-servantify-proxy-internal new file mode 100644 index 00000000000..f161136a346 --- /dev/null +++ b/changelog.d/5-internal/WPB-1220-servantify-proxy-internal @@ -0,0 +1 @@ +Servantify internal routing table for proxy. diff --git a/libs/metrics-wai/default.nix b/libs/metrics-wai/default.nix index eb3a260e929..8bb74088e5e 100644 --- a/libs/metrics-wai/default.nix +++ b/libs/metrics-wai/default.nix @@ -14,6 +14,7 @@ , servant , servant-multipart , text +, types-common , utf8-string , wai , wai-middleware-prometheus @@ -32,6 +33,7 @@ mkDerivation { servant servant-multipart text + types-common utf8-string wai wai-middleware-prometheus diff --git a/libs/metrics-wai/metrics-wai.cabal b/libs/metrics-wai/metrics-wai.cabal index ed848c893cb..1b6e5cfa03b 100644 --- a/libs/metrics-wai/metrics-wai.cabal +++ b/libs/metrics-wai/metrics-wai.cabal @@ -76,6 +76,7 @@ library , servant , servant-multipart , text >=0.11 + , types-common , utf8-string , wai >=3 , wai-middleware-prometheus diff --git a/libs/metrics-wai/src/Data/Metrics/Middleware/Prometheus.hs b/libs/metrics-wai/src/Data/Metrics/Middleware/Prometheus.hs index f1f7c1ca562..39b73e351e9 100644 --- a/libs/metrics-wai/src/Data/Metrics/Middleware/Prometheus.hs +++ b/libs/metrics-wai/src/Data/Metrics/Middleware/Prometheus.hs @@ -17,10 +17,12 @@ module Data.Metrics.Middleware.Prometheus ( waiPrometheusMiddleware, + waiPrometheusMiddlewarePaths, normalizeWaiRequestRoute, ) where +import Data.Id import Data.Metrics.Types (Paths, treeLookup) import Data.Metrics.WaiRoute (treeToPaths) import Data.Text.Encoding qualified as T @@ -33,12 +35,17 @@ import Network.Wai.Routing.Route (Routes, prepare) -- This middleware requires your servers 'Routes' because it does some normalization -- (e.g. removing params from calls) waiPrometheusMiddleware :: (Monad m) => Routes a m b -> Wai.Middleware -waiPrometheusMiddleware routes = +waiPrometheusMiddleware routes = waiPrometheusMiddlewarePaths $ treeToPaths $ prepare routes + +-- | Helper function that should only be needed as long as we have wai-routing code left in +-- proxy: run 'treeToPaths' on old routing tables and 'routeToPaths' on the servant ones, and +-- feed both to this function. +waiPrometheusMiddlewarePaths :: Paths -> Wai.Middleware +waiPrometheusMiddlewarePaths paths = Promth.prometheus conf . instrument (normalizeWaiRequestRoute paths) where -- See Note [Raw Response] instrument = Promth.instrumentHandlerValueWithFilter Promth.ignoreRawResponses - paths = treeToPaths $ prepare routes conf = Promth.def { Promth.prometheusEndPoint = ["i", "metrics"], @@ -57,4 +64,4 @@ normalizeWaiRequestRoute paths req = pathInfo -- Use the normalized path info if available; otherwise dump the raw path info for -- debugging purposes pathInfo :: Text - pathInfo = T.decodeUtf8 $ fromMaybe "N/A" mPathInfo + pathInfo = T.decodeUtf8 $ fromMaybe defRequestId mPathInfo diff --git a/libs/metrics-wai/src/Data/Metrics/Servant.hs b/libs/metrics-wai/src/Data/Metrics/Servant.hs index a66da6837a2..490ed13ded2 100644 --- a/libs/metrics-wai/src/Data/Metrics/Servant.hs +++ b/libs/metrics-wai/src/Data/Metrics/Servant.hs @@ -27,6 +27,7 @@ module Data.Metrics.Servant where import Data.ByteString.UTF8 qualified as UTF8 +import Data.Id import Data.Metrics.Types import Data.Metrics.Types qualified as Metrics import Data.Proxy @@ -49,7 +50,7 @@ servantPrometheusMiddleware _ = Promth.prometheus conf . instrument promthNormal promthNormalize req = pathInfo where mPathInfo = Metrics.treeLookup (routesToPaths @api) $ encodeUtf8 <$> Wai.pathInfo req - pathInfo = decodeUtf8With lenientDecode $ fromMaybe "N/A" mPathInfo + pathInfo = decodeUtf8With lenientDecode $ fromMaybe defRequestId mPathInfo -- See Note [Raw Response] instrument = Promth.instrumentHandlerValueWithFilter Promth.ignoreRawResponses diff --git a/libs/metrics-wai/src/Data/Metrics/Types.hs b/libs/metrics-wai/src/Data/Metrics/Types.hs index 0d1a70903d0..4d83874789b 100644 --- a/libs/metrics-wai/src/Data/Metrics/Types.hs +++ b/libs/metrics-wai/src/Data/Metrics/Types.hs @@ -41,6 +41,7 @@ newtype PathTemplate = PathTemplate Text -- (e.g. user id). newtype Paths = Paths (Forest PathSegment) deriving (Eq, Show) + deriving newtype (Semigroup) type PathSegment = Either ByteString ByteString diff --git a/libs/types-common/src/Data/Id.hs b/libs/types-common/src/Data/Id.hs index 3ef7152c913..0a1dbe22ad3 100644 --- a/libs/types-common/src/Data/Id.hs +++ b/libs/types-common/src/Data/Id.hs @@ -48,6 +48,7 @@ module Data.Id -- * Other IDs ConnId (..), RequestId (..), + defRequestId, BotId (..), NoId, OAuthClientId, @@ -418,6 +419,9 @@ newtype RequestId = RequestId ToBytes ) +defRequestId :: (IsString s) => s +defRequestId = "N/A" + instance ToSchema RequestId where schema = RequestId . encodeUtf8 diff --git a/libs/wai-utilities/src/Network/Wai/Utilities/Request.hs b/libs/wai-utilities/src/Network/Wai/Utilities/Request.hs index 2450bfd7b47..484c1b34643 100644 --- a/libs/wai-utilities/src/Network/Wai/Utilities/Request.hs +++ b/libs/wai-utilities/src/Network/Wai/Utilities/Request.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -Wno-orphans #-} -- This file is part of the Wire Server implementation. @@ -56,7 +55,7 @@ lookupRequestId reqIdHeaderName = getRequestId :: HeaderName -> Request -> RequestId getRequestId reqIdHeaderName req = - RequestId $ fromMaybe "N/A" $ lookupRequestId reqIdHeaderName req + RequestId $ fromMaybe defRequestId $ lookupRequestId reqIdHeaderName req ---------------------------------------------------------------------------- -- Typed JSON 'Request' diff --git a/libs/wai-utilities/src/Network/Wai/Utilities/Server.hs b/libs/wai-utilities/src/Network/Wai/Utilities/Server.hs index dd3306f4a65..20f8fc9b934 100644 --- a/libs/wai-utilities/src/Network/Wai/Utilities/Server.hs +++ b/libs/wai-utilities/src/Network/Wai/Utilities/Server.hs @@ -60,6 +60,7 @@ import Data.ByteString.Builder import Data.ByteString.Char8 qualified as C import Data.ByteString.Lazy qualified as LBS import Data.Domain (domainText) +import Data.Id import Data.Metrics.GC (spawnGCMetricsCollector) import Data.Streaming.Zlib (ZlibException (..)) import Data.Text.Encoding qualified as Text @@ -168,7 +169,7 @@ compile routes = Route.prepare (Route.renderer predicateError >> routes) r = reasonStr <$> reason e t = message e in case catMaybes [l, s, r] of - [] -> maybe "N/A" (LT.decodeUtf8With lenientDecode . LBS.fromStrict) t + [] -> maybe defRequestId (LT.decodeUtf8With lenientDecode . LBS.fromStrict) t bs -> LT.decodeUtf8With lenientDecode . toLazyByteString $ mconcat bs <> messageStr t labelStr [] = Nothing labelStr ls = @@ -311,7 +312,7 @@ heavyDebugLogging sanitizeReq lvl lgr reqIdHeaderName app = \req cont -> do logMostlyEverything req bdy resp = Log.debug lgr logMsg where logMsg = - field "request" (fromMaybe "N/A" $ lookupRequestId reqIdHeaderName req) + field "request" (fromMaybe defRequestId $ lookupRequestId reqIdHeaderName req) . field "request_details" (show req) . field "request_body" bdy . field "response_status" (show $ responseStatus resp) @@ -350,7 +351,7 @@ rethrow5xx getRequestId logger app req k = app req k' let logMsg = field "canoncalpath" (show $ pathInfo req) . field "rawpath" (rawPathInfo req) - . field "request" (fromMaybe "N/A" $ getRequestId req) + . field "request" (fromMaybe defRequestId $ getRequestId req) . msg (val "ResponseRaw - cannot collect metrics or log info on errors") Log.log logger Log.Debug logMsg k resp @@ -436,7 +437,7 @@ logError' g mr e = liftIO $ doLog g (logErrorMsgWithRequest mr e) logJSONResponse :: (MonadIO m) => Logger -> Maybe ByteString -> JSONResponse -> m () logJSONResponse g mReqId e = do - let r = fromMaybe "N/A" mReqId + let r = fromMaybe defRequestId mReqId liftIO $ doLog g $ field "request" r @@ -462,7 +463,7 @@ logErrorMsg (Wai.Error c l m md inner) = logErrorMsgWithRequest :: Maybe ByteString -> Wai.Error -> Msg -> Msg logErrorMsgWithRequest mr e = - field "request" (fromMaybe "N/A" mr) . logErrorMsg e + field "request" (fromMaybe defRequestId mr) . logErrorMsg e runHandlers :: SomeException -> [Handler IO a] -> IO a runHandlers e [] = throwIO e diff --git a/libs/wire-subsystems/src/Wire/EmailSubsystem/Interpreter.hs b/libs/wire-subsystems/src/Wire/EmailSubsystem/Interpreter.hs index a78e26f3754..f3451750e47 100644 --- a/libs/wire-subsystems/src/Wire/EmailSubsystem/Interpreter.hs +++ b/libs/wire-subsystems/src/Wire/EmailSubsystem/Interpreter.hs @@ -345,8 +345,8 @@ renderNewClientEmail email name locale Client {..} NewClientEmailTemplate {..} b html = renderHtmlWithBranding newClientEmailBodyHtml replace branding subj = renderTextWithBranding newClientEmailSubject replace branding replace "name" = fromName name - replace "label" = fromMaybe "N/A" clientLabel - replace "model" = fromMaybe "N/A" clientModel + replace "label" = fromMaybe defRequestId clientLabel + replace "model" = fromMaybe defRequestId clientModel replace "date" = formatDateTime "%A %e %B %Y, %H:%M - %Z" diff --git a/libs/wire-subsystems/test/unit/Wire/NotificationSubsystem/InterpreterSpec.hs b/libs/wire-subsystems/test/unit/Wire/NotificationSubsystem/InterpreterSpec.hs index 880a213d25b..9fbd3babe89 100644 --- a/libs/wire-subsystems/test/unit/Wire/NotificationSubsystem/InterpreterSpec.hs +++ b/libs/wire-subsystems/test/unit/Wire/NotificationSubsystem/InterpreterSpec.hs @@ -1,9 +1,9 @@ module Wire.NotificationSubsystem.InterpreterSpec (spec) where -import Bilge (RequestId (..)) import Control.Concurrent.Async (async, wait) import Control.Exception (throwIO) import Data.Data (Proxy (Proxy)) +import Data.Id import Data.List.NonEmpty (NonEmpty ((:|)), fromList) import Data.List1 qualified as List1 import Data.Range (fromRange, toRange) @@ -37,7 +37,7 @@ spec = describe "NotificationSubsystem.Interpreter" do { fanoutLimit = toRange $ Proxy @30, chunkSize = 12, slowPushDelay = 0, - requestId = RequestId "N/A" + requestId = RequestId defRequestId } connId2 <- generate arbitrary @@ -98,7 +98,7 @@ spec = describe "NotificationSubsystem.Interpreter" do { fanoutLimit = toRange $ Proxy @30, chunkSize = 12, slowPushDelay = 0, - requestId = RequestId "N/A" + requestId = RequestId defRequestId } connId2 <- generate arbitrary @@ -153,7 +153,7 @@ spec = describe "NotificationSubsystem.Interpreter" do { fanoutLimit = toRange $ Proxy @30, chunkSize = 12, slowPushDelay = 1, - requestId = RequestId "N/A" + requestId = RequestId defRequestId } connId2 <- generate arbitrary @@ -211,7 +211,7 @@ spec = describe "NotificationSubsystem.Interpreter" do { fanoutLimit = toRange $ Proxy @30, chunkSize = 12, slowPushDelay = 1, - requestId = RequestId "N/A" + requestId = RequestId defRequestId } user1 <- generate arbitrary diff --git a/services/background-worker/src/Wire/BackendNotificationPusher.hs b/services/background-worker/src/Wire/BackendNotificationPusher.hs index 464c93e0cf0..6a6cf2f7f62 100644 --- a/services/background-worker/src/Wire/BackendNotificationPusher.hs +++ b/services/background-worker/src/Wire/BackendNotificationPusher.hs @@ -116,7 +116,7 @@ pushNotification runningFlag targetDomain (msg, envelope) = do ceHttp2Manager <- asks http2Manager let ceOriginDomain = notif.ownDomain ceTargetDomain = targetDomain - ceOriginRequestId = fromMaybe (RequestId "N/A") notif.requestId + ceOriginRequestId = fromMaybe (RequestId defRequestId) notif.requestId cveEnv = FederatorClientEnv {..} cveVersion = Just V0 -- V0 is assumed for non-versioned queue messages fcEnv = FederatorClientVersionedEnv {..} @@ -135,7 +135,7 @@ pushNotification runningFlag targetDomain (msg, envelope) = do ceFederator = federator, ceHttp2Manager = manager, ceOriginRequestId = - fromMaybe (RequestId "N/A") . (.requestId) . NE.head $ bundle.notifications + fromMaybe (RequestId defRequestId) . (.requestId) . NE.head $ bundle.notifications } remoteVersions :: Set Int <- liftIO @@ -166,7 +166,7 @@ pushNotification runningFlag targetDomain (msg, envelope) = do ceHttp2Manager <- asks http2Manager let ceOriginDomain = notif.ownDomain ceTargetDomain = targetDomain - ceOriginRequestId = fromMaybe (RequestId "N/A") notif.requestId + ceOriginRequestId = fromMaybe (RequestId defRequestId) notif.requestId cveEnv = FederatorClientEnv {..} fcEnv = FederatorClientVersionedEnv {..} sendNotificationIgnoringVersionMismatch fcEnv notif.targetComponent notif.path notif.body diff --git a/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs b/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs index 29906684cae..416a2653f82 100644 --- a/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs +++ b/services/background-worker/test/Test/Wire/BackendNotificationPusherSpec.hs @@ -67,7 +67,7 @@ spec = do path = "/on-user-deleted-connections", body = RawJson $ Aeson.encode notifContent, bodyVersions = Nothing, - requestId = Just $ RequestId "N/A" + requestId = Just $ RequestId defRequestId } envelope <- newMockEnvelope let msg = @@ -104,7 +104,7 @@ spec = do notifContent <- generate $ ClientRemovedRequest <$> arbitrary <*> arbitrary <*> arbitrary - let bundle = toBundle @'OnClientRemovedTag (RequestId "N/A") origDomain notifContent + let bundle = toBundle @'OnClientRemovedTag (RequestId defRequestId) origDomain notifContent envelope <- newMockEnvelope let msg = Q.newMsg @@ -148,8 +148,8 @@ spec = do } let update0 = conversationUpdateToV0 update let bundle = - toBundle (RequestId "N/A") origDomain update - <> toBundle (RequestId "N/A") origDomain update0 + toBundle (RequestId defRequestId) origDomain update + <> toBundle (RequestId defRequestId) origDomain update0 envelope <- newMockEnvelope let msg = Q.newMsg @@ -215,7 +215,7 @@ spec = do path = "/on-user-deleted-connections", body = RawJson $ Aeson.encode notifContent, bodyVersions = Nothing, - requestId = Just $ RequestId "N/A" + requestId = Just $ RequestId defRequestId } envelope <- newMockEnvelope let msg = diff --git a/services/brig/src/Brig/App.hs b/services/brig/src/Brig/App.hs index 28735c7817b..eb7b06457e2 100644 --- a/services/brig/src/Brig/App.hs +++ b/services/brig/src/Brig/App.hs @@ -92,7 +92,6 @@ module Brig.App ) where -import Bilge (RequestId (..)) import Bilge qualified as RPC import Bilge.IO import Bilge.RPC (HasRequestId (..)) @@ -122,6 +121,7 @@ import Control.Monad.Trans.Resource import Data.ByteString.Conversion import Data.Credentials (Credentials (..)) import Data.Domain +import Data.Id import Data.Misc import Data.Qualified import Data.Text qualified as Text @@ -278,7 +278,7 @@ newEnv opts = do awsEnv = aws, -- used by `journalEvent` directly appLogger = lgr, internalEvents = (eventsQueue :: QueueEnv), - requestId = RequestId "N/A", + requestId = RequestId defRequestId, userTemplates = utp, providerTemplates = ptp, teamTemplates = ttp, diff --git a/services/cannon/src/Cannon/App.hs b/services/cannon/src/Cannon/App.hs index 842d38135a3..770bf0ff499 100644 --- a/services/cannon/src/Cannon/App.hs +++ b/services/cannon/src/Cannon/App.hs @@ -29,7 +29,7 @@ import Control.Monad.Catch import Data.Aeson hiding (Error, Key, (.=)) import Data.ByteString.Conversion import Data.ByteString.Lazy (toStrict) -import Data.Id (ClientId) +import Data.Id import Data.Text.Lazy qualified as Text import Data.Timeout import Imports hiding (threadDelay) @@ -155,7 +155,7 @@ rejectOnError :: PendingConnection -> HandshakeException -> IO a rejectOnError p x = do let f lb mg = toStrict . encode $ mkError status400 lb mg case x of - NotSupported -> rejectRequest p (f "protocol not supported" "N/A") + NotSupported -> rejectRequest p (f "protocol not supported" defRequestId) MalformedRequest _ m -> rejectRequest p (f "malformed-request" (Text.pack m)) OtherHandshakeException m -> rejectRequest p (f "other-error" (Text.pack m)) _ -> pure () diff --git a/services/cannon/src/Cannon/Types.hs b/services/cannon/src/Cannon/Types.hs index eec8d20ac4b..6fa37b78a65 100644 --- a/services/cannon/src/Cannon/Types.hs +++ b/services/cannon/src/Cannon/Types.hs @@ -33,7 +33,7 @@ module Cannon.Types ) where -import Bilge (Manager, RequestId (..)) +import Bilge (Manager) import Bilge.RPC (HasRequestId (..)) import Cannon.Dict (Dict) import Cannon.Options @@ -42,6 +42,7 @@ import Cannon.WS qualified as WS import Control.Concurrent.Async (mapConcurrently) import Control.Lens ((^.)) import Control.Monad.Catch +import Data.Id import Data.Text.Encoding import Imports import Prometheus @@ -100,7 +101,7 @@ mkEnv :: Clock -> Env mkEnv external o l d p g t = - Env o l d (RequestId "N/A") $ + Env o l d (RequestId defRequestId) $ WS.env external (o ^. cannon . port) (encodeUtf8 $ o ^. gundeck . host) (o ^. gundeck . port) l p d g t (o ^. drainOpts) runCannon :: Env -> Cannon a -> IO a diff --git a/services/cannon/src/Cannon/WS.hs b/services/cannon/src/Cannon/WS.hs index 0ad9820df96..ea106f4cf03 100644 --- a/services/cannon/src/Cannon/WS.hs +++ b/services/cannon/src/Cannon/WS.hs @@ -62,7 +62,7 @@ import Data.ByteString.Char8 (pack) import Data.ByteString.Conversion import Data.ByteString.Lazy qualified as L import Data.Hashable -import Data.Id (ClientId, ConnId (..), UserId) +import Data.Id (ClientId, ConnId (..), UserId, defRequestId) import Data.List.Extra (chunksOf) import Data.Text.Encoding (decodeUtf8) import Data.Timeout (TimeoutUnit (..), (#)) @@ -192,7 +192,7 @@ env :: Clock -> DrainOpts -> Env -env leh lp gh gp = Env leh lp (host gh . port gp $ empty) (RequestId "N/A") +env leh lp gh gp = Env leh lp (host gh . port gp $ empty) (RequestId defRequestId) runWS :: (MonadIO m) => Env -> WS a -> m a runWS e m = liftIO $ runReaderT (_conn m) e diff --git a/services/cargohold/src/CargoHold/App.hs b/services/cargohold/src/CargoHold/App.hs index 1495bda34fe..5acb66a57ed 100644 --- a/services/cargohold/src/CargoHold/App.hs +++ b/services/cargohold/src/CargoHold/App.hs @@ -47,7 +47,7 @@ module CargoHold.App where import Amazonka (S3AddressingStyle (S3AddressingStylePath)) -import Bilge (Manager, MonadHttp, RequestId (..), newManager, withResponse) +import Bilge (Manager, MonadHttp, newManager, withResponse) import qualified Bilge import Bilge.RPC (HasRequestId (..)) import qualified CargoHold.AWS as AWS @@ -57,6 +57,7 @@ import Control.Error (ExceptT, exceptT) import Control.Exception (throw) import Control.Lens (lensField, lensRules, makeLensesWith, non, (.~), (?~), (^.)) import Control.Monad.Catch (MonadCatch, MonadMask, MonadThrow) +import Data.Id import qualified Data.Map as Map import Data.Qualified import HTTP2.Client.Manager (Http2Manager, http2ManagerWithSSLCtx) @@ -100,7 +101,7 @@ newEnv opts = do awsEnv <- initAws opts.aws logger httpMgr multiIngressAWS <- initMultiIngressAWS logger httpMgr let localDomain = toLocalUnsafe opts.settings.federationDomain () - pure $ Env awsEnv logger httpMgr http2Mgr (RequestId "N/A") opts localDomain multiIngressAWS + pure $ Env awsEnv logger httpMgr http2Mgr (RequestId defRequestId) opts localDomain multiIngressAWS where initMultiIngressAWS :: Logger -> Manager -> IO (Map String AWS.Env) initMultiIngressAWS logger httpMgr = diff --git a/services/federator/src/Federator/Run.hs b/services/federator/src/Federator/Run.hs index d7ddfb27d4f..83b9883b414 100644 --- a/services/federator/src/Federator/Run.hs +++ b/services/federator/src/Federator/Run.hs @@ -92,7 +92,7 @@ run opts = do newEnv :: Opts -> DNS.Resolver -> Log.Logger -> IO Env newEnv o _dnsResolver _applog = do - let _requestId = RequestId "N/A" + let _requestId = RequestId defRequestId _runSettings = o.optSettings _service Brig = o.brig _service Galley = o.galley diff --git a/services/federator/test/integration/Test/Federator/IngressSpec.hs b/services/federator/test/integration/Test/Federator/IngressSpec.hs index babbaca3a43..41b9f42af3c 100644 --- a/services/federator/test/integration/Test/Federator/IngressSpec.hs +++ b/services/federator/test/integration/Test/Federator/IngressSpec.hs @@ -150,7 +150,7 @@ inwardBrigCallViaIngressWithSettings sslCtx requestPath payload = mgr <- liftToCodensity . liftIO $ http2ManagerWithSSLCtx sslCtx liftToCodensity . runInputConst mgr - . runInputConst (RequestId "N/A") + . runInputConst (RequestId defRequestId) . assertNoError @DiscoveryFailure . discoverConst target . interpretRemote diff --git a/services/federator/test/unit/Test/Federator/Client.hs b/services/federator/test/unit/Test/Federator/Client.hs index a816f7710c9..36c2717a6b4 100644 --- a/services/federator/test/unit/Test/Federator/Client.hs +++ b/services/federator/test/unit/Test/Federator/Client.hs @@ -97,7 +97,7 @@ withMockFederatorClient mock action = withTempMockFederator mock $ \port -> do ceTargetDomain = targetDomain, ceFederator = Endpoint "127.0.0.1" (fromIntegral port), ceHttp2Manager = mgr, - ceOriginRequestId = RequestId "N/A" + ceOriginRequestId = RequestId defRequestId } a <- runFederatorClient env action case a of @@ -137,7 +137,7 @@ testClientStreaming = withInfiniteMockServer $ \port -> do ceTargetDomain = targetDomain, ceFederator = Endpoint "127.0.0.1" (fromIntegral port), ceHttp2Manager = mgr, - ceOriginRequestId = RequestId "N/A" + ceOriginRequestId = RequestId defRequestId } venv = FederatorClientVersionedEnv env Nothing let c = clientIn (Proxy @StreamingAPI) (Proxy @(FederatorClient 'Brig)) @@ -202,7 +202,7 @@ testClientConnectionError = do ceTargetDomain = targetDomain, ceFederator = Endpoint "127.0.0.1" 1, ceHttp2Manager = mgr, - ceOriginRequestId = RequestId "N/A" + ceOriginRequestId = RequestId defRequestId } result <- runFederatorClient env (fedClient @'Brig @"get-user-by-handle" handle) case result of diff --git a/services/galley/src/Galley/App.hs b/services/galley/src/Galley/App.hs index baa3284e861..a9a02e660ea 100644 --- a/services/galley/src/Galley/App.hs +++ b/services/galley/src/Galley/App.hs @@ -167,7 +167,7 @@ createEnv o l = do mgr <- initHttpManager o h2mgr <- initHttp2Manager codeURIcfg <- validateOptions o - Env (RequestId "N/A") o l mgr h2mgr (o ^. O.federator) (o ^. O.brig) cass + Env (RequestId defRequestId) o l mgr h2mgr (o ^. O.federator) (o ^. O.brig) cass <$> Q.new 16000 <*> initExtEnv <*> maybe (pure Nothing) (fmap Just . Aws.mkEnv l mgr) (o ^. journal) diff --git a/services/gundeck/src/Gundeck/Env.hs b/services/gundeck/src/Gundeck/Env.hs index e3d1fcbe148..2397005c68a 100644 --- a/services/gundeck/src/Gundeck/Env.hs +++ b/services/gundeck/src/Gundeck/Env.hs @@ -27,6 +27,7 @@ import Control.Concurrent.Async (Async) import Control.Lens (makeLenses, (^.)) import Control.Retry (capDelay, exponentialBackoff) import Data.ByteString.Char8 qualified as BSChar8 +import Data.Id import Data.Misc (Milliseconds (..)) import Data.Text qualified as Text import Data.Time.Clock @@ -100,7 +101,7 @@ createEnv o = do { updateAction = Ms . round . (* 1000) <$> getPOSIXTime } mtbs <- mkThreadBudgetState `mapM` (o ^. settings . maxConcurrentNativePushes) - pure $! (rThread : rAdditionalThreads,) $! Env (RequestId "N/A") o l n p r rAdditional a io mtbs + pure $! (rThread : rAdditionalThreads,) $! Env (RequestId defRequestId) o l n p r rAdditional a io mtbs reqIdMsg :: RequestId -> Logger.Msg -> Logger.Msg reqIdMsg = ("request" Logger..=) . unRequestId diff --git a/services/proxy/default.nix b/services/proxy/default.nix index b6205a6acee..8b689661b9c 100644 --- a/services/proxy/default.nix +++ b/services/proxy/default.nix @@ -9,6 +9,7 @@ , bytestring , case-insensitive , configurator +, errors , exceptions , extended , gitignoreSource @@ -21,6 +22,7 @@ , lib , metrics-wai , retry +, servant-server , text , tinylog , types-common @@ -46,6 +48,7 @@ mkDerivation { bytestring case-insensitive configurator + errors exceptions extended http-client @@ -56,6 +59,7 @@ mkDerivation { lens metrics-wai retry + servant-server text tinylog types-common diff --git a/services/proxy/proxy.cabal b/services/proxy/proxy.cabal index e92831949f6..5da48e93a8b 100644 --- a/services/proxy/proxy.cabal +++ b/services/proxy/proxy.cabal @@ -17,7 +17,7 @@ flag static library exposed-modules: - Proxy.API + Proxy.API.Internal Proxy.API.Public Proxy.Env Proxy.Options @@ -80,6 +80,7 @@ library , bytestring >=0.10 , case-insensitive >=1.2 , configurator >=0.3 + , errors , exceptions >=0.8 , extended , http-client >=0.7 @@ -90,6 +91,7 @@ library , lens >=4.11 , metrics-wai >=0.5 , retry >=0.7 + , servant-server , text >=1.2 , tinylog >=0.12 , types-common >=0.8 diff --git a/services/proxy/src/Proxy/API.hs b/services/proxy/src/Proxy/API/Internal.hs similarity index 55% rename from services/proxy/src/Proxy/API.hs rename to services/proxy/src/Proxy/API/Internal.hs index d3ba31ca4f3..c7128b0bfdb 100644 --- a/services/proxy/src/Proxy/API.hs +++ b/services/proxy/src/Proxy/API/Internal.hs @@ -15,29 +15,19 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Proxy.API - ( sitemap, +module Proxy.API.Internal + ( InternalAPI, + servantSitemap, ) where import Imports hiding (head) -import Network.Wai.Predicate (true) -import Network.Wai.Routing (Routes, continue, get, head) -import Network.Wai.Utilities (empty) -import Proxy.API.Public qualified as Public -import Proxy.Env (Env) -import Proxy.Proxy (Proxy) +import Proxy.Proxy qualified +import Servant +import Wire.API.Routes.MultiVerb +import Wire.API.Routes.Named (Named (Named)) -sitemap :: Env -> Routes a Proxy () -sitemap e = do - Public.sitemap e - routesInternal +type InternalAPI = Named "status" ("i" :> "status" :> MultiVerb 'GET '[Servant.JSON] '[RespondEmpty 200 "OK"] ()) --- | IF YOU MODIFY THIS, BE AWARE OF: --- --- >>> /libs/wire-api/src/Wire/API/Routes/Public/Proxy.hs --- >>> https://wearezeta.atlassian.net/browse/SQSERVICES-1647 -routesInternal :: Routes a Proxy () -routesInternal = do - head "/i/status" (continue $ const (pure empty)) true - get "/i/status" (continue $ const (pure empty)) true +servantSitemap :: ServerT InternalAPI Proxy.Proxy.Proxy +servantSitemap = Named @"status" (pure ()) diff --git a/services/proxy/src/Proxy/API/Public.hs b/services/proxy/src/Proxy/API/Public.hs index a33bafb1d9c..24989369d48 100644 --- a/services/proxy/src/Proxy/API/Public.hs +++ b/services/proxy/src/Proxy/API/Public.hs @@ -16,7 +16,9 @@ -- with this program. If not, see . module Proxy.API.Public - ( sitemap, + ( PublicAPI, + servantSitemap, + waiRoutingSitemap, ) where @@ -41,18 +43,35 @@ import Network.Wai.Internal qualified as I import Network.Wai.Predicate hiding (Error, err, setStatus) import Network.Wai.Predicate.Request (getRequest) import Network.Wai.Routing hiding (path, route) +import Network.Wai.Routing qualified as Routing import Network.Wai.Utilities +import Network.Wai.Utilities.Server (compile) import Proxy.Env import Proxy.Proxy +import Servant qualified import System.Logger.Class hiding (Error, info, render) import System.Logger.Class qualified as Logger +type PublicAPI = Servant.Raw -- see https://wearezeta.atlassian.net/browse/WPB-1216 + +servantSitemap :: Env -> Servant.ServerT PublicAPI Proxy.Proxy.Proxy +servantSitemap e = Servant.Tagged app + where + app :: Application + app r k = appInProxy e r (Routing.route tree r k') + where + tree :: Tree (App Proxy) + tree = compile (waiRoutingSitemap e) + + k' :: Response -> Proxy.Proxy.Proxy ResponseReceived + k' = liftIO . k + -- | IF YOU MODIFY THIS, BE AWARE OF: -- -- >>> /libs/wire-api/src/Wire/API/Routes/Public/Proxy.hs -- >>> https://wearezeta.atlassian.net/browse/SQSERVICES-1647 -sitemap :: Env -> Routes a Proxy () -sitemap e = do +waiRoutingSitemap :: Env -> Routes a Proxy () +waiRoutingSitemap e = do get "/proxy/youtube/v3/:path" (proxy e "key" "secrets.youtube" Prefix "/youtube/v3" youtube) @@ -107,7 +126,7 @@ proxy e qparam keyname reroute path phost rq k = do then do threadDelay 5000 loop runInIO (n - 1) waiReq req - else runProxy e waiReq (k res) + else appInProxy e waiReq (k res) onUpstreamError runInIO x _ next = do void . runInIO $ Logger.warn (msg (val "gateway error") ~~ field "error" (show x)) next (errorRs error502) diff --git a/services/proxy/src/Proxy/Env.hs b/services/proxy/src/Proxy/Env.hs index d429787d1be..7b50325ed80 100644 --- a/services/proxy/src/Proxy/Env.hs +++ b/services/proxy/src/Proxy/Env.hs @@ -32,7 +32,7 @@ where import Control.Lens (makeLenses, (^.)) import Data.Configurator import Data.Configurator.Types -import Data.Id (RequestId (..)) +import Data.Id (RequestId (..), defRequestId) import Imports import Network.HTTP.Client import Network.HTTP.Client.TLS (tlsManagerSettings) @@ -62,7 +62,7 @@ createEnv o = do } let ac = AutoConfig 60 (reloadError g) (c, t) <- autoReload ac [Required $ o ^. secretsConfig] - let rid = RequestId "N/A" + let rid = RequestId defRequestId pure $! Env rid o g n c t where reloadError g x = diff --git a/services/proxy/src/Proxy/Proxy.hs b/services/proxy/src/Proxy/Proxy.hs index fe65dc4b920..348c96eb39b 100644 --- a/services/proxy/src/Proxy/Proxy.hs +++ b/services/proxy/src/Proxy/Proxy.hs @@ -17,7 +17,7 @@ -- You should have received a copy of the GNU Affero General Public License along -- with this program. If not, see . -module Proxy.Proxy (Proxy, runProxy) where +module Proxy.Proxy (Proxy, appInProxy, runProxy) where import Bilge.Request (requestIdName) import Control.Lens hiding ((.=)) @@ -51,11 +51,14 @@ newtype Proxy a = Proxy instance MonadLogger Proxy where log l m = ask >>= \e -> Logger.log (e ^. applog) l (reqIdMsg (e ^. reqId) . m) -runProxy :: Env -> Request -> Proxy ResponseReceived -> IO ResponseReceived -runProxy e r m = do +appInProxy :: Env -> Request -> Proxy ResponseReceived -> IO ResponseReceived +appInProxy e r m = do rid <- lookupReqId (e ^. applog) r runReaderT (unProxy m) (reqId .~ rid $ e) +runProxy :: Env -> Proxy a -> IO a +runProxy e m = runReaderT (unProxy m) e + reqIdMsg :: RequestId -> Msg -> Msg reqIdMsg = ("request" .=) . unRequestId {-# INLINE reqIdMsg #-} diff --git a/services/proxy/src/Proxy/Run.hs b/services/proxy/src/Proxy/Run.hs index 16d43994006..14ebb11f691 100644 --- a/services/proxy/src/Proxy/Run.hs +++ b/services/proxy/src/Proxy/Run.hs @@ -20,29 +20,69 @@ module Proxy.Run ) where +import Bilge.Request (requestIdName) +import Control.Error import Control.Lens hiding ((.=)) import Control.Monad.Catch -import Data.Metrics.Middleware.Prometheus (waiPrometheusMiddleware) +import Data.Id (RequestId (RequestId), defRequestId) +import Data.Metrics.Middleware.Prometheus (waiPrometheusMiddlewarePaths) +import Data.Metrics.Servant +import Data.Metrics.Types +import Data.Metrics.WaiRoute import Imports hiding (head) +import Network.Wai (Middleware, Request, requestHeaders) import Network.Wai.Middleware.Gunzip qualified as GZip +import Network.Wai.Routing.Route import Network.Wai.Utilities.Server hiding (serverPort) -import Proxy.API (sitemap) +import Proxy.API.Internal as I +import Proxy.API.Public as P import Proxy.Env import Proxy.Options import Proxy.Proxy +import Servant qualified import Wire.API.Routes.Version import Wire.API.Routes.Version.Wai +type CombinedAPI = PublicAPI Servant.:<|> InternalAPI + +combinedSitemap :: Env -> Servant.ServerT CombinedAPI Proxy +combinedSitemap env = P.servantSitemap env Servant.:<|> I.servantSitemap + run :: Opts -> IO () run o = do e <- createEnv o s <- newSettings $ defaultServer (o ^. host) (o ^. port) (e ^. applog) - let rtree = compile (sitemap e) - let app r k = runProxy e r (route rtree r k) - let middleware = + + let metricsMW :: Middleware + metricsMW = + -- FUTUREWORK: once wai-routing has been removed from proxy: use `servantPrometheusMiddleware + -- (Servant.Proxy @CombinedAPI)` here (and probably inline the whole thing). + waiPrometheusMiddlewarePaths (pub <> int) + where + pub, int :: Paths + pub = treeToPaths $ prepare (P.waiRoutingSitemap e) + int = routesToPaths @InternalAPI + + middleware :: Middleware + middleware = versionMiddleware (foldMap expandVersionExp (o ^. disabledAPIVersions)) . requestIdMiddleware (e ^. applog) defaultRequestIdHeaderName - . waiPrometheusMiddleware (sitemap e) + . metricsMW . GZip.gunzip . catchErrors (e ^. applog) defaultRequestIdHeaderName - runSettingsWithShutdown s (middleware app) Nothing `finally` destroyEnv e + + runSettingsWithShutdown s (middleware (mkApp e)) Nothing `finally` destroyEnv e + +mkApp :: Env -> Servant.Application +mkApp env req = Servant.serve (Servant.Proxy @CombinedAPI) toServantSitemap req + where + toServantSitemap :: Servant.Server CombinedAPI + toServantSitemap = Servant.hoistServer (Servant.Proxy @CombinedAPI) toServantHandler (combinedSitemap env) + + toServantHandler :: Proxy a -> Servant.Handler a + toServantHandler p = Servant.Handler . ExceptT $ Right <$> runProxy (injectReqId req env) p + + injectReqId :: Request -> Env -> Env + injectReqId r = reqId .~ lookupReqId r + where + lookupReqId = RequestId . fromMaybe defRequestId . lookup requestIdName . requestHeaders diff --git a/services/proxy/test/scripts/proxy-test.sh b/services/proxy/test/scripts/proxy-test.sh index 3f8ee9ed3ba..ea7b89fe403 100755 --- a/services/proxy/test/scripts/proxy-test.sh +++ b/services/proxy/test/scripts/proxy-test.sh @@ -11,7 +11,8 @@ instance. this replaces more thorough integration tests, since integration tests for just proxy without the proxied services installed is hard and inadequate. -WIRE_BACKEND: $WIRE_BACKEND +WIRE_BACKEND: $WIRE_BACKEND (do not append a / to host:port!) + WIRE_ADMIN: $WIRE_ADMIN WIRE_PASSWD: " diff --git a/services/spar/src/Spar/Run.hs b/services/spar/src/Spar/Run.hs index e577e9ed5b6..170721df48b 100644 --- a/services/spar/src/Spar/Run.hs +++ b/services/spar/src/Spar/Run.hs @@ -99,7 +99,7 @@ mkApp sparCtxOpts = do Bilge.host (sparCtxOpts ^. to galley . to host . to encodeUtf8) . Bilge.port (sparCtxOpts ^. to galley . to port) $ Bilge.empty - let sparCtxRequestId = RequestId "N/A" + let sparCtxRequestId = RequestId defRequestId let ctx0 = Env {..} let heavyLogOnly :: (Wai.Request, LByteString) -> Maybe (Wai.Request, LByteString) heavyLogOnly out@(req, _) = diff --git a/tools/stern/src/Stern/App.hs b/tools/stern/src/Stern/App.hs index 1056cf37182..e0f021a0932 100644 --- a/tools/stern/src/Stern/App.hs +++ b/tools/stern/src/Stern/App.hs @@ -69,7 +69,7 @@ newEnv opts = do (mkRequest opts.ibis) (mkRequest opts.galeb) l - (RequestId "N/A") + (RequestId defRequestId) <$> newManager where mkRequest s = Bilge.host (encodeUtf8 s.host) . Bilge.port s.port $ Bilge.empty