mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-15 09:22:43 +03:00
This commit is contained in:
parent
669bb40e72
commit
98784212e2
@ -103,4 +103,5 @@ if (globals.consoleMode === SERVER_CONSOLE_MODE) {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export default globals;
|
export default globals;
|
||||||
|
@ -21,12 +21,15 @@ const prefixUrl = globals.urlPrefix + appPrefix;
|
|||||||
const MANUAL_URL_CHANGED = '@addRemoteSchema/MANUAL_URL_CHANGED';
|
const MANUAL_URL_CHANGED = '@addRemoteSchema/MANUAL_URL_CHANGED';
|
||||||
const ENV_URL_CHANGED = '@addRemoteSchema/ENV_URL_CHANGED';
|
const ENV_URL_CHANGED = '@addRemoteSchema/ENV_URL_CHANGED';
|
||||||
const NAME_CHANGED = '@addRemoteSchema/NAME_CHANGED';
|
const NAME_CHANGED = '@addRemoteSchema/NAME_CHANGED';
|
||||||
|
const TIMEOUT_CONF_CHANGED = '@addRemoteSchema/TIMEOUT_CONF_CHANGED';
|
||||||
// const HEADER_CHANGED = '@addRemoteSchema/HEADER_CHANGED';
|
// const HEADER_CHANGED = '@addRemoteSchema/HEADER_CHANGED';
|
||||||
const ADDING_REMOTE_SCHEMA = '@addRemoteSchema/ADDING_REMOTE_SCHEMA';
|
const ADDING_REMOTE_SCHEMA = '@addRemoteSchema/ADDING_REMOTE_SCHEMA';
|
||||||
const ADD_REMOTE_SCHEMA_FAIL = '@addRemoteSchema/ADD_REMOTE_SCHEMA_FAIL';
|
const ADD_REMOTE_SCHEMA_FAIL = '@addRemoteSchema/ADD_REMOTE_SCHEMA_FAIL';
|
||||||
const RESET = '@addRemoteSchema/RESET';
|
const RESET = '@addRemoteSchema/RESET';
|
||||||
const FETCHING_INDIV_REMOTE_SCHEMA = '@addRemoteSchema/FETCHING_INDIV_REMOTE_SCHEMA';
|
const FETCHING_INDIV_REMOTE_SCHEMA =
|
||||||
const REMOTE_SCHEMA_FETCH_SUCCESS = '@addRemoteSchema/REMOTE_SCHEMA_FETCH_SUCCESS';
|
'@addRemoteSchema/FETCHING_INDIV_REMOTE_SCHEMA';
|
||||||
|
const REMOTE_SCHEMA_FETCH_SUCCESS =
|
||||||
|
'@addRemoteSchema/REMOTE_SCHEMA_FETCH_SUCCESS';
|
||||||
const REMOTE_SCHEMA_FETCH_FAIL = '@addRemoteSchema/REMOTE_SCHEMA_FETCH_FAIL';
|
const REMOTE_SCHEMA_FETCH_FAIL = '@addRemoteSchema/REMOTE_SCHEMA_FETCH_FAIL';
|
||||||
|
|
||||||
const DELETING_REMOTE_SCHEMA = '@addRemoteSchema/DELETING_REMOTE_SCHEMA';
|
const DELETING_REMOTE_SCHEMA = '@addRemoteSchema/DELETING_REMOTE_SCHEMA';
|
||||||
@ -47,6 +50,7 @@ const inputEventMap = {
|
|||||||
name: NAME_CHANGED,
|
name: NAME_CHANGED,
|
||||||
envName: ENV_URL_CHANGED,
|
envName: ENV_URL_CHANGED,
|
||||||
manualUrl: MANUAL_URL_CHANGED,
|
manualUrl: MANUAL_URL_CHANGED,
|
||||||
|
timeoutConf: TIMEOUT_CONF_CHANGED,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Action creators */
|
/* Action creators */
|
||||||
@ -139,12 +143,17 @@ const addRemoteSchema = () => {
|
|||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const currState = getState().remoteSchemas.addData;
|
const currState = getState().remoteSchemas.addData;
|
||||||
// const url = Endpoints.getSchema;
|
// const url = Endpoints.getSchema;
|
||||||
|
|
||||||
|
let timeoutSeconds = parseInt(currState.timeoutConf, 10);
|
||||||
|
if (isNaN(timeoutSeconds)) timeoutSeconds = 60;
|
||||||
|
|
||||||
const resolveObj = {
|
const resolveObj = {
|
||||||
name: currState.name.trim().replace(/ +/g, ''),
|
name: currState.name.trim().replace(/ +/g, ''),
|
||||||
definition: {
|
definition: {
|
||||||
url: currState.manualUrl,
|
url: currState.manualUrl,
|
||||||
url_from_env: currState.envName,
|
url_from_env: currState.envName,
|
||||||
headers: [],
|
headers: [],
|
||||||
|
timeout_seconds: timeoutSeconds,
|
||||||
forward_client_headers: currState.forwardClientHeaders,
|
forward_client_headers: currState.forwardClientHeaders,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -316,11 +325,18 @@ const modifyRemoteSchema = () => {
|
|||||||
name: currState.editState.originalName,
|
name: currState.editState.originalName,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let newTimeout = parseInt(currState.timeoutConf, 10);
|
||||||
|
let oldTimeout = parseInt(currState.editState.originalTimeoutConf, 10);
|
||||||
|
if (isNaN(newTimeout)) newTimeout = 60;
|
||||||
|
if (isNaN(oldTimeout)) oldTimeout = 60;
|
||||||
|
|
||||||
const resolveObj = {
|
const resolveObj = {
|
||||||
name: remoteSchemaName,
|
name: remoteSchemaName,
|
||||||
definition: {
|
definition: {
|
||||||
url: currState.manualUrl,
|
url: currState.manualUrl,
|
||||||
url_from_env: currState.envName,
|
url_from_env: currState.envName,
|
||||||
|
timeout_seconds: newTimeout,
|
||||||
forward_client_headers: currState.forwardClientHeaders,
|
forward_client_headers: currState.forwardClientHeaders,
|
||||||
headers: [],
|
headers: [],
|
||||||
},
|
},
|
||||||
@ -351,11 +367,13 @@ const modifyRemoteSchema = () => {
|
|||||||
name: remoteSchemaName,
|
name: remoteSchemaName,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const resolveDownObj = {
|
const resolveDownObj = {
|
||||||
name: currState.editState.originalName,
|
name: currState.editState.originalName,
|
||||||
definition: {
|
definition: {
|
||||||
url: currState.editState.originalUrl,
|
url: currState.editState.originalUrl,
|
||||||
url_from_env: currState.editState.originalEnvUrl,
|
url_from_env: currState.editState.originalEnvUrl,
|
||||||
|
timeout_seconds: oldTimeout,
|
||||||
headers: [],
|
headers: [],
|
||||||
forward_client_headers:
|
forward_client_headers:
|
||||||
currState.editState.originalForwardClientHeaders,
|
currState.editState.originalForwardClientHeaders,
|
||||||
@ -377,6 +395,7 @@ const modifyRemoteSchema = () => {
|
|||||||
...resolveDownObj,
|
...resolveDownObj,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
downQueryArgs.push(deleteRemoteSchemaDown);
|
downQueryArgs.push(deleteRemoteSchemaDown);
|
||||||
downQueryArgs.push(createRemoteSchemaDown);
|
downQueryArgs.push(createRemoteSchemaDown);
|
||||||
// End of down
|
// End of down
|
||||||
@ -389,6 +408,7 @@ const modifyRemoteSchema = () => {
|
|||||||
type: 'bulk',
|
type: 'bulk',
|
||||||
args: downQueryArgs,
|
args: downQueryArgs,
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestMsg = 'Modifying remote schema...';
|
const requestMsg = 'Modifying remote schema...';
|
||||||
const successMsg = 'Remote schema modified';
|
const successMsg = 'Remote schema modified';
|
||||||
const errorMsg = 'Modify remote schema failed';
|
const errorMsg = 'Modify remote schema failed';
|
||||||
@ -441,6 +461,11 @@ const addRemoteSchemaReducer = (state = addState, action) => {
|
|||||||
envName: action.data,
|
envName: action.data,
|
||||||
manualUrl: null,
|
manualUrl: null,
|
||||||
};
|
};
|
||||||
|
case TIMEOUT_CONF_CHANGED:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
timeoutConf: action.data,
|
||||||
|
};
|
||||||
case ADDING_REMOTE_SCHEMA:
|
case ADDING_REMOTE_SCHEMA:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -480,6 +505,9 @@ const addRemoteSchemaReducer = (state = addState, action) => {
|
|||||||
manualUrl: action.data[0].definition.url || null,
|
manualUrl: action.data[0].definition.url || null,
|
||||||
envName: action.data[0].definition.url_from_env || null,
|
envName: action.data[0].definition.url_from_env || null,
|
||||||
headers: action.data[0].definition.headers || [],
|
headers: action.data[0].definition.headers || [],
|
||||||
|
timeoutConf: action.data[0].definition.timeout_seconds
|
||||||
|
? action.data[0].definition.timeout_seconds.toString()
|
||||||
|
: '60',
|
||||||
forwardClientHeaders: action.data[0].definition.forward_client_headers,
|
forwardClientHeaders: action.data[0].definition.forward_client_headers,
|
||||||
editState: {
|
editState: {
|
||||||
...state,
|
...state,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import globals from '../../../../Globals';
|
||||||
|
import { REMOTE_SCHEMA_TIMEOUT_CONF_SUPPORT } from '../../../../helpers/versionUtils';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
import OverlayTrigger from 'react-bootstrap/lib/OverlayTrigger';
|
||||||
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
import Tooltip from 'react-bootstrap/lib/Tooltip';
|
||||||
@ -36,42 +38,92 @@ class Common extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
const styles = require('../RemoteSchema.scss');
|
const styles = require('../RemoteSchema.scss');
|
||||||
|
|
||||||
const { name, manualUrl, envName, forwardClientHeaders } = this.props;
|
const {
|
||||||
|
name,
|
||||||
|
manualUrl,
|
||||||
|
envName,
|
||||||
|
timeoutConf,
|
||||||
|
forwardClientHeaders,
|
||||||
|
} = this.props;
|
||||||
const { isModify, id } = this.props.editState;
|
const { isModify, id } = this.props.editState;
|
||||||
|
|
||||||
const isDisabled = id >= 0 && !isModify;
|
const isDisabled = id >= 0 && !isModify;
|
||||||
const urlRequired = !manualUrl && !envName;
|
const urlRequired = !manualUrl && !envName;
|
||||||
|
|
||||||
const graphqlurl = (
|
const tooltips = {
|
||||||
<Tooltip id="tooltip-cascade">
|
graphqlurl: (
|
||||||
Remote GraphQL server’s URL. E.g. https://my-domain/v1/graphql
|
<Tooltip id="tooltip-cascade">
|
||||||
</Tooltip>
|
Remote GraphQL server’s URL. E.g. https://my-domain/v1/graphql
|
||||||
);
|
</Tooltip>
|
||||||
|
),
|
||||||
|
clientHeaderForward: (
|
||||||
|
<Tooltip id="tooltip-cascade">
|
||||||
|
Toggle forwarding headers sent by the client app in the request to
|
||||||
|
your remote GraphQL server
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
additionalHeaders: (
|
||||||
|
<Tooltip id="tooltip-cascade">
|
||||||
|
Custom headers to be sent to the remote GraphQL server
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
schema: (
|
||||||
|
<Tooltip id="tooltip-cascade">
|
||||||
|
Give this GraphQL schema a friendly name.
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
timeoutConf: (
|
||||||
|
<Tooltip id="tooltip-cascade">
|
||||||
|
Configure timeout for your remote GraphQL server. Defaults to 60
|
||||||
|
seconds.
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
const clientHeaderForward = (
|
const getTimeoutSection = () => {
|
||||||
<Tooltip id="tooltip-cascade">
|
const supportTimeoutConf =
|
||||||
Toggle forwarding headers sent by the client app in the request to your
|
globals.featuresCompatibility &&
|
||||||
remote GraphQL server
|
globals.featuresCompatibility[REMOTE_SCHEMA_TIMEOUT_CONF_SUPPORT];
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
const additionalHeaders = (
|
if (!supportTimeoutConf) {
|
||||||
<Tooltip id="tooltip-cascade">
|
return null;
|
||||||
Custom headers to be sent to the remote GraphQL server
|
}
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
|
|
||||||
const schema = (
|
return (
|
||||||
<Tooltip id="tooltip-cascade">
|
<React.Fragment>
|
||||||
Give this GraphQL schema a friendly name.
|
<div className={styles.subheading_text}>
|
||||||
</Tooltip>
|
GraphQL server timeout
|
||||||
);
|
<OverlayTrigger placement="right" overlay={tooltips.timeoutConf}>
|
||||||
|
<i className="fa fa-question-circle" aria-hidden="true" />
|
||||||
|
</OverlayTrigger>
|
||||||
|
</div>
|
||||||
|
<label
|
||||||
|
className={
|
||||||
|
styles.inputLabel + ' radio-inline ' + styles.padd_left_remove
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
className={'form-control'}
|
||||||
|
type="text"
|
||||||
|
placeholder="Timeout in seconds"
|
||||||
|
value={timeoutConf}
|
||||||
|
data-key="timeoutConf"
|
||||||
|
onChange={this.handleInputChange.bind(this)}
|
||||||
|
disabled={isDisabled}
|
||||||
|
data-test="remote-schema-timeout-conf"
|
||||||
|
pattern="^\d+$"
|
||||||
|
title="Only non negative integers are allowed"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.CommonWrapper}>
|
<div className={styles.CommonWrapper}>
|
||||||
<div className={styles.subheading_text + ' ' + styles.addPaddTop}>
|
<div className={styles.subheading_text + ' ' + styles.addPaddTop}>
|
||||||
Remote Schema name *
|
Remote Schema name *
|
||||||
<OverlayTrigger placement="right" overlay={schema}>
|
<OverlayTrigger placement="right" overlay={tooltips.schema}>
|
||||||
<i className="fa fa-question-circle" aria-hidden="true" />
|
<i className="fa fa-question-circle" aria-hidden="true" />
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</div>
|
</div>
|
||||||
@ -95,12 +147,12 @@ class Common extends React.Component {
|
|||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<hr />
|
<hr />
|
||||||
<h4 className={styles.subheading_text}>
|
<div className={styles.subheading_text}>
|
||||||
GraphQL server URL *
|
GraphQL server URL *
|
||||||
<OverlayTrigger placement="right" overlay={graphqlurl}>
|
<OverlayTrigger placement="right" overlay={tooltips.graphqlurl}>
|
||||||
<i className="fa fa-question-circle" aria-hidden="true" />
|
<i className="fa fa-question-circle" aria-hidden="true" />
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</h4>
|
</div>
|
||||||
<div className={styles.wd_300}>
|
<div className={styles.wd_300}>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
dropdownOptions={[
|
dropdownOptions={[
|
||||||
@ -152,13 +204,19 @@ class Common extends React.Component {
|
|||||||
/>
|
/>
|
||||||
<span>Forward all headers from client</span>
|
<span>Forward all headers from client</span>
|
||||||
</label>
|
</label>
|
||||||
<OverlayTrigger placement="right" overlay={clientHeaderForward}>
|
<OverlayTrigger
|
||||||
|
placement="right"
|
||||||
|
overlay={tooltips.clientHeaderForward}
|
||||||
|
>
|
||||||
<i className="fa fa-question-circle" aria-hidden="true" />
|
<i className="fa fa-question-circle" aria-hidden="true" />
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.subheading_text + ' ' + styles.font_normal}>
|
<div className={styles.subheading_text + ' ' + styles.font_normal}>
|
||||||
Additional headers:
|
Additional headers:
|
||||||
<OverlayTrigger placement="right" overlay={additionalHeaders}>
|
<OverlayTrigger
|
||||||
|
placement="right"
|
||||||
|
overlay={tooltips.additionalHeaders}
|
||||||
|
>
|
||||||
<i className="fa fa-question-circle" aria-hidden="true" />
|
<i className="fa fa-question-circle" aria-hidden="true" />
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
</div>
|
</div>
|
||||||
@ -174,6 +232,8 @@ class Common extends React.Component {
|
|||||||
placeHolderText={this.getPlaceHolderText.bind(this)}
|
placeHolderText={this.getPlaceHolderText.bind(this)}
|
||||||
keyInputPlaceholder="header name"
|
keyInputPlaceholder="header name"
|
||||||
/>
|
/>
|
||||||
|
<hr />
|
||||||
|
{getTimeoutSection()}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ const addState = {
|
|||||||
manualUrl: '',
|
manualUrl: '',
|
||||||
envName: null,
|
envName: null,
|
||||||
headers: [],
|
headers: [],
|
||||||
|
timeoutConf: '',
|
||||||
name: '',
|
name: '',
|
||||||
forwardClientHeaders: false,
|
forwardClientHeaders: false,
|
||||||
...asyncState,
|
...asyncState,
|
||||||
@ -27,6 +28,7 @@ const addState = {
|
|||||||
originalHeaders: [],
|
originalHeaders: [],
|
||||||
originalUrl: '',
|
originalUrl: '',
|
||||||
originalEnvUrl: '',
|
originalEnvUrl: '',
|
||||||
|
originalTimeoutConf: '',
|
||||||
originalForwardClientHeaders: false,
|
originalForwardClientHeaders: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -8,6 +8,7 @@ const envObj = `apiHost: '${process.env.API_HOST}',
|
|||||||
enableTelemetry: ${process.env.ENABLE_TELEMETRY},
|
enableTelemetry: ${process.env.ENABLE_TELEMETRY},
|
||||||
assetsPath: '${process.env.ASSETS_PATH}',
|
assetsPath: '${process.env.ASSETS_PATH}',
|
||||||
assetsVersion: '${process.env.ASSETS_VERSION}',
|
assetsVersion: '${process.env.ASSETS_VERSION}',
|
||||||
|
serverVersion: '${process.env.SERVER_VERSION}',
|
||||||
cdnAssets: ${process.env.CDN_ASSETS},
|
cdnAssets: ${process.env.CDN_ASSETS},
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -2,12 +2,15 @@ const semver = require('semver');
|
|||||||
|
|
||||||
export const FT_JWT_ANALYZER = 'JWTAnalyzer';
|
export const FT_JWT_ANALYZER = 'JWTAnalyzer';
|
||||||
export const RELOAD_METADATA_API_CHANGE = 'reloadMetaDataApiChange';
|
export const RELOAD_METADATA_API_CHANGE = 'reloadMetaDataApiChange';
|
||||||
|
export const REMOTE_SCHEMA_TIMEOUT_CONF_SUPPORT =
|
||||||
|
'remoteSchemaTimeoutConfSupport';
|
||||||
|
|
||||||
// list of feature launch versions
|
// list of feature launch versions
|
||||||
const featureLaunchVersions = {
|
const featureLaunchVersions = {
|
||||||
// feature: 'v1.0.0'
|
// feature: 'v1.0.0'
|
||||||
[RELOAD_METADATA_API_CHANGE]: 'v1.0.0-beta.3',
|
[RELOAD_METADATA_API_CHANGE]: 'v1.0.0-beta.3',
|
||||||
[FT_JWT_ANALYZER]: 'v1.0.0-beta.3',
|
[FT_JWT_ANALYZER]: 'v1.0.0-beta.3',
|
||||||
|
[REMOTE_SCHEMA_TIMEOUT_CONF_SUPPORT]: 'v1.0.0-beta.5',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFeaturesCompatibility = serverVersion => {
|
export const getFeaturesCompatibility = serverVersion => {
|
||||||
|
@ -30,7 +30,8 @@ An example request as follows:
|
|||||||
"definition": {
|
"definition": {
|
||||||
"url": "https://remote-server.com/graphql",
|
"url": "https://remote-server.com/graphql",
|
||||||
"headers": [{"name": "X-Server-Request-From", "value": "Hasura"}],
|
"headers": [{"name": "X-Server-Request-From", "value": "Hasura"}],
|
||||||
"forward_client_headers": false
|
"forward_client_headers": false,
|
||||||
|
"timeout_seconds": 60
|
||||||
},
|
},
|
||||||
"comment": "some optional comment"
|
"comment": "some optional comment"
|
||||||
}
|
}
|
||||||
|
@ -433,7 +433,8 @@ RemoteSchemaDef
|
|||||||
"value_from_env": env-var-string
|
"value_from_env": env-var-string
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"forward_client_headers": boolean
|
"forward_client_headers": boolean,
|
||||||
|
"timeout_seconds": integer
|
||||||
}
|
}
|
||||||
|
|
||||||
.. _CollectionName:
|
.. _CollectionName:
|
||||||
|
@ -20,10 +20,10 @@ import Data.Int (Int64)
|
|||||||
import Data.IORef (IORef, readIORef)
|
import Data.IORef (IORef, readIORef)
|
||||||
import Data.Time.Clock
|
import Data.Time.Clock
|
||||||
import Hasura.Events.HTTP
|
import Hasura.Events.HTTP
|
||||||
|
import Hasura.HTTP
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
import Hasura.RQL.DDL.Headers
|
import Hasura.RQL.DDL.Headers
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
import Hasura.Server.Version (currentVersion)
|
|
||||||
import Hasura.SQL.Types
|
import Hasura.SQL.Types
|
||||||
|
|
||||||
import qualified Control.Concurrent.STM.TQueue as TQ
|
import qualified Control.Concurrent.STM.TQueue as TQ
|
||||||
@ -344,12 +344,6 @@ decodeHeader logenv headerInfos (hdrName, hdrVal)
|
|||||||
where
|
where
|
||||||
decodeBS = TE.decodeUtf8With TE.lenientDecode
|
decodeBS = TE.decodeUtf8With TE.lenientDecode
|
||||||
|
|
||||||
addDefaultHeaders :: [HTTP.Header] -> [HTTP.Header]
|
|
||||||
addDefaultHeaders hdrs = hdrs ++
|
|
||||||
[ (CI.mk "Content-Type", "application/json")
|
|
||||||
, (CI.mk "User-Agent", "hasura-graphql-engine/" <> T.encodeUtf8 currentVersion)
|
|
||||||
]
|
|
||||||
|
|
||||||
mkInvo
|
mkInvo
|
||||||
:: EventPayload -> Int -> [HeaderConf] -> TBS.TByteString -> [HeaderConf]
|
:: EventPayload -> Int -> [HeaderConf] -> TBS.TByteString -> [HeaderConf]
|
||||||
-> Invocation
|
-> Invocation
|
||||||
|
@ -371,19 +371,26 @@ execRemoteGQ reqId userInfo reqHdrs q rsi opDef = do
|
|||||||
, Map.fromList userInfoToHdrs
|
, Map.fromList userInfoToHdrs
|
||||||
, Map.fromList clientHdrs
|
, Map.fromList clientHdrs
|
||||||
]
|
]
|
||||||
finalHdrs = foldr Map.union Map.empty hdrMaps
|
headers = Map.toList $ foldr Map.union Map.empty hdrMaps
|
||||||
options = wreqOptions manager (Map.toList finalHdrs)
|
finalHeaders = addDefaultHeaders headers
|
||||||
|
initReqE <- liftIO $ try $ HTTP.parseRequest (show url)
|
||||||
|
initReq <- either httpThrow pure initReqE
|
||||||
|
let req = initReq
|
||||||
|
{ HTTP.method = "POST"
|
||||||
|
, HTTP.requestHeaders = finalHeaders
|
||||||
|
, HTTP.requestBody = HTTP.RequestBodyLBS (J.encode q)
|
||||||
|
, HTTP.responseTimeout = HTTP.responseTimeoutMicro (timeout * 1000000)
|
||||||
|
}
|
||||||
|
|
||||||
-- log the graphql query
|
|
||||||
liftIO $ logGraphqlQuery logger $ QueryLog q Nothing reqId
|
liftIO $ logGraphqlQuery logger $ QueryLog q Nothing reqId
|
||||||
res <- liftIO $ try $ Wreq.postWith options (show url) (J.toJSON q)
|
res <- liftIO $ try $ HTTP.httpLbs req manager
|
||||||
resp <- either httpThrow return res
|
resp <- either httpThrow return res
|
||||||
let cookieHdrs = getCookieHdr (resp ^.. Wreq.responseHeader "Set-Cookie")
|
let cookieHdrs = getCookieHdr (resp ^.. Wreq.responseHeader "Set-Cookie")
|
||||||
respHdrs = Just $ mkRespHeaders cookieHdrs
|
respHdrs = Just $ mkRespHeaders cookieHdrs
|
||||||
return $ HttpResponse (encJFromLBS $ resp ^. Wreq.responseBody) respHdrs
|
return $ HttpResponse (encJFromLBS $ resp ^. Wreq.responseBody) respHdrs
|
||||||
|
|
||||||
where
|
where
|
||||||
RemoteSchemaInfo url hdrConf fwdClientHdrs = rsi
|
RemoteSchemaInfo url hdrConf fwdClientHdrs timeout = rsi
|
||||||
httpThrow :: (MonadError QErr m) => HTTP.HttpException -> m a
|
httpThrow :: (MonadError QErr m) => HTTP.HttpException -> m a
|
||||||
httpThrow = \case
|
httpThrow = \case
|
||||||
HTTP.HttpExceptionRequest _req content -> throw500 $ T.pack . show $ content
|
HTTP.HttpExceptionRequest _req content -> throw500 $ T.pack . show $ content
|
||||||
|
@ -5,6 +5,7 @@ import Control.Lens ((^.))
|
|||||||
import Data.Aeson ((.:), (.:?))
|
import Data.Aeson ((.:), (.:?))
|
||||||
import Data.FileEmbed (embedStringFile)
|
import Data.FileEmbed (embedStringFile)
|
||||||
import Data.Foldable (foldlM)
|
import Data.Foldable (foldlM)
|
||||||
|
import Hasura.HTTP
|
||||||
import Hasura.Prelude
|
import Hasura.Prelude
|
||||||
|
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
@ -19,7 +20,6 @@ import qualified Language.GraphQL.Draft.Syntax as G
|
|||||||
import qualified Network.HTTP.Client as HTTP
|
import qualified Network.HTTP.Client as HTTP
|
||||||
import qualified Network.Wreq as Wreq
|
import qualified Network.Wreq as Wreq
|
||||||
|
|
||||||
import Hasura.HTTP (wreqOptions)
|
|
||||||
import Hasura.RQL.DDL.Headers (getHeadersFromConf)
|
import Hasura.RQL.DDL.Headers (getHeadersFromConf)
|
||||||
import Hasura.RQL.Types
|
import Hasura.RQL.Types
|
||||||
import Hasura.Server.Utils (httpExceptToJSON)
|
import Hasura.Server.Utils (httpExceptToJSON)
|
||||||
@ -37,12 +37,21 @@ fetchRemoteSchema
|
|||||||
-> RemoteSchemaName
|
-> RemoteSchemaName
|
||||||
-> RemoteSchemaInfo
|
-> RemoteSchemaInfo
|
||||||
-> m GC.RemoteGCtx
|
-> m GC.RemoteGCtx
|
||||||
fetchRemoteSchema manager name def@(RemoteSchemaInfo url headerConf _) = do
|
fetchRemoteSchema manager name def@(RemoteSchemaInfo url headerConf _ timeout) = do
|
||||||
headers <- getHeadersFromConf headerConf
|
headers <- getHeadersFromConf headerConf
|
||||||
let hdrs = flip map headers $
|
let hdrs = flip map headers $
|
||||||
\(hn, hv) -> (CI.mk . T.encodeUtf8 $ hn, T.encodeUtf8 hv)
|
\(hn, hv) -> (CI.mk . T.encodeUtf8 $ hn, T.encodeUtf8 hv)
|
||||||
options = wreqOptions manager hdrs
|
hdrsWithDefaults = addDefaultHeaders hdrs
|
||||||
res <- liftIO $ try $ Wreq.postWith options (show url) introspectionQuery
|
|
||||||
|
initReqE <- liftIO $ try $ HTTP.parseRequest (show url)
|
||||||
|
initReq <- either throwHttpErr pure initReqE
|
||||||
|
let req = initReq
|
||||||
|
{ HTTP.method = "POST"
|
||||||
|
, HTTP.requestHeaders = hdrsWithDefaults
|
||||||
|
, HTTP.requestBody = HTTP.RequestBodyLBS introspectionQuery
|
||||||
|
, HTTP.responseTimeout = HTTP.responseTimeoutMicro (timeout * 1000000)
|
||||||
|
}
|
||||||
|
res <- liftIO $ try $ HTTP.httpLbs req manager
|
||||||
resp <- either throwHttpErr return res
|
resp <- either throwHttpErr return res
|
||||||
|
|
||||||
let respData = resp ^. Wreq.responseBody
|
let respData = resp ^. Wreq.responseBody
|
||||||
|
@ -2,6 +2,7 @@ module Hasura.HTTP
|
|||||||
( wreqOptions
|
( wreqOptions
|
||||||
, HttpException(..)
|
, HttpException(..)
|
||||||
, hdrsToText
|
, hdrsToText
|
||||||
|
, addDefaultHeaders
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Lens hiding ((.=))
|
import Control.Lens hiding ((.=))
|
||||||
@ -25,9 +26,21 @@ hdrsToText hdrs =
|
|||||||
wreqOptions :: HTTP.Manager -> [HTTP.Header] -> Wreq.Options
|
wreqOptions :: HTTP.Manager -> [HTTP.Header] -> Wreq.Options
|
||||||
wreqOptions manager hdrs =
|
wreqOptions manager hdrs =
|
||||||
Wreq.defaults
|
Wreq.defaults
|
||||||
& Wreq.headers .~ contentType : userAgent : hdrs
|
& Wreq.headers .~ addDefaultHeaders hdrs
|
||||||
& Wreq.checkResponse ?~ (\_ _ -> return ())
|
& Wreq.checkResponse ?~ (\_ _ -> return ())
|
||||||
& Wreq.manager .~ Right manager
|
& Wreq.manager .~ Right manager
|
||||||
|
|
||||||
|
-- Adds defaults headers overwriting any existing ones
|
||||||
|
addDefaultHeaders :: [HTTP.Header] -> [HTTP.Header]
|
||||||
|
addDefaultHeaders hdrs = defaultHeaders <> rmDefaultHeaders hdrs
|
||||||
|
where
|
||||||
|
rmDefaultHeaders = filter (not . isDefaultHeader)
|
||||||
|
|
||||||
|
isDefaultHeader :: HTTP.Header -> Bool
|
||||||
|
isDefaultHeader (hdrName, _) = hdrName `elem` (map fst defaultHeaders)
|
||||||
|
|
||||||
|
defaultHeaders :: [HTTP.Header]
|
||||||
|
defaultHeaders = [contentType, userAgent]
|
||||||
where
|
where
|
||||||
contentType = ("Content-Type", "application/json")
|
contentType = ("Content-Type", "application/json")
|
||||||
userAgent = ( "User-Agent"
|
userAgent = ( "User-Agent"
|
||||||
|
@ -30,6 +30,7 @@ data RemoteSchemaInfo
|
|||||||
{ rsUrl :: !N.URI
|
{ rsUrl :: !N.URI
|
||||||
, rsHeaders :: ![HeaderConf]
|
, rsHeaders :: ![HeaderConf]
|
||||||
, rsFwdClientHeaders :: !Bool
|
, rsFwdClientHeaders :: !Bool
|
||||||
|
, rsTimeoutSeconds :: !Int
|
||||||
} deriving (Show, Eq, Lift, Generic)
|
} deriving (Show, Eq, Lift, Generic)
|
||||||
|
|
||||||
instance Hashable RemoteSchemaInfo
|
instance Hashable RemoteSchemaInfo
|
||||||
@ -42,6 +43,7 @@ data RemoteSchemaDef
|
|||||||
, _rsdUrlFromEnv :: !(Maybe UrlFromEnv)
|
, _rsdUrlFromEnv :: !(Maybe UrlFromEnv)
|
||||||
, _rsdHeaders :: !(Maybe [HeaderConf])
|
, _rsdHeaders :: !(Maybe [HeaderConf])
|
||||||
, _rsdForwardClientHeaders :: !Bool
|
, _rsdForwardClientHeaders :: !Bool
|
||||||
|
, _rsdTimeoutSeconds :: !(Maybe Int)
|
||||||
} deriving (Show, Eq, Lift)
|
} deriving (Show, Eq, Lift)
|
||||||
|
|
||||||
$(J.deriveJSON (J.aesonDrop 4 J.snakeCase) ''RemoteSchemaDef)
|
$(J.deriveJSON (J.aesonDrop 4 J.snakeCase) ''RemoteSchemaDef)
|
||||||
@ -77,15 +79,18 @@ validateRemoteSchemaDef
|
|||||||
:: (MonadError QErr m, MonadIO m)
|
:: (MonadError QErr m, MonadIO m)
|
||||||
=> RemoteSchemaDef
|
=> RemoteSchemaDef
|
||||||
-> m RemoteSchemaInfo
|
-> m RemoteSchemaInfo
|
||||||
validateRemoteSchemaDef (RemoteSchemaDef mUrl mUrlEnv hdrC fwdHdrs) =
|
validateRemoteSchemaDef (RemoteSchemaDef mUrl mUrlEnv hdrC fwdHdrs mTimeout) =
|
||||||
case (mUrl, mUrlEnv) of
|
case (mUrl, mUrlEnv) of
|
||||||
(Just url, Nothing) ->
|
(Just url, Nothing) ->
|
||||||
return $ RemoteSchemaInfo url hdrs fwdHdrs
|
return $ RemoteSchemaInfo url hdrs fwdHdrs timeout
|
||||||
(Nothing, Just urlEnv) -> do
|
(Nothing, Just urlEnv) -> do
|
||||||
url <- getUrlFromEnv urlEnv
|
url <- getUrlFromEnv urlEnv
|
||||||
return $ RemoteSchemaInfo url hdrs fwdHdrs
|
return $ RemoteSchemaInfo url hdrs fwdHdrs timeout
|
||||||
(Nothing, Nothing) ->
|
(Nothing, Nothing) ->
|
||||||
throw400 InvalidParams "both `url` and `url_from_env` can't be empty"
|
throw400 InvalidParams "both `url` and `url_from_env` can't be empty"
|
||||||
(Just _, Just _) ->
|
(Just _, Just _) ->
|
||||||
throw400 InvalidParams "both `url` and `url_from_env` can't be present"
|
throw400 InvalidParams "both `url` and `url_from_env` can't be present"
|
||||||
where hdrs = fromMaybe [] hdrC
|
where
|
||||||
|
hdrs = fromMaybe [] hdrC
|
||||||
|
|
||||||
|
timeout = fromMaybe 60 mTimeout
|
||||||
|
@ -10,6 +10,8 @@ from webserver import RequestHandler, WebServer, MkHandlers, Response
|
|||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
def mkJSONResp(graphql_result):
|
def mkJSONResp(graphql_result):
|
||||||
return Response(HTTPStatus.OK, graphql_result.to_dict(),
|
return Response(HTTPStatus.OK, graphql_result.to_dict(),
|
||||||
{'Content-Type': 'application/json'})
|
{'Content-Type': 'application/json'})
|
||||||
@ -25,9 +27,15 @@ class HelloWorldHandler(RequestHandler):
|
|||||||
class Hello(graphene.ObjectType):
|
class Hello(graphene.ObjectType):
|
||||||
hello = graphene.String(arg=graphene.String(default_value="world"))
|
hello = graphene.String(arg=graphene.String(default_value="world"))
|
||||||
|
|
||||||
|
delayedHello = graphene.String(arg=graphene.String(default_value="world"))
|
||||||
|
|
||||||
def resolve_hello(self, info, arg):
|
def resolve_hello(self, info, arg):
|
||||||
return "Hello " + arg
|
return "Hello " + arg
|
||||||
|
|
||||||
|
def resolve_delayedHello(self, info, arg):
|
||||||
|
time.sleep(10)
|
||||||
|
return "Hello " + arg
|
||||||
|
|
||||||
hello_schema = graphene.Schema(query=Hello, subscription=Hello)
|
hello_schema = graphene.Schema(query=Hello, subscription=Hello)
|
||||||
|
|
||||||
class HelloGraphQL(RequestHandler):
|
class HelloGraphQL(RequestHandler):
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
description: Simple GraphQL query (takes 10s to respond)
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
response:
|
||||||
|
errors:
|
||||||
|
- message: ResponseTimeout
|
||||||
|
extensions: {'path': '$', 'code': 'unexpected'}
|
||||||
|
query:
|
||||||
|
query: |
|
||||||
|
query {
|
||||||
|
delayedHello(arg: "me")
|
||||||
|
}
|
@ -6,13 +6,14 @@ import yaml
|
|||||||
import json
|
import json
|
||||||
import queue
|
import queue
|
||||||
import requests
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from validate import check_query_f, check_query
|
from validate import check_query_f, check_query
|
||||||
|
|
||||||
|
|
||||||
def mk_add_remote_q(name, url, headers=None, client_hdrs=False):
|
def mk_add_remote_q(name, url, headers=None, client_hdrs=False, timeout=None):
|
||||||
return {
|
return {
|
||||||
"type": "add_remote_schema",
|
"type": "add_remote_schema",
|
||||||
"args": {
|
"args": {
|
||||||
@ -21,7 +22,8 @@ def mk_add_remote_q(name, url, headers=None, client_hdrs=False):
|
|||||||
"definition": {
|
"definition": {
|
||||||
"url": url,
|
"url": url,
|
||||||
"headers": headers,
|
"headers": headers,
|
||||||
"forward_client_headers": client_hdrs
|
"forward_client_headers": client_hdrs,
|
||||||
|
"timeout_seconds": timeout
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -434,6 +436,22 @@ class TestAddRemoteSchemaCompareRootQueryFields:
|
|||||||
compare_flds(fldH, fldR)
|
compare_flds(fldH, fldR)
|
||||||
assert has_fld[fldR['name']], 'Field ' + fldR['name'] + ' in the remote shema root query type not found in Hasura schema'
|
assert has_fld[fldR['name']], 'Field ' + fldR['name'] + ' in the remote shema root query type not found in Hasura schema'
|
||||||
|
|
||||||
|
class TestRemoteSchemaTimeout:
|
||||||
|
dir = 'queries/remote_schemas'
|
||||||
|
teardown = {"type": "clear_metadata", "args": {}}
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def transact(self, hge_ctx):
|
||||||
|
q = mk_add_remote_q('simple 1', 'http://localhost:5000/hello-graphql', timeout = 5)
|
||||||
|
st_code, resp = hge_ctx.v1q(q)
|
||||||
|
assert st_code == 200, resp
|
||||||
|
yield
|
||||||
|
hge_ctx.v1q(self.teardown)
|
||||||
|
|
||||||
|
def test_remote_query_timeout(self, hge_ctx):
|
||||||
|
check_query_f(hge_ctx, self.dir + '/basic_timeout_query.yaml')
|
||||||
|
# wait for graphql server to finish else teardown throws
|
||||||
|
time.sleep(6)
|
||||||
|
|
||||||
# def test_remote_query_variables(self, hge_ctx):
|
# def test_remote_query_variables(self, hge_ctx):
|
||||||
# pass
|
# pass
|
||||||
|
Loading…
Reference in New Issue
Block a user