cache remote schema's introspection query response (fix #1679) (#2089)

This commit is contained in:
Rakesh Emmadi
2019-07-08 11:21:41 +05:30
committed by Vamshi Surabhi
parent 9675e036ea
commit 9eb38e6c96
28 changed files with 841 additions and 436 deletions

View File

@@ -136,7 +136,7 @@ mkHsraObjFldInfo
-> G.GType
-> ObjFldInfo
mkHsraObjFldInfo descM name params ty =
ObjFldInfo descM name params ty HasuraType
ObjFldInfo descM name params ty TLHasuraType
mkHsraObjTyInfo
:: Maybe G.Description
@@ -145,7 +145,7 @@ mkHsraObjTyInfo
-> ObjFieldMap
-> ObjTyInfo
mkHsraObjTyInfo descM ty implIFaces flds =
mkObjTyInfo descM ty implIFaces flds HasuraType
mkObjTyInfo descM ty implIFaces flds TLHasuraType
mkHsraInpTyInfo
:: Maybe G.Description
@@ -153,7 +153,7 @@ mkHsraInpTyInfo
-> InpObjFldMap
-> InpObjTyInfo
mkHsraInpTyInfo descM ty flds =
InpObjTyInfo descM ty flds HasuraType
InpObjTyInfo descM ty flds TLHasuraType
mkHsraEnumTyInfo
:: Maybe G.Description
@@ -161,10 +161,10 @@ mkHsraEnumTyInfo
-> Map.HashMap G.EnumValue EnumValInfo
-> EnumTyInfo
mkHsraEnumTyInfo descM ty enumVals =
EnumTyInfo descM ty enumVals HasuraType
EnumTyInfo descM ty enumVals TLHasuraType
mkHsraScalarTyInfo :: PGColType -> ScalarTyInfo
mkHsraScalarTyInfo ty = ScalarTyInfo Nothing ty HasuraType
mkHsraScalarTyInfo ty = ScalarTyInfo Nothing ty TLHasuraType
fromInpValL :: [InpValInfo] -> Map.HashMap G.Name InpValInfo
fromInpValL = mapFromL _iviName
@@ -211,7 +211,7 @@ mkCompExpInp colTy =
, bool [] (stDWithinGeoOpInpVal stDWithinGeographyInpTy :
map geoOpToInpVal geoOps) isGeographyType
, [InpValInfo Nothing "_is_null" Nothing $ G.TypeNamed (G.Nullability True) $ G.NamedType "Boolean"]
]) HasuraType
]) TLHasuraType
where
tyDesc = mconcat
[ "expression to compare columns of type "
@@ -349,7 +349,7 @@ ordByEnumTy =
]
defaultTypes :: [TypeInfo]
defaultTypes = $(fromSchemaDocQ defaultSchema HasuraType)
defaultTypes = $(fromSchemaDocQ defaultSchema TLHasuraType)
mkGCtx :: TyAgg -> RootFlds -> InsCtxMap -> GCtx
@@ -397,10 +397,10 @@ mkGCtx tyAgg (RootFlds flds) insCtxMap =
-- _st_d_within has to stay with geometry type
stDWithinGeometryInpM =
bool Nothing (Just $ stDWithinGeomInp) (PGGeometry `elem` colTys)
bool Nothing (Just stDWithinGeomInp) (PGGeometry `elem` colTys)
-- _st_d_within_geography is created for geography type
stDWithinGeographyInpM =
bool Nothing (Just $ stDWithinGeogInp) (PGGeography `elem` colTys)
bool Nothing (Just stDWithinGeogInp) (PGGeography `elem` colTys)
stDWithinGeomInp =
mkHsraInpTyInfo Nothing stDWithinGeometryInpTy $ fromInpValL
@@ -417,3 +417,11 @@ mkGCtx tyAgg (RootFlds flds) insCtxMap =
emptyGCtx :: GCtx
emptyGCtx = mkGCtx mempty mempty mempty
data RemoteGCtx
= RemoteGCtx
{ _rgTypes :: !TypeMap
, _rgQueryRoot :: !ObjTyInfo
, _rgMutationRoot :: !(Maybe ObjTyInfo)
, _rgSubscriptionRoot :: !(Maybe ObjTyInfo)
} deriving (Show, Eq)

View File

@@ -69,7 +69,7 @@ assertSameLocationNodes
assertSameLocationNodes typeLocs =
case Set.toList (Set.fromList typeLocs) of
-- this shouldn't happen
[] -> return VT.HasuraType
[] -> return VT.TLHasuraType
[loc] -> return loc
_ -> throw400 NotSupported msg
where
@@ -124,11 +124,11 @@ getExecPlanPartial userInfo sc enableAL req = do
typeLoc <- assertSameLocationNodes typeLocs
case typeLoc of
VT.HasuraType -> do
VT.TLHasuraType -> do
rootSelSet <- runReaderT (VQ.validateGQ queryParts) gCtx
let varDefs = G._todVariableDefinitions $ VQ.qpOpDef queryParts
return $ GExPHasura (gCtx, rootSelSet, varDefs)
VT.RemoteType _ rsi ->
VT.TLRemoteType _ rsi ->
return $ GExPRemote rsi opDef
where
role = userRole userInfo

View File

@@ -22,12 +22,12 @@ import qualified Network.Wreq as Wreq
import Hasura.HTTP (wreqOptions)
import Hasura.RQL.DDL.Headers (getHeadersFromConf)
import Hasura.RQL.Types
import Hasura.Server.Utils (bsToTxt, httpExceptToJSON)
import qualified Hasura.GraphQL.Context as GC
import qualified Hasura.GraphQL.Schema as GS
import qualified Hasura.GraphQL.Validate.Types as VT
introspectionQuery :: BL.ByteString
introspectionQuery = $(embedStringFile "src-rsr/introspection.json")
@@ -36,61 +36,75 @@ fetchRemoteSchema
=> HTTP.Manager
-> RemoteSchemaName
-> RemoteSchemaInfo
-> m GS.RemoteGCtx
-> m GC.RemoteGCtx
fetchRemoteSchema manager name def@(RemoteSchemaInfo url headerConf _) = do
headers <- getHeadersFromConf headerConf
let hdrs = map (\(hn, hv) -> (CI.mk . T.encodeUtf8 $ hn, T.encodeUtf8 hv)) headers
let hdrs = flip map headers $
\(hn, hv) -> (CI.mk . T.encodeUtf8 $ hn, T.encodeUtf8 hv)
options = wreqOptions manager hdrs
res <- liftIO $ try $ Wreq.postWith options (show url) introspectionQuery
resp <- either throwHttpErr return res
let respData = resp ^. Wreq.responseBody
statusCode = resp ^. Wreq.responseStatus . Wreq.statusCode
when (statusCode /= 200) $ schemaErr $ show respData
when (statusCode /= 200) $ throwNon200 statusCode respData
introspectRes :: (FromIntrospection IntrospectionResult) <-
either schemaErr return $ J.eitherDecode respData
either (remoteSchemaErr . T.pack) return $ J.eitherDecode respData
let (sDoc, qRootN, mRootN, sRootN) =
fromIntrospection introspectRes
typMap <- either remoteSchemaErr return $ VT.fromSchemaDoc sDoc $
VT.RemoteType name def
VT.TLRemoteType name def
let mQrTyp = Map.lookup qRootN typMap
mMrTyp = maybe Nothing (\mr -> Map.lookup mr typMap) mRootN
mSrTyp = maybe Nothing (\sr -> Map.lookup sr typMap) sRootN
mMrTyp = maybe Nothing (`Map.lookup` typMap) mRootN
mSrTyp = maybe Nothing (`Map.lookup` typMap) sRootN
qrTyp <- liftMaybe noQueryRoot mQrTyp
let mRmQR = VT.getObjTyM qrTyp
mRmMR = join $ VT.getObjTyM <$> mMrTyp
mRmSR = join $ VT.getObjTyM <$> mSrTyp
rmQR <- liftMaybe (err400 Unexpected "query root has to be an object type") mRmQR
return $ GS.RemoteGCtx typMap rmQR mRmMR mRmSR
return $ GC.RemoteGCtx typMap rmQR mRmMR mRmSR
where
noQueryRoot = err400 Unexpected "query root not found in remote schema"
remoteSchemaErr :: (MonadError QErr m) => T.Text -> m a
remoteSchemaErr = throw400 RemoteSchemaError
schemaErr err = remoteSchemaErr (T.pack err)
throwHttpErr :: (MonadError QErr m) => HTTP.HttpException -> m a
throwHttpErr _ = schemaErr $
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"
decodeNon200Resp bs = case J.eitherDecode bs of
Right a -> J.object ["response" J..= (a :: J.Value)]
Left _ -> J.object ["raw_body" J..= bsToTxt (BL.toStrict bs)]
mergeSchemas
:: (MonadIO m, MonadError QErr m)
:: (MonadError QErr m)
=> RemoteSchemaMap
-> GS.GCtxMap
-> HTTP.Manager
-> m (GS.GCtxMap, GS.GCtx) -- the merged GCtxMap and the default GCtx without roles
mergeSchemas rmSchemaMap gCtxMap httpManager = do
remoteSchemas <- forM (Map.toList rmSchemaMap) $ \(name, def) ->
fetchRemoteSchema httpManager name def
-- the merged GCtxMap and the default GCtx without roles
-> m (GS.GCtxMap, GS.GCtx)
mergeSchemas rmSchemaMap gCtxMap = do
def <- mkDefaultRemoteGCtx remoteSchemas
merged <- mergeRemoteSchema gCtxMap def
return (merged, def)
where
remoteSchemas = map rscGCtx $ Map.elems rmSchemaMap
mkDefaultRemoteGCtx
:: (MonadError QErr m)
=> [GS.RemoteGCtx] -> m GS.GCtx
=> [GC.RemoteGCtx] -> m GS.GCtx
mkDefaultRemoteGCtx =
foldlM (\combG -> mergeGCtx combG . convRemoteGCtx) GS.emptyGCtx
@@ -126,12 +140,12 @@ mergeGCtx gCtx rmMergedGCtx = do
}
return updatedGCtx
convRemoteGCtx :: GS.RemoteGCtx -> GS.GCtx
convRemoteGCtx :: GC.RemoteGCtx -> GS.GCtx
convRemoteGCtx rmGCtx =
GS.emptyGCtx { GS._gTypes = GS._rgTypes rmGCtx
, GS._gQueryRoot = GS._rgQueryRoot rmGCtx
, GS._gMutRoot = GS._rgMutationRoot rmGCtx
, GS._gSubRoot = GS._rgSubscriptionRoot rmGCtx
GS.emptyGCtx { GS._gTypes = GC._rgTypes rmGCtx
, GS._gQueryRoot = GC._rgQueryRoot rmGCtx
, GS._gMutRoot = GC._rgMutationRoot rmGCtx
, GS._gSubRoot = GC._rgSubscriptionRoot rmGCtx
}
@@ -185,8 +199,8 @@ mergeTyMaps
-> VT.TypeMap
mergeTyMaps hTyMap rmTyMap newQR newMR =
let newTyMap = hTyMap <> rmTyMap
newTyMap' = Map.insert (G.NamedType "query_root") (VT.TIObj newQR) $
newTyMap
newTyMap' =
Map.insert (G.NamedType "query_root") (VT.TIObj newQR) newTyMap
in maybe newTyMap' (\mr -> Map.insert
(G.NamedType "mutation_root")
(VT.TIObj mr) newTyMap') newMR
@@ -280,28 +294,6 @@ instance J.FromJSON (FromIntrospection G.ValueConst) where
parseJSON = J.withText "defaultValue" $ \t -> fmap FromIntrospection
$ either (fail . T.unpack) return $ G.parseValueConst t
-- instance J.FromJSON (FromIntrospection G.ListType) where
-- parseJSON = parseJSON
-- instance (J.FromJSON (G.ObjectFieldG a)) =>
-- J.FromJSON (FromIntrospection (G.ObjectValueG a)) where
-- parseJSON = fmap (FromIntrospection . G.ObjectValueG) . J.parseJSON
-- instance (J.FromJSON a) => J.FromJSON (FromIntrospection (G.ObjectFieldG a)) where
-- parseJSON = J.withObject "ObjectValueG a" $ \o -> do
-- name <- o .: "name"
-- ofVal <- o .: "value"
-- return $ FromIntrospection $ G.ObjectFieldG name ofVal
-- instance J.FromJSON (FromIntrospection G.Value) where
-- parseJSON =
-- fmap FromIntrospection .
-- $(J.mkParseJSON J.defaultOptions{J.sumEncoding=J.UntaggedValue} ''G.Value)
-- $(J.deriveFromJSON J.defaultOptions{J.sumEncoding=J.UntaggedValue} ''G.Value)
instance J.FromJSON (FromIntrospection G.InterfaceTypeDefinition) where
parseJSON = J.withObject "InterfaceTypeDefinition" $ \o -> do
kind <- o .: "kind"

View File

@@ -1,7 +1,7 @@
module Hasura.GraphQL.Schema
( mkGCtxMap
, updateSCWithGCtx
, GCtxMap
, buildGCtxMapPG
, getGCtx
, GCtx(..)
, OpCtx(..)
@@ -11,7 +11,6 @@ module Hasura.GraphQL.Schema
, isAggFld
, qualObjectToName
-- Schema stitching related
, RemoteGCtx (..)
, checkSchemaConflicts
, checkConflictingNode
, emptyGCtx
@@ -20,7 +19,6 @@ module Hasura.GraphQL.Schema
) where
import Data.Has
import Data.Maybe (maybeToList)
import qualified Data.HashMap.Strict as Map
@@ -53,18 +51,6 @@ getTabInfo tc t =
onNothing (Map.lookup t tc) $
throw500 $ "table not found: " <>> t
data RemoteGCtx
= RemoteGCtx
{ _rgTypes :: !TypeMap
, _rgQueryRoot :: !ObjTyInfo
, _rgMutationRoot :: !(Maybe ObjTyInfo)
, _rgSubscriptionRoot :: !(Maybe ObjTyInfo)
} deriving (Show, Eq)
instance Has TypeMap RemoteGCtx where
getter = _rgTypes
modifier f ctx = ctx { _rgTypes = f $ _rgTypes ctx }
type SelField = Either PGColInfo (RelInfo, Bool, AnnBoolExpPartialSQL, Maybe Int, Bool)
qualObjectToName :: (ToTxt a) => QualifiedObject a -> G.Name
@@ -265,7 +251,7 @@ mkTableObj
-> [SelField]
-> ObjTyInfo
mkTableObj tn allowedFlds =
mkObjTyInfo (Just desc) (mkTableTy tn) Set.empty (mapFromL _fiName flds) HasuraType
mkObjTyInfo (Just desc) (mkTableTy tn) Set.empty (mapFromL _fiName flds) TLHasuraType
where
flds = concatMap (either (pure . mkPGColFld) mkRelFld') allowedFlds
mkRelFld' (relInfo, allowAgg, _, _, isNullable) =
@@ -1778,12 +1764,14 @@ mkGCtxMap tableCache functionCache = do
tableFltr ti = not (tiSystemDefined ti)
&& isValidObjectName (tiName ti)
updateSCWithGCtx
:: (MonadError QErr m)
=> SchemaCache -> m SchemaCache
updateSCWithGCtx sc = do
-- | build GraphQL schema from postgres tables and functions
buildGCtxMapPG
:: (QErrM m, CacheRWM m)
=> m ()
buildGCtxMapPG = do
sc <- askSchemaCache
gCtxMap <- mkGCtxMap (scTables sc) (scFunctions sc)
return $ sc {scGCtxMap = gCtxMap}
writeSchemaCache sc {scGCtxMap = gCtxMap}
getGCtx :: (CacheRM m) => RoleName -> GCtxMap -> m GCtx
getGCtx rn ctxMap = do

View File

@@ -126,8 +126,8 @@ type ParamMap = Map.HashMap G.Name InpValInfo
-- | location of the type: a hasura type or a remote type
data TypeLoc
= HasuraType
| RemoteType RemoteSchemaName RemoteSchemaInfo
= TLHasuraType
| TLRemoteType !RemoteSchemaName !RemoteSchemaInfo
deriving (Show, Eq, TH.Lift, Generic)
instance Hashable TypeLoc

View File

@@ -41,7 +41,6 @@ import Hasura.RQL.Types
import Hasura.SQL.Types
import qualified Database.PG.Query as Q
import qualified Hasura.GraphQL.Schema as GS
import qualified Hasura.RQL.DDL.EventTrigger as DE
import qualified Hasura.RQL.DDL.Permission as DP
import qualified Hasura.RQL.DDL.Permission.Internal as DP
@@ -287,17 +286,10 @@ applyQP2 (ReplaceMetadata tables templates mFunctions mSchemas mCollections mAll
-- remote schemas
onJust mSchemas $ \schemas ->
withPathK "remote_schemas" $
indexedForM_ schemas $ \conf ->
void $ DRS.addRemoteSchemaP1 conf
>>= DRS.addRemoteSchemaP2 conf
indexedMapM_ (void . DRS.addRemoteSchemaP2) schemas
-- build GraphQL Context
sc <- GS.updateSCWithGCtx =<< askSchemaCache
-- resolve remote schemas
httpMgr <- askHttpManager
newSc <- DRS.resolveRemoteSchemas sc httpMgr
writeSchemaCache newSc
-- build GraphQL Context with Remote schemas
DRS.buildGCtxMap
return successMsg

View File

@@ -1,13 +1,12 @@
module Hasura.RQL.DDL.RemoteSchema
( runAddRemoteSchema
, addRemoteSchemaToCache
, resolveRemoteSchemas
, runRemoveRemoteSchema
, removeRemoteSchemaFromCache
, removeRemoteSchemaFromCatalog
, refreshGCtxMapInSchema
, runReloadRemoteSchema
, buildGCtxMap
, fetchRemoteSchemas
, addRemoteSchemaP1
, addRemoteSchemaP2Setup
, addRemoteSchemaP2
) where
@@ -17,10 +16,10 @@ import Hasura.Prelude
import qualified Data.Aeson as J
import qualified Data.HashMap.Strict as Map
import qualified Database.PG.Query as Q
import qualified Network.HTTP.Client as HTTP
import Hasura.GraphQL.RemoteServer
import Hasura.RQL.Types
import Hasura.SQL.Types
import qualified Hasura.GraphQL.Schema as GS
@@ -31,20 +30,30 @@ runAddRemoteSchema
)
=> AddRemoteSchemaQuery -> m EncJSON
runAddRemoteSchema q = do
addRemoteSchemaP1 q >>= addRemoteSchemaP2 q
addRemoteSchemaP1 name >> addRemoteSchemaP2 q
where
name = _arsqName q
addRemoteSchemaP1
:: ( QErrM m, UserInfoM m
, MonadIO m, HasHttpManager m
)
=> AddRemoteSchemaQuery -> m RemoteSchemaInfo
addRemoteSchemaP1 q = do
:: (QErrM m, UserInfoM m, CacheRM m)
=> RemoteSchemaName -> m ()
addRemoteSchemaP1 name = do
adminOnly
remoteSchemaMap <- scRemoteSchemas <$> askSchemaCache
onJust (Map.lookup name remoteSchemaMap) $ const $
throw400 AlreadyExists $ "remote schema with name "
<> name <<> " already exists"
addRemoteSchemaP2Setup
:: (QErrM m, CacheRWM m, MonadIO m, HasHttpManager m)
=> AddRemoteSchemaQuery -> m RemoteSchemaCtx
addRemoteSchemaP2Setup q = do
httpMgr <- askHttpManager
rsi <- validateRemoteSchemaDef def
-- TODO:- Maintain a cache of remote schema with it's GCtx
void $ fetchRemoteSchema httpMgr name rsi
return rsi
gCtx <- fetchRemoteSchema httpMgr name rsi
let rsCtx = RemoteSchemaCtx name gCtx rsi
addRemoteSchemaToCache rsCtx
return rsCtx
where
AddRemoteSchemaQuery name def _ = q
@@ -52,44 +61,19 @@ addRemoteSchemaP2
:: ( QErrM m
, CacheRWM m
, MonadTx m
, MonadIO m, HasHttpManager m
)
=> AddRemoteSchemaQuery
-> RemoteSchemaInfo
-> m EncJSON
addRemoteSchemaP2 q rsi = do
addRemoteSchemaToCache name rsi
addRemoteSchemaP2 q = do
void $ addRemoteSchemaP2Setup q
liftTx $ addRemoteSchemaToCatalog q
return successMsg
where
name = _arsqName q
addRemoteSchemaToCache
:: CacheRWM m
=> RemoteSchemaName
-> RemoteSchemaInfo
-> m ()
addRemoteSchemaToCache name rmDef = do
sc <- askSchemaCache
let resolvers = scRemoteResolvers sc
writeSchemaCache sc
{scRemoteResolvers = Map.insert name rmDef resolvers}
refreshGCtxMapInSchema
:: (CacheRWM m, MonadIO m, MonadError QErr m, HasHttpManager m)
=> m ()
refreshGCtxMapInSchema = do
sc <- askSchemaCache
gCtxMap <- GS.mkGCtxMap (scTables sc) (scFunctions sc)
httpMgr <- askHttpManager
(mergedGCtxMap, defGCtx) <-
mergeSchemas (scRemoteResolvers sc) gCtxMap httpMgr
writeSchemaCache sc { scGCtxMap = mergedGCtxMap
, scDefaultRemoteGCtx = defGCtx }
runRemoveRemoteSchema
:: (QErrM m, UserInfoM m, CacheRWM m, MonadTx m)
=> RemoveRemoteSchemaQuery -> m EncJSON
runRemoveRemoteSchema (RemoveRemoteSchemaQuery rsn)= do
=> RemoteSchemaNameQuery -> m EncJSON
runRemoveRemoteSchema (RemoteSchemaNameQuery rsn)= do
removeRemoteSchemaP1 rsn
removeRemoteSchemaP2 rsn
@@ -99,10 +83,9 @@ removeRemoteSchemaP1
removeRemoteSchemaP1 rsn = do
adminOnly
sc <- askSchemaCache
let resolvers = scRemoteResolvers sc
case Map.lookup rsn resolvers of
Just _ -> return ()
Nothing -> throw400 NotExists "no such remote schema"
let rmSchemas = scRemoteSchemas sc
void $ onNothing (Map.lookup rsn rmSchemas) $
throw400 NotExists "no such remote schema"
removeRemoteSchemaP2
:: ( CacheRWM m
@@ -111,30 +94,40 @@ removeRemoteSchemaP2
=> RemoteSchemaName
-> m EncJSON
removeRemoteSchemaP2 rsn = do
removeRemoteSchemaFromCache rsn
delRemoteSchemaFromCache rsn
liftTx $ removeRemoteSchemaFromCatalog rsn
return successMsg
removeRemoteSchemaFromCache
:: CacheRWM m => RemoteSchemaName -> m ()
removeRemoteSchemaFromCache rsn = do
sc <- askSchemaCache
let resolvers = scRemoteResolvers sc
writeSchemaCache sc {scRemoteResolvers = Map.delete rsn resolvers}
resolveRemoteSchemas
:: ( MonadError QErr m
, MonadIO m
runReloadRemoteSchema
:: ( QErrM m, UserInfoM m , CacheRWM m
, MonadIO m, HasHttpManager m
)
=> SchemaCache -> HTTP.Manager -> m SchemaCache
resolveRemoteSchemas sc httpMgr = do
(mergedGCtxMap, defGCtx) <-
mergeSchemas (scRemoteResolvers sc) gCtxMap httpMgr
return $ sc { scGCtxMap = mergedGCtxMap
, scDefaultRemoteGCtx = defGCtx
}
where
gCtxMap = scGCtxMap sc
=> RemoteSchemaNameQuery -> m EncJSON
runReloadRemoteSchema (RemoteSchemaNameQuery name) = do
adminOnly
rmSchemas <- scRemoteSchemas <$> askSchemaCache
rsi <- fmap rscInfo $ onNothing (Map.lookup name rmSchemas) $
throw400 NotExists $ "remote schema with name "
<> name <<> " does not exist"
httpMgr <- askHttpManager
gCtx <- fetchRemoteSchema httpMgr name rsi
delRemoteSchemaFromCache name
addRemoteSchemaToCache $ RemoteSchemaCtx name gCtx rsi
return successMsg
-- | build GraphQL schema
buildGCtxMap
:: (QErrM m, CacheRWM m) => m ()
buildGCtxMap = do
-- build GraphQL Context with Hasura schema
GS.buildGCtxMapPG
sc <- askSchemaCache
let gCtxMap = scGCtxMap sc
-- Stitch remote schemas
(mergedGCtxMap, defGCtx) <- mergeSchemas (scRemoteSchemas sc) gCtxMap
writeSchemaCache sc { scGCtxMap = mergedGCtxMap
, scDefaultRemoteGCtx = defGCtx
}
addRemoteSchemaToCatalog
:: AddRemoteSchemaQuery

View File

@@ -65,7 +65,7 @@ trackExistingTableOrViewP1 (TrackTable vn) = do
throw400 AlreadyTracked $ "view/table already tracked : " <>> vn
trackExistingTableOrViewP2
:: (QErrM m, CacheRWM m, MonadTx m, MonadIO m, HasHttpManager m)
:: (QErrM m, CacheRWM m, MonadTx m)
=> QualifiedTable -> Bool -> m EncJSON
trackExistingTableOrViewP2 vn isSystemDefined = do
sc <- askSchemaCache
@@ -79,9 +79,6 @@ trackExistingTableOrViewP2 vn isSystemDefined = do
_ -> throw500 $ "more than one row found for: " <>> vn
liftTx $ Q.catchE defaultTxErrorHandler $ saveTableToCatalog vn
-- refresh the gCtx in schema cache
refreshGCtxMapInSchema
return successMsg
where
QualifiedObject sn tn = vn
@@ -99,9 +96,7 @@ trackExistingTableOrViewP2 vn isSystemDefined = do
|] (sn, tn) True
runTrackTableQ
:: ( QErrM m, CacheRWM m, MonadTx m
, MonadIO m, HasHttpManager m, UserInfoM m
)
:: (QErrM m, CacheRWM m, MonadTx m, UserInfoM m)
=> TrackTable -> m EncJSON
runTrackTableQ q = do
trackExistingTableOrViewP1 q
@@ -257,7 +252,7 @@ unTrackExistingTableOrViewP1 (UntrackTable vn _) = do
"view/table already untracked : " <>> vn
unTrackExistingTableOrViewP2
:: (QErrM m, CacheRWM m, MonadTx m, MonadIO m, HasHttpManager m)
:: (QErrM m, CacheRWM m, MonadTx m)
=> UntrackTable -> m EncJSON
unTrackExistingTableOrViewP2 (UntrackTable qtn cascade) = do
sc <- askSchemaCache
@@ -275,9 +270,6 @@ unTrackExistingTableOrViewP2 (UntrackTable qtn cascade) = do
-- delete the table and its direct dependencies
delTableAndDirectDeps qtn
-- refresh the gctxmap in schema cache
refreshGCtxMapInSchema
return successMsg
where
isDirectDep = \case
@@ -285,9 +277,7 @@ unTrackExistingTableOrViewP2 (UntrackTable qtn cascade) = do
_ -> False
runUntrackTableQ
:: ( QErrM m, CacheRWM m, MonadTx m
, MonadIO m, HasHttpManager m, UserInfoM m
)
:: (QErrM m, CacheRWM m, MonadTx m, UserInfoM m)
=> UntrackTable -> m EncJSON
runUntrackTableQ q = do
unTrackExistingTableOrViewP1 q
@@ -349,7 +339,6 @@ buildSchemaCacheG withSetup = do
when withSetup $ liftTx $ Q.catchE defaultTxErrorHandler clearHdbViews
-- reset the current schemacache
writeSchemaCache emptySchemaCache
hMgr <- askHttpManager
sqlGenCtx <- askSQLGenCtx
-- fetch all catalog metadata
@@ -442,12 +431,11 @@ buildSchemaCacheG withSetup = do
-- allow list
replaceAllowlist $ concatMap _cdQueries allowlistDefs
-- build GraphQL context
postGCtxSc <- askSchemaCache >>= GS.updateSCWithGCtx
writeSchemaCache postGCtxSc
-- build GraphQL context with tables and functions
GS.buildGCtxMapPG
-- remote schemas
forM_ remoteSchemas $ resolveSingleRemoteSchema hMgr
forM_ remoteSchemas resolveSingleRemoteSchema
where
permHelper setup sqlGenCtx qt rn pDef pa = do
@@ -460,17 +448,16 @@ buildSchemaCacheG withSetup = do
addPermToCache qt rn pa permInfo deps
-- p2F qt rn p1Res
resolveSingleRemoteSchema hMgr rs = do
let AddRemoteSchemaQuery name def _ = rs
resolveSingleRemoteSchema rs = do
let AddRemoteSchemaQuery name _ _ = rs
mkInconsObj = InconsistentMetadataObj (MORemoteSchema name)
MOTRemoteSchema (toJSON rs)
handleInconsistentObj mkInconsObj $ do
rsi <- validateRemoteSchemaDef def
addRemoteSchemaToCache name rsi
rsCtx <- addRemoteSchemaP2Setup rs
sc <- askSchemaCache
let gCtxMap = scGCtxMap sc
defGCtx = scDefaultRemoteGCtx sc
rGCtx <- convRemoteGCtx <$> fetchRemoteSchema hMgr name rsi
rGCtx = convRemoteGCtx $ rscGCtx rsCtx
mergedGCtxMap <- mergeRemoteSchema gCtxMap rGCtx
mergedDefGCtx <- mergeGCtx defGCtx rGCtx
writeSchemaCache sc { scGCtxMap = mergedGCtxMap
@@ -599,9 +586,6 @@ execWithMDCheck (RunSQL t cascade _) = do
bool withoutReload withReload reloadRequired
-- refresh the gCtxMap in schema cache
refreshGCtxMapInSchema
return res
where
reportFuncs = T.intercalate ", " . map dquoteTxt

View File

@@ -7,20 +7,22 @@ import System.Environment (lookupEnv)
import qualified Data.Aeson as J
import qualified Data.Aeson.Casing as J
import qualified Data.Aeson.TH as J
import qualified Data.HashMap.Strict as Map
import qualified Data.Text as T
import qualified Database.PG.Query as Q
import qualified Network.URI.Extended as N
import Hasura.RQL.DDL.Headers (HeaderConf (..))
import Hasura.RQL.Types.Error
import Hasura.SQL.Types (DQuote)
type UrlFromEnv = Text
newtype RemoteSchemaName
= RemoteSchemaName
{ unRemoteSchemaName :: Text}
deriving (Show, Eq, Lift, Hashable, J.ToJSON, J.ToJSONKey, J.FromJSON, Q.ToPrepArg, Q.FromCol)
deriving ( Show, Eq, Lift, Hashable, J.ToJSON, J.ToJSONKey
, J.FromJSON, Q.ToPrepArg, Q.FromCol, DQuote
)
data RemoteSchemaInfo
= RemoteSchemaInfo
@@ -43,25 +45,6 @@ data RemoteSchemaDef
$(J.deriveJSON (J.aesonDrop 4 J.snakeCase) ''RemoteSchemaDef)
type RemoteSchemaMap = Map.HashMap RemoteSchemaName RemoteSchemaInfo
-- instance J.ToJSON RemoteSchemaDef where
-- toJSON (RemoteSchemaDef name eUrlVal headers fwdHdrs) =
-- case eUrlVal of
-- Left url ->
-- J.object [ "url" J..= url
-- , "headers" J..= headers
-- , "name" J..= name
-- , "forward_client_headers" J..= fwdHdrs
-- ]
-- Right urlFromEnv ->
-- J.object [ "url_from_env" J..= urlFromEnv
-- , "headers" J..= headers
-- , "name" J..= name
-- , "forward_client_headers" J..= fwdHdrs
-- ]
data AddRemoteSchemaQuery
= AddRemoteSchemaQuery
{ _arsqName :: !RemoteSchemaName -- TODO: name validation: cannot be empty?
@@ -71,20 +54,12 @@ data AddRemoteSchemaQuery
$(J.deriveJSON (J.aesonDrop 5 J.snakeCase) ''AddRemoteSchemaQuery)
-- data AddRemoteSchemaQuery'
-- = AddRemoteSchemaQuery'
-- { _arsqUrl :: !(Maybe N.URI)
-- , _arsqUrlFromEnv :: !(Maybe Text)
-- , _arsqHeaders :: !(Maybe [HeaderConf])
-- , _arsqForwardClientHeaders :: !Bool
-- } deriving (Show, Eq, Lift)
data RemoveRemoteSchemaQuery
= RemoveRemoteSchemaQuery
{ _rrsqName :: !RemoteSchemaName
newtype RemoteSchemaNameQuery
= RemoteSchemaNameQuery
{ _rsnqName :: RemoteSchemaName
} deriving (Show, Eq, Lift)
$(J.deriveJSON (J.aesonDrop 5 J.snakeCase) ''RemoveRemoteSchemaQuery)
$(J.deriveJSON (J.aesonDrop 5 J.snakeCase) ''RemoteSchemaNameQuery)
getUrlFromEnv :: (MonadIO m, MonadError QErr m) => Text -> m N.URI
getUrlFromEnv urlFromEnv = do

View File

@@ -24,6 +24,11 @@ module Hasura.RQL.Types.SchemaCache
, modTableInCache
, delTableFromCache
, RemoteSchemaCtx(..)
, RemoteSchemaMap
, addRemoteSchemaToCache
, delRemoteSchemaFromCache
, WithDeps
, CacheRM(..)
@@ -103,6 +108,7 @@ module Hasura.RQL.Types.SchemaCache
) where
import qualified Hasura.GraphQL.Context as GC
import Hasura.Prelude
import Hasura.RQL.Types.BoolExp
import Hasura.RQL.Types.Common
@@ -410,6 +416,18 @@ $(deriveToJSON (aesonDrop 2 snakeCase) ''FunctionInfo)
type TableCache = M.HashMap QualifiedTable TableInfo -- info of all tables
type FunctionCache = M.HashMap QualifiedFunction FunctionInfo -- info of all functions
data RemoteSchemaCtx
= RemoteSchemaCtx
{ rscName :: !RemoteSchemaName
, rscGCtx :: !GC.RemoteGCtx
, rscInfo :: !RemoteSchemaInfo
} deriving (Show, Eq)
instance ToJSON RemoteSchemaCtx where
toJSON = toJSON . rscInfo
type RemoteSchemaMap = M.HashMap RemoteSchemaName RemoteSchemaCtx
type DepMap = M.HashMap SchemaObjId (HS.HashSet SchemaDependency)
addToDepMap :: SchemaObjId -> [SchemaDependency] -> DepMap -> DepMap
@@ -443,8 +461,8 @@ data SchemaCache
{ scTables :: !TableCache
, scFunctions :: !FunctionCache
, scQTemplates :: !QTemplateCache
, scRemoteSchemas :: !RemoteSchemaMap
, scAllowlist :: !(HS.HashSet GQLQuery)
, scRemoteResolvers :: !RemoteSchemaMap
, scGCtxMap :: !GC.GCtxMap
, scDefaultRemoteGCtx :: !GC.GCtx
, scDepMap :: !DepMap
@@ -513,8 +531,8 @@ delQTemplateFromCache qtn = do
emptySchemaCache :: SchemaCache
emptySchemaCache =
SchemaCache M.empty M.empty M.empty HS.empty
M.empty M.empty GC.emptyGCtx mempty []
SchemaCache M.empty M.empty M.empty M.empty
HS.empty M.empty GC.emptyGCtx mempty []
modTableCache :: (CacheRWM m) => TableCache -> m ()
modTableCache tc = do
@@ -789,6 +807,32 @@ data TemplateParamInfo
, tpiDefault :: !(Maybe Value)
} deriving (Show, Eq)
addRemoteSchemaToCache
:: (QErrM m, CacheRWM m) => RemoteSchemaCtx -> m ()
addRemoteSchemaToCache rmCtx = do
sc <- askSchemaCache
let rmSchemas = scRemoteSchemas sc
name = rscName rmCtx
-- ideally, remote schema shouldn't present in cache
-- if present unexpected 500 is thrown
onJust (M.lookup name rmSchemas) $ const $
throw500 $ "remote schema with name " <> name
<<> " already found in cache"
writeSchemaCache sc
{scRemoteSchemas = M.insert name rmCtx rmSchemas}
delRemoteSchemaFromCache
:: (QErrM m, CacheRWM m) => RemoteSchemaName -> m ()
delRemoteSchemaFromCache name = do
sc <- askSchemaCache
let rmSchemas = scRemoteSchemas sc
-- ideally, remote schema should be present in cache
-- if not present unexpected 500 is thrown
void $ onNothing (M.lookup name rmSchemas) $
throw500 $ "remote schema with name " <> name
<<> " not found in cache"
writeSchemaCache sc {scRemoteSchemas = M.delete name rmSchemas}
replaceAllowlist
:: (CacheRWM m)
=> QueryList -> m ()

View File

@@ -35,7 +35,6 @@ import qualified Database.PG.Query as Q
import qualified Hasura.GraphQL.Execute as E
import qualified Hasura.GraphQL.Execute.LiveQuery as EL
import qualified Hasura.GraphQL.Explain as GE
import qualified Hasura.GraphQL.Schema as GS
import qualified Hasura.GraphQL.Transport.HTTP as GH
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
import qualified Hasura.GraphQL.Transport.WebSocket as WS
@@ -43,7 +42,6 @@ import qualified Hasura.Logging as L
import Hasura.EncJSON
import Hasura.Prelude hiding (get, put)
import Hasura.RQL.DDL.RemoteSchema
import Hasura.RQL.DDL.Schema.Table
import Hasura.RQL.DML.QueryTemplate
import Hasura.RQL.Types
@@ -188,7 +186,7 @@ logResult
-> Either QErr BL.ByteString -> Maybe (UTCTime, UTCTime)
-> m ()
logResult userInfoM req reqBody logger res qTime =
liftIO $ (L.unLogger logger) $ mkAccessLog userInfoM req (reqBody, res) qTime
liftIO $ L.unLogger logger $ mkAccessLog userInfoM req (reqBody, res) qTime
logError
:: MonadIO m
@@ -254,7 +252,7 @@ v1QueryHandler :: RQLQuery -> Handler (HttpResponse EncJSON)
v1QueryHandler query = do
scRef <- scCacheRef . hcServerCtx <$> ask
logger <- scLogger . hcServerCtx <$> ask
res <- bool (fst <$> dbAction) (withSCUpdate scRef logger dbActionReload) $
res <- bool (fst <$> dbAction) (withSCUpdate scRef logger dbAction) $
queryNeedsReload query
return $ HttpResponse res Nothing
where
@@ -269,14 +267,6 @@ v1QueryHandler query = do
instanceId <- scInstanceId . hcServerCtx <$> ask
runQuery pgExecCtx instanceId userInfo schemaCache httpMgr sqlGenCtx query
-- Also update the schema cache
dbActionReload = do
(resp, newSc) <- dbAction
httpMgr <- scManager . hcServerCtx <$> ask
--FIXME: should we be fetching the remote schema again? if not how do we get the remote schema?
newSc' <- GS.updateSCWithGCtx newSc >>= flip resolveRemoteSchemas httpMgr
return (resp, newSc')
v1Alpha1GQHandler :: GH.GQLReqUnparsed -> Handler (HttpResponse EncJSON)
v1Alpha1GQHandler query = do
userInfo <- asks hcUser
@@ -335,7 +325,7 @@ consoleAssetsHandler logger dir path = do
headers = ("Content-Type", mimeType) : encHeader
mkConsoleHTML :: T.Text -> AuthMode -> Bool -> Maybe Text -> Either String T.Text
mkConsoleHTML path authMode enableTelemetry consoleAssetsDir = do
mkConsoleHTML path authMode enableTelemetry consoleAssetsDir =
bool (Left errMsg) (Right res) $ null errs
where
(errs, res) = M.checkedSubstitute consoleTmplt $
@@ -451,8 +441,7 @@ httpApp corsCfg serverCtx enableConsole consoleAssetsDir enableTelemetry = do
middleware $ corsMiddleware (mkDefaultCorsPolicy corsCfg)
-- API Console and Root Dir
when (enableConsole && enableMetadata) $ do
serveApiConsole
when (enableConsole && enableMetadata) serveApiConsole
-- Health check endpoint
get "healthz" $ do

View File

@@ -68,7 +68,8 @@ data RQLQuery
-- schema-stitching, custom resolver related
| RQAddRemoteSchema !AddRemoteSchemaQuery
| RQRemoveRemoteSchema !RemoveRemoteSchemaQuery
| RQRemoveRemoteSchema !RemoteSchemaNameQuery
| RQReloadRemoteSchema !RemoteSchemaNameQuery
| RQCreateEventTrigger !CreateEventTriggerQuery
| RQDeleteEventTrigger !DeleteEventTriggerQuery
@@ -214,6 +215,7 @@ queryNeedsReload qi = case qi of
RQAddRemoteSchema _ -> True
RQRemoveRemoteSchema _ -> True
RQReloadRemoteSchema _ -> True
RQCreateEventTrigger _ -> True
RQDeleteEventTrigger _ -> True
@@ -249,67 +251,73 @@ runQueryM
)
=> RQLQuery
-> m EncJSON
runQueryM rq = withPathK "args" $ case rq of
RQAddExistingTableOrView q -> runTrackTableQ q
RQTrackTable q -> runTrackTableQ q
RQUntrackTable q -> runUntrackTableQ q
runQueryM rq =
withPathK "args" $ runQueryM' <* rebuildGCtx
where
rebuildGCtx = when (queryNeedsReload rq) buildGCtxMap
RQTrackFunction q -> runTrackFunc q
RQUntrackFunction q -> runUntrackFunc q
runQueryM' = case rq of
RQAddExistingTableOrView q -> runTrackTableQ q
RQTrackTable q -> runTrackTableQ q
RQUntrackTable q -> runUntrackTableQ q
RQCreateObjectRelationship q -> runCreateObjRel q
RQCreateArrayRelationship q -> runCreateArrRel q
RQDropRelationship q -> runDropRel q
RQSetRelationshipComment q -> runSetRelComment q
RQRenameRelationship q -> runRenameRel q
RQTrackFunction q -> runTrackFunc q
RQUntrackFunction q -> runUntrackFunc q
RQCreateInsertPermission q -> runCreatePerm q
RQCreateSelectPermission q -> runCreatePerm q
RQCreateUpdatePermission q -> runCreatePerm q
RQCreateDeletePermission q -> runCreatePerm q
RQCreateObjectRelationship q -> runCreateObjRel q
RQCreateArrayRelationship q -> runCreateArrRel q
RQDropRelationship q -> runDropRel q
RQSetRelationshipComment q -> runSetRelComment q
RQRenameRelationship q -> runRenameRel q
RQDropInsertPermission q -> runDropPerm q
RQDropSelectPermission q -> runDropPerm q
RQDropUpdatePermission q -> runDropPerm q
RQDropDeletePermission q -> runDropPerm q
RQSetPermissionComment q -> runSetPermComment q
RQCreateInsertPermission q -> runCreatePerm q
RQCreateSelectPermission q -> runCreatePerm q
RQCreateUpdatePermission q -> runCreatePerm q
RQCreateDeletePermission q -> runCreatePerm q
RQGetInconsistentMetadata q -> runGetInconsistentMetadata q
RQDropInconsistentMetadata q -> runDropInconsistentMetadata q
RQDropInsertPermission q -> runDropPerm q
RQDropSelectPermission q -> runDropPerm q
RQDropUpdatePermission q -> runDropPerm q
RQDropDeletePermission q -> runDropPerm q
RQSetPermissionComment q -> runSetPermComment q
RQInsert q -> runInsert q
RQSelect q -> runSelect q
RQUpdate q -> runUpdate q
RQDelete q -> runDelete q
RQCount q -> runCount q
RQGetInconsistentMetadata q -> runGetInconsistentMetadata q
RQDropInconsistentMetadata q -> runDropInconsistentMetadata q
RQAddRemoteSchema q -> runAddRemoteSchema q
RQRemoveRemoteSchema q -> runRemoveRemoteSchema q
RQInsert q -> runInsert q
RQSelect q -> runSelect q
RQUpdate q -> runUpdate q
RQDelete q -> runDelete q
RQCount q -> runCount q
RQCreateEventTrigger q -> runCreateEventTriggerQuery q
RQDeleteEventTrigger q -> runDeleteEventTriggerQuery q
RQRedeliverEvent q -> runRedeliverEvent q
RQInvokeEventTrigger q -> runInvokeEventTrigger q
RQAddRemoteSchema q -> runAddRemoteSchema q
RQRemoveRemoteSchema q -> runRemoveRemoteSchema q
RQReloadRemoteSchema q -> runReloadRemoteSchema q
RQCreateQueryTemplate q -> runCreateQueryTemplate q
RQDropQueryTemplate q -> runDropQueryTemplate q
RQExecuteQueryTemplate q -> runExecQueryTemplate q
RQSetQueryTemplateComment q -> runSetQueryTemplateComment q
RQCreateEventTrigger q -> runCreateEventTriggerQuery q
RQDeleteEventTrigger q -> runDeleteEventTriggerQuery q
RQRedeliverEvent q -> runRedeliverEvent q
RQInvokeEventTrigger q -> runInvokeEventTrigger q
RQCreateQueryCollection q -> runCreateCollection q
RQDropQueryCollection q -> runDropCollection q
RQAddQueryToCollection q -> runAddQueryToCollection q
RQDropQueryFromCollection q -> runDropQueryFromCollection q
RQAddCollectionToAllowlist q -> runAddCollectionToAllowlist q
RQDropCollectionFromAllowlist q -> runDropCollectionFromAllowlist q
RQCreateQueryTemplate q -> runCreateQueryTemplate q
RQDropQueryTemplate q -> runDropQueryTemplate q
RQExecuteQueryTemplate q -> runExecQueryTemplate q
RQSetQueryTemplateComment q -> runSetQueryTemplateComment q
RQReplaceMetadata q -> runReplaceMetadata q
RQClearMetadata q -> runClearMetadata q
RQExportMetadata q -> runExportMetadata q
RQReloadMetadata q -> runReloadMetadata q
RQCreateQueryCollection q -> runCreateCollection q
RQDropQueryCollection q -> runDropCollection q
RQAddQueryToCollection q -> runAddQueryToCollection q
RQDropQueryFromCollection q -> runDropQueryFromCollection q
RQAddCollectionToAllowlist q -> runAddCollectionToAllowlist q
RQDropCollectionFromAllowlist q -> runDropCollectionFromAllowlist q
RQDumpInternalState q -> runDumpInternalState q
RQReplaceMetadata q -> runReplaceMetadata q
RQClearMetadata q -> runClearMetadata q
RQExportMetadata q -> runExportMetadata q
RQReloadMetadata q -> runReloadMetadata q
RQRunSql q -> runRunSQL q
RQDumpInternalState q -> runDumpInternalState q
RQBulk qs -> encJFromList <$> indexedMapM runQueryM qs
RQRunSql q -> runRunSQL q
RQBulk qs -> encJFromList <$> indexedMapM runQueryM qs

View File

@@ -145,7 +145,7 @@ computeMetrics sc =
PermissionMetric selPerms insPerms updPerms delPerms nRoles
evtTriggers = Map.size $ Map.filter (not . Map.null)
$ Map.map tiEventTriggerInfoMap usrTbls
rmSchemas = Map.size $ scRemoteResolvers sc
rmSchemas = Map.size $ scRemoteSchemas sc
funcs = Map.size $ Map.filter (not . fiSystemDefined) $ scFunctions sc
in Metrics nTables nViews relMetrics permMetrics evtTriggers rmSchemas funcs

View File

@@ -17,6 +17,7 @@ import qualified Data.Text.Encoding as TE
import qualified Data.Text.Encoding.Error as TE
import qualified Data.Text.IO as TI
import qualified Language.Haskell.TH.Syntax as TH
import qualified Network.HTTP.Client as HC
import qualified Network.HTTP.Types as HTTP
import qualified Text.Ginger as TG
import qualified Text.Regex.TDFA as TDFA
@@ -166,8 +167,29 @@ diffTimeToMicro diff =
where
aSecond = 1000 * 1000
-- ignore the following request headers from the client
-- json representation of HTTP exception
httpExceptToJSON :: HC.HttpException -> Value
httpExceptToJSON e = case e of
HC.HttpExceptionRequest x c ->
let reqObj = object
[ "host" .= bsToTxt (HC.host x)
, "port" .= show (HC.port x)
, "secure" .= HC.secure x
, "path" .= bsToTxt (HC.path x)
, "method" .= bsToTxt (HC.method x)
, "proxy" .= (showProxy <$> HC.proxy x)
, "redirectCount" .= show (HC.redirectCount x)
, "responseTimeout" .= show (HC.responseTimeout x)
, "requestVersion" .= show (HC.requestVersion x)
]
msg = show c
in object ["request" .= reqObj, "message" .= msg]
_ -> toJSON $ show e
where
showProxy (HC.Proxy h p) =
"host: " <> bsToTxt h <> " port: " <> T.pack (show p)
-- ignore the following request headers from the client
commonClientHeadersIgnored :: (IsString a) => [a]
commonClientHeadersIgnored =
[ "Content-Length", "Content-MD5", "User-Agent", "Host"