-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: support for GET method for existing servers #180
Merged
Merged
Changes from 9 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
a2d7c85
chore(get-support): support for GET method for express and connect
DxCx 7abcee8
doc(get-support): update README.md for GET method examples
DxCx aa251f0
chore(get-support): support for GET method for hapi
DxCx d7b724f
chore(get-support): support for GET method for koa
DxCx 977f6df
chore(get-support): Update README.md
DxCx 93d7966
chore(get-support): rename buffer to requestPayload
DxCx 5b46db4
fix(get-support): fixed returned status for get without query
DxCx 32140cb
chore(get-support): refactor to share http logic in core module
DxCx aa3a96a
chore(get-support): blocked GET for queries only
DxCx 0ad6d42
chore(package): Update CHANGELOG.md
DxCx 11bd610
fix(get-support): unnamed operation was not treated as query
DxCx be573a6
chore(get-support): cleanup
DxCx 01e3010
fix(get-support): preparse query for GET requests to handle query pro…
DxCx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export { runQuery, LogFunction, LogMessage, LogStep, LogAction } from './runQuery' | ||
export { default as GraphQLOptions} from './graphqlOptions' | ||
export { runHttpQuery, HttpQueryRequest, HttpQueryError } from './runHttpQuery'; | ||
export { default as GraphQLOptions } from './graphqlOptions' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { formatError, ExecutionResult } from 'graphql'; | ||
import { runQuery } from './runQuery'; | ||
import { default as GraphQLOptions, isOptionsFunction } from './graphqlOptions'; | ||
|
||
export interface HttpQueryRequest { | ||
method: string; | ||
query: string; | ||
options: GraphQLOptions | Function; | ||
} | ||
|
||
export class HttpQueryError extends Error { | ||
public statusCode: number; | ||
public isGraphQLError: boolean; | ||
public headers: { [key: string]: string }; | ||
|
||
constructor (statusCode: number, message: string, isGraphQLError: boolean = false, headers?: { [key: string]: string }) { | ||
super(message); | ||
this.statusCode = statusCode; | ||
this.isGraphQLError = isGraphQLError; | ||
this.headers = headers; | ||
} | ||
} | ||
|
||
export async function runHttpQuery(handlerArguments: Array<any>, request: HttpQueryRequest): Promise<string> { | ||
let isGetRequest: boolean = false; | ||
let optionsObject: GraphQLOptions; | ||
if (isOptionsFunction(request.options)) { | ||
try { | ||
optionsObject = await request.options(...handlerArguments); | ||
} catch (e) { | ||
throw new HttpQueryError(500, `Invalid options provided to ApolloServer: ${e.message}`); | ||
} | ||
} else { | ||
optionsObject = request.options; | ||
} | ||
|
||
const formatErrorFn = optionsObject.formatError || formatError; | ||
let requestPayload; | ||
|
||
switch ( request.method ) { | ||
case 'POST': | ||
if ( !request.query ) { | ||
throw new HttpQueryError(500, 'POST body missing. Did you forget use body-parser middleware?'); | ||
} | ||
|
||
requestPayload = request.query; | ||
break; | ||
case 'GET': | ||
if ( !request.query || (Object.keys(request.query).length === 0) ) { | ||
throw new HttpQueryError(400, 'GET query missing.'); | ||
} | ||
|
||
isGetRequest = true; | ||
requestPayload = request.query; | ||
break; | ||
|
||
default: | ||
throw new HttpQueryError(405, 'Apollo Server supports only GET/POST requests.', false, { | ||
'Allow': 'GET, POST', | ||
}); | ||
} | ||
|
||
let isBatch = true; | ||
// TODO: do something different here if the body is an array. | ||
// Throw an error if body isn't either array or object. | ||
if (!Array.isArray(requestPayload)) { | ||
isBatch = false; | ||
requestPayload = [requestPayload]; | ||
} | ||
|
||
let responses: Array<ExecutionResult> = []; | ||
for (let requestParams of requestPayload) { | ||
if ( isGetRequest && !requestParams.query.trim().startsWith('query')) { | ||
const errorMsg = `GET supports only query operation`; | ||
throw new HttpQueryError(405, errorMsg, false, { | ||
'Allow': 'POST', | ||
}); | ||
} | ||
|
||
try { | ||
const query = requestParams.query; | ||
const operationName = requestParams.operationName; | ||
let variables = requestParams.variables; | ||
|
||
if (typeof variables === 'string') { | ||
try { | ||
variables = JSON.parse(variables); | ||
} catch (error) { | ||
throw new HttpQueryError(400, 'Variables are invalid JSON.'); | ||
} | ||
} | ||
|
||
// Shallow clone context for queries in batches. This allows | ||
// users to distinguish multiple queries in the batch and to | ||
// modify the context object without interfering with each other. | ||
let context = optionsObject.context; | ||
if (isBatch) { | ||
context = Object.assign({}, context || {}); | ||
} | ||
|
||
let params = { | ||
schema: optionsObject.schema, | ||
query: query, | ||
variables: variables, | ||
context: context, | ||
rootValue: optionsObject.rootValue, | ||
operationName: operationName, | ||
logFunction: optionsObject.logFunction, | ||
validationRules: optionsObject.validationRules, | ||
formatError: formatErrorFn, | ||
formatResponse: optionsObject.formatResponse, | ||
debug: optionsObject.debug, | ||
}; | ||
|
||
if (optionsObject.formatParams) { | ||
params = optionsObject.formatParams(params); | ||
} | ||
|
||
responses.push(await runQuery(params)); | ||
} catch (e) { | ||
responses.push({ errors: [formatErrorFn(e)] }); | ||
} | ||
} | ||
|
||
if (!isBatch) { | ||
const gqlResponse = responses[0]; | ||
if (gqlResponse.errors && typeof gqlResponse.data === 'undefined') { | ||
throw new HttpQueryError(400, JSON.stringify(gqlResponse), true, { | ||
'Content-Type': 'application/json', | ||
}); | ||
} | ||
return JSON.stringify(gqlResponse); | ||
} | ||
|
||
return JSON.stringify(responses); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the spec the query keyword is optional. I know it's annoying, but that's how it is. So we'll have to check here whether it's an anonymous query that starts with
{
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the query isn't necessarily the first thing in the document right? For example:
Is also a valid query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would check for "document doesn't contain the keyword
mutation
outside of all brackets"There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@helfer => i was already figured it out, and actually, you were reviewing outdated chunk, which this was already fixed.
@stubailo => about the fragment, this is really usecase i didn't take into consideration,
i'll try to use GraphQL Parser instead to get the exact operation GraphQL will try to execute.