server: improve handling of HTTP client errors for remote schema calls

https://github.com/hasura/graphql-engine-mono/pull/1719

GitOrigin-RevId: 5ff671bcff5f0559b9821f8359ebee4129f4b664
This commit is contained in:
Robert 2021-07-07 12:14:13 +02:00 committed by hasura-bot
parent 80161e4208
commit ae6d624441
3 changed files with 47 additions and 29 deletions

View File

@ -8,6 +8,7 @@
- server: Add global limit to BigQuery via the `global_select_limit` - server: Add global limit to BigQuery via the `global_select_limit`
field in the connection configuration field in the connection configuration
- server: include action and event names in log output - server: include action and event names in log output
- server: log all HTTP errors in remote schema calls as `remote-schema-error` with details
## v2.0.1 ## v2.0.1

View File

@ -24,6 +24,7 @@ import Control.Lens ((^.))
import Control.Monad.Unique import Control.Monad.Unique
import Data.Aeson ((.:), (.:?)) import Data.Aeson ((.:), (.:?))
import Data.FileEmbed (makeRelativeToProject) import Data.FileEmbed (makeRelativeToProject)
import Network.URI (URI)
import qualified Hasura.GraphQL.Parser.Monad as P import qualified Hasura.GraphQL.Parser.Monad as P
@ -62,7 +63,7 @@ fetchRemoteSchema env manager schemaName schemaInfo@(RemoteSchemaInfo url header
let hdrsWithDefaults = addDefaultHeaders headers let hdrsWithDefaults = addDefaultHeaders headers
initReqE <- liftIO $ try $ HTTP.parseRequest (show url) initReqE <- liftIO $ try $ HTTP.parseRequest (show url)
initReq <- onLeft initReqE throwHttpErr initReq <- onLeft initReqE (throwRemoteSchemaHttp url)
let req = initReq let req = initReq
{ HTTP.method = "POST" { HTTP.method = "POST"
, HTTP.requestHeaders = hdrsWithDefaults , HTTP.requestHeaders = hdrsWithDefaults
@ -70,7 +71,7 @@ fetchRemoteSchema env manager schemaName schemaInfo@(RemoteSchemaInfo url header
, HTTP.responseTimeout = HTTP.responseTimeoutMicro (timeout * 1000000) , HTTP.responseTimeout = HTTP.responseTimeoutMicro (timeout * 1000000)
} }
res <- liftIO $ try $ HTTP.httpLbs req manager res <- liftIO $ try $ HTTP.httpLbs req manager
resp <- onLeft res throwHttpErr resp <- onLeft res (throwRemoteSchemaHttp url)
let respData = resp ^. Wreq.responseBody let respData = resp ^. Wreq.responseBody
statusCode = resp ^. Wreq.responseStatus . Wreq.statusCode statusCode = resp ^. Wreq.responseStatus . Wreq.statusCode
@ -78,7 +79,7 @@ fetchRemoteSchema env manager schemaName schemaInfo@(RemoteSchemaInfo url header
-- Parse the JSON into flat GraphQL type AST -- Parse the JSON into flat GraphQL type AST
(FromIntrospection introspectRes) :: (FromIntrospection IntrospectionResult) <- (FromIntrospection introspectRes) :: (FromIntrospection IntrospectionResult) <-
onLeft (J.eitherDecode respData) (remoteSchemaErr . T.pack) onLeft (J.eitherDecode respData) (throwRemoteSchema . T.pack)
-- Check that the parsed GraphQL type info is valid by running the schema generation -- Check that the parsed GraphQL type info is valid by running the schema generation
(queryParsers, mutationParsers, subscriptionParsers) <- (queryParsers, mutationParsers, subscriptionParsers) <-
@ -92,24 +93,9 @@ fetchRemoteSchema env manager schemaName schemaInfo@(RemoteSchemaInfo url header
-- is called by simple encoding the result to JSON. -- is called by simple encoding the result to JSON.
return $ RemoteSchemaCtx schemaName introspectRes schemaInfo respData parsedIntrospection mempty return $ RemoteSchemaCtx schemaName introspectRes schemaInfo respData parsedIntrospection mempty
where where
remoteSchemaErr :: Text -> m a throwNon200 st = throwRemoteSchemaWithInternal (non200Msg st) . decodeNon200Resp
remoteSchemaErr = throw400 RemoteSchemaError non200Msg st = T.pack $ "introspection query to " <> show url
throwHttpErr :: HTTP.HttpException -> m a
throwHttpErr = throwWithInternal httpExceptMsg . httpExceptToJSON
throwNon200 st = throwWithInternal (non200Msg st) . decodeNon200Resp
throwWithInternal msg v =
let err = err400 RemoteSchemaError $ T.pack msg
in throwError err{qeInternal = Just $ J.toJSON v}
httpExceptMsg =
"HTTP exception occurred while sending the request to " <> show url
non200Msg st = "introspection query to " <> show url
<> " has responded with " <> show st <> " status code" <> " has responded with " <> show st <> " status code"
decodeNon200Resp bs = case J.eitherDecode bs of decodeNon200Resp bs = case J.eitherDecode bs of
Right a -> J.object ["response" J..= (a :: J.Value)] Right a -> J.object ["response" J..= (a :: J.Value)]
Left _ -> J.object ["raw_body" J..= bsToTxt (BL.toStrict bs)] Left _ -> J.object ["raw_body" J..= bsToTxt (BL.toStrict bs)]
@ -348,7 +334,7 @@ execRemoteGQ env manager userInfo reqHdrs rsi gqlReq@GQLReq{..} = do
headers = Map.toList $ foldr Map.union Map.empty hdrMaps headers = Map.toList $ foldr Map.union Map.empty hdrMaps
finalHeaders = addDefaultHeaders headers finalHeaders = addDefaultHeaders headers
initReqE <- liftIO $ try $ HTTP.parseRequest (show url) initReqE <- liftIO $ try $ HTTP.parseRequest (show url)
initReq <- onLeft initReqE httpThrow initReq <- onLeft initReqE (throwRemoteSchemaHttp url)
let req = initReq let req = initReq
{ HTTP.method = "POST" { HTTP.method = "POST"
, HTTP.requestHeaders = finalHeaders , HTTP.requestHeaders = finalHeaders
@ -357,13 +343,30 @@ execRemoteGQ env manager userInfo reqHdrs rsi gqlReq@GQLReq{..} = do
} }
Tracing.tracedHttpRequest req \req' -> do Tracing.tracedHttpRequest req \req' -> do
(time, res) <- withElapsedTime $ liftIO $ try $ HTTP.httpLbs req' manager (time, res) <- withElapsedTime $ liftIO $ try $ HTTP.httpLbs req' manager
resp <- onLeft res httpThrow resp <- onLeft res (throwRemoteSchemaHttp url)
pure (time, mkSetCookieHeaders resp, resp ^. Wreq.responseBody) pure (time, mkSetCookieHeaders resp, resp ^. Wreq.responseBody)
where where
RemoteSchemaInfo url hdrConf fwdClientHdrs timeout = rsi RemoteSchemaInfo url hdrConf fwdClientHdrs timeout = rsi
httpThrow :: (MonadError QErr m) => HTTP.HttpException -> m a
httpThrow = \case
HTTP.HttpExceptionRequest _req content -> throw500 $ tshow content
HTTP.InvalidUrlException _url reason -> throw500 $ tshow reason
userInfoToHdrs = sessionVariablesToHeaders $ _uiSession userInfo userInfoToHdrs = sessionVariablesToHeaders $ _uiSession userInfo
throwRemoteSchema
:: QErrM m
=> Text -> m a
throwRemoteSchema = throw400 RemoteSchemaError
throwRemoteSchemaWithInternal
:: (QErrM m, J.ToJSON a)
=> Text -> a -> m b
throwRemoteSchemaWithInternal msg v =
let err = err400 RemoteSchemaError msg
in throwError err{qeInternal = Just $ J.toJSON v}
throwRemoteSchemaHttp
:: QErrM m
=> URI -> HTTP.HttpException -> m a
throwRemoteSchemaHttp url =
throwRemoteSchemaWithInternal (T.pack httpExceptMsg) . httpExceptToJSON
where
httpExceptMsg =
"HTTP exception occurred while sending the request to " <> show url

View File

@ -3,8 +3,22 @@ url: /v1/graphql
status: 200 status: 200
response: response:
errors: errors:
- message: ResponseTimeout - message: HTTP exception occurred while sending the request to http://localhost:5000/hello-graphql
extensions: {'path': '$', 'code': 'unexpected'} extensions:
code: remote-schema-error
path: $
internal:
message: ResponseTimeout
request:
proxy:
secure: false
path: /hello-graphql
responseTimeout: ResponseTimeoutMicro 5000000
method: POST
host: localhost
requestVersion: HTTP/1.1
redirectCount: '10'
port: '5000'
query: query:
query: | query: |
query { query {