requestPipeline.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.processGraphQLRequest = exports.APQ_CACHE_PREFIX = void 0;
  4. const utils_createhash_1 = require("@apollo/utils.createhash");
  5. const graphql_1 = require("graphql");
  6. const schemaInstrumentation_js_1 = require("./utils/schemaInstrumentation.js");
  7. const internalErrorClasses_js_1 = require("./internalErrorClasses.js");
  8. const errorNormalize_js_1 = require("./errorNormalize.js");
  9. const invokeHooks_js_1 = require("./utils/invokeHooks.js");
  10. const makeGatewayGraphQLRequestContext_js_1 = require("./utils/makeGatewayGraphQLRequestContext.js");
  11. const runHttpQuery_js_1 = require("./runHttpQuery.js");
  12. const isDefined_js_1 = require("./utils/isDefined.js");
  13. const incrementalDeliveryPolyfill_js_1 = require("./incrementalDeliveryPolyfill.js");
  14. const HeaderMap_js_1 = require("./utils/HeaderMap.js");
  15. exports.APQ_CACHE_PREFIX = 'apq:';
  16. function computeQueryHash(query) {
  17. return (0, utils_createhash_1.createHash)('sha256').update(query).digest('hex');
  18. }
  19. function isBadUserInputGraphQLError(error) {
  20. return (error.nodes?.length === 1 &&
  21. error.nodes[0].kind === graphql_1.Kind.VARIABLE_DEFINITION &&
  22. (error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" got invalid value `) ||
  23. error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" of required type `) ||
  24. error.message.startsWith(`Variable "$${error.nodes[0].variable.name.value}" of non-null type `)));
  25. }
  26. async function processGraphQLRequest(schemaDerivedData, server, internals, requestContext) {
  27. const requestListeners = (await Promise.all(internals.plugins.map((p) => p.requestDidStart?.(requestContext)))).filter(isDefined_js_1.isDefined);
  28. const request = requestContext.request;
  29. let { query, extensions } = request;
  30. let queryHash;
  31. requestContext.metrics.persistedQueryHit = false;
  32. requestContext.metrics.persistedQueryRegister = false;
  33. if (extensions?.persistedQuery) {
  34. if (!internals.persistedQueries) {
  35. return await sendErrorResponse([new internalErrorClasses_js_1.PersistedQueryNotSupportedError()]);
  36. }
  37. else if (extensions.persistedQuery.version !== 1) {
  38. return await sendErrorResponse([
  39. new graphql_1.GraphQLError('Unsupported persisted query version', {
  40. extensions: { http: (0, runHttpQuery_js_1.newHTTPGraphQLHead)(400) },
  41. }),
  42. ]);
  43. }
  44. queryHash = extensions.persistedQuery.sha256Hash;
  45. if (query === undefined) {
  46. query = await internals.persistedQueries.cache.get(queryHash);
  47. if (query) {
  48. requestContext.metrics.persistedQueryHit = true;
  49. }
  50. else {
  51. return await sendErrorResponse([new internalErrorClasses_js_1.PersistedQueryNotFoundError()]);
  52. }
  53. }
  54. else {
  55. const computedQueryHash = computeQueryHash(query);
  56. if (queryHash !== computedQueryHash) {
  57. return await sendErrorResponse([
  58. new graphql_1.GraphQLError('provided sha does not match query', {
  59. extensions: { http: (0, runHttpQuery_js_1.newHTTPGraphQLHead)(400) },
  60. }),
  61. ]);
  62. }
  63. requestContext.metrics.persistedQueryRegister = true;
  64. }
  65. }
  66. else if (query) {
  67. queryHash = computeQueryHash(query);
  68. }
  69. else {
  70. return await sendErrorResponse([
  71. new internalErrorClasses_js_1.BadRequestError('GraphQL operations must contain a non-empty `query` or a `persistedQuery` extension.'),
  72. ]);
  73. }
  74. requestContext.queryHash = queryHash;
  75. requestContext.source = query;
  76. await Promise.all(requestListeners.map((l) => l.didResolveSource?.(requestContext)));
  77. if (schemaDerivedData.documentStore) {
  78. try {
  79. requestContext.document = await schemaDerivedData.documentStore.get(schemaDerivedData.documentStoreKeyPrefix + queryHash);
  80. }
  81. catch (err) {
  82. server.logger.warn('An error occurred while attempting to read from the documentStore. ' +
  83. (0, errorNormalize_js_1.ensureError)(err).message);
  84. }
  85. }
  86. if (!requestContext.document) {
  87. const parsingDidEnd = await (0, invokeHooks_js_1.invokeDidStartHook)(requestListeners, async (l) => l.parsingDidStart?.(requestContext));
  88. try {
  89. requestContext.document = (0, graphql_1.parse)(query, internals.parseOptions);
  90. }
  91. catch (syntaxMaybeError) {
  92. const error = (0, errorNormalize_js_1.ensureError)(syntaxMaybeError);
  93. await parsingDidEnd(error);
  94. return await sendErrorResponse([
  95. new internalErrorClasses_js_1.SyntaxError((0, errorNormalize_js_1.ensureGraphQLError)(error)),
  96. ]);
  97. }
  98. await parsingDidEnd();
  99. if (internals.dangerouslyDisableValidation !== true) {
  100. const validationDidEnd = await (0, invokeHooks_js_1.invokeDidStartHook)(requestListeners, async (l) => l.validationDidStart?.(requestContext));
  101. const validationErrors = (0, graphql_1.validate)(schemaDerivedData.schema, requestContext.document, [...graphql_1.specifiedRules, ...internals.validationRules]);
  102. if (validationErrors.length === 0) {
  103. await validationDidEnd();
  104. }
  105. else {
  106. await validationDidEnd(validationErrors);
  107. return await sendErrorResponse(validationErrors.map((error) => new internalErrorClasses_js_1.ValidationError(error)));
  108. }
  109. }
  110. if (schemaDerivedData.documentStore) {
  111. Promise.resolve(schemaDerivedData.documentStore.set(schemaDerivedData.documentStoreKeyPrefix + queryHash, requestContext.document)).catch((err) => server.logger.warn('Could not store validated document. ' + err?.message || err));
  112. }
  113. }
  114. const operation = (0, graphql_1.getOperationAST)(requestContext.document, request.operationName);
  115. requestContext.operation = operation || undefined;
  116. requestContext.operationName = operation?.name?.value || null;
  117. if (request.http?.method === 'GET' &&
  118. operation?.operation &&
  119. operation.operation !== 'query') {
  120. return await sendErrorResponse([
  121. new internalErrorClasses_js_1.BadRequestError(`GET requests only support query operations, not ${operation.operation} operations`, {
  122. extensions: {
  123. http: { status: 405, headers: new HeaderMap_js_1.HeaderMap([['allow', 'POST']]) },
  124. },
  125. }),
  126. ]);
  127. }
  128. try {
  129. await Promise.all(requestListeners.map((l) => l.didResolveOperation?.(requestContext)));
  130. }
  131. catch (err) {
  132. return await sendErrorResponse([(0, errorNormalize_js_1.ensureGraphQLError)(err)]);
  133. }
  134. if (requestContext.metrics.persistedQueryRegister &&
  135. internals.persistedQueries) {
  136. const ttl = internals.persistedQueries?.ttl;
  137. Promise.resolve(internals.persistedQueries.cache.set(queryHash, query, ttl !== undefined
  138. ? { ttl: internals.persistedQueries?.ttl }
  139. : undefined)).catch(server.logger.warn);
  140. }
  141. const responseFromPlugin = await (0, invokeHooks_js_1.invokeHooksUntilDefinedAndNonNull)(requestListeners, async (l) => await l.responseForOperation?.(requestContext));
  142. if (responseFromPlugin !== null) {
  143. requestContext.response.body = responseFromPlugin.body;
  144. (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, responseFromPlugin.http);
  145. }
  146. else {
  147. const executionListeners = (await Promise.all(requestListeners.map((l) => l.executionDidStart?.(requestContext)))).filter(isDefined_js_1.isDefined);
  148. executionListeners.reverse();
  149. if (executionListeners.some((l) => l.willResolveField)) {
  150. const invokeWillResolveField = (...args) => (0, invokeHooks_js_1.invokeSyncDidStartHook)(executionListeners, (l) => l.willResolveField?.(...args));
  151. Object.defineProperty(requestContext.contextValue, schemaInstrumentation_js_1.symbolExecutionDispatcherWillResolveField, { value: invokeWillResolveField });
  152. if (internals.fieldResolver) {
  153. Object.defineProperty(requestContext.contextValue, schemaInstrumentation_js_1.symbolUserFieldResolver, {
  154. value: internals.fieldResolver,
  155. });
  156. }
  157. (0, schemaInstrumentation_js_1.enablePluginsForSchemaResolvers)(schemaDerivedData.schema);
  158. }
  159. try {
  160. const fullResult = await execute(requestContext);
  161. const result = 'singleResult' in fullResult
  162. ? fullResult.singleResult
  163. : fullResult.initialResult;
  164. if (!requestContext.operation) {
  165. if (!result.errors?.length) {
  166. throw new Error('Unexpected error: Apollo Server did not resolve an operation but execute did not return errors');
  167. }
  168. throw new internalErrorClasses_js_1.OperationResolutionError(result.errors[0]);
  169. }
  170. const resultErrors = result.errors?.map((e) => {
  171. if (isBadUserInputGraphQLError(e) && e.extensions?.code == null) {
  172. return new internalErrorClasses_js_1.UserInputError(e);
  173. }
  174. return e;
  175. });
  176. if (resultErrors) {
  177. await didEncounterErrors(resultErrors);
  178. }
  179. const { formattedErrors, httpFromErrors } = resultErrors
  180. ? formatErrors(resultErrors)
  181. : { formattedErrors: undefined, httpFromErrors: (0, runHttpQuery_js_1.newHTTPGraphQLHead)() };
  182. if (internals.status400ForVariableCoercionErrors &&
  183. resultErrors?.length &&
  184. result.data === undefined &&
  185. !httpFromErrors.status) {
  186. httpFromErrors.status = 400;
  187. }
  188. (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, httpFromErrors);
  189. if ('singleResult' in fullResult) {
  190. requestContext.response.body = {
  191. kind: 'single',
  192. singleResult: {
  193. ...result,
  194. errors: formattedErrors,
  195. },
  196. };
  197. }
  198. else {
  199. requestContext.response.body = {
  200. kind: 'incremental',
  201. initialResult: {
  202. ...fullResult.initialResult,
  203. errors: formattedErrors,
  204. },
  205. subsequentResults: fullResult.subsequentResults,
  206. };
  207. }
  208. }
  209. catch (executionMaybeError) {
  210. const executionError = (0, errorNormalize_js_1.ensureError)(executionMaybeError);
  211. await Promise.all(executionListeners.map((l) => l.executionDidEnd?.(executionError)));
  212. return await sendErrorResponse([(0, errorNormalize_js_1.ensureGraphQLError)(executionError)]);
  213. }
  214. await Promise.all(executionListeners.map((l) => l.executionDidEnd?.()));
  215. }
  216. await invokeWillSendResponse();
  217. if (!requestContext.response.body) {
  218. throw Error('got to end of processGraphQLRequest without setting body?');
  219. }
  220. return requestContext.response;
  221. async function execute(requestContext) {
  222. const { request, document } = requestContext;
  223. if (internals.__testing_incrementalExecutionResults) {
  224. return internals.__testing_incrementalExecutionResults;
  225. }
  226. else if (internals.gatewayExecutor) {
  227. const result = await internals.gatewayExecutor((0, makeGatewayGraphQLRequestContext_js_1.makeGatewayGraphQLRequestContext)(requestContext, server, internals));
  228. return { singleResult: result };
  229. }
  230. else {
  231. const resultOrResults = await (0, incrementalDeliveryPolyfill_js_1.executeIncrementally)({
  232. schema: schemaDerivedData.schema,
  233. document,
  234. rootValue: typeof internals.rootValue === 'function'
  235. ? internals.rootValue(document)
  236. : internals.rootValue,
  237. contextValue: requestContext.contextValue,
  238. variableValues: request.variables,
  239. operationName: request.operationName,
  240. fieldResolver: internals.fieldResolver,
  241. });
  242. if ('initialResult' in resultOrResults) {
  243. return {
  244. initialResult: resultOrResults.initialResult,
  245. subsequentResults: formatErrorsInSubsequentResults(resultOrResults.subsequentResults),
  246. };
  247. }
  248. else {
  249. return { singleResult: resultOrResults };
  250. }
  251. }
  252. }
  253. async function* formatErrorsInSubsequentResults(results) {
  254. for await (const result of results) {
  255. const payload = result.incremental
  256. ? {
  257. ...result,
  258. incremental: await seriesAsyncMap(result.incremental, async (incrementalResult) => {
  259. const { errors } = incrementalResult;
  260. if (errors) {
  261. await Promise.all(requestListeners.map((l) => l.didEncounterSubsequentErrors?.(requestContext, errors)));
  262. return {
  263. ...incrementalResult,
  264. errors: formatErrors(errors).formattedErrors,
  265. };
  266. }
  267. return incrementalResult;
  268. }),
  269. }
  270. : result;
  271. await Promise.all(requestListeners.map((l) => l.willSendSubsequentPayload?.(requestContext, payload)));
  272. yield payload;
  273. }
  274. }
  275. async function invokeWillSendResponse() {
  276. await Promise.all(requestListeners.map((l) => l.willSendResponse?.(requestContext)));
  277. }
  278. async function didEncounterErrors(errors) {
  279. requestContext.errors = errors;
  280. return await Promise.all(requestListeners.map((l) => l.didEncounterErrors?.(requestContext)));
  281. }
  282. async function sendErrorResponse(errors) {
  283. await didEncounterErrors(errors);
  284. const { formattedErrors, httpFromErrors } = formatErrors(errors);
  285. requestContext.response.body = {
  286. kind: 'single',
  287. singleResult: {
  288. errors: formattedErrors,
  289. },
  290. };
  291. (0, runHttpQuery_js_1.mergeHTTPGraphQLHead)(requestContext.response.http, httpFromErrors);
  292. if (!requestContext.response.http.status) {
  293. requestContext.response.http.status = 500;
  294. }
  295. await invokeWillSendResponse();
  296. return requestContext.response;
  297. }
  298. function formatErrors(errors) {
  299. return (0, errorNormalize_js_1.normalizeAndFormatErrors)(errors, {
  300. formatError: internals.formatError,
  301. includeStacktraceInErrorResponses: internals.includeStacktraceInErrorResponses,
  302. });
  303. }
  304. }
  305. exports.processGraphQLRequest = processGraphQLRequest;
  306. async function seriesAsyncMap(ts, fn) {
  307. const us = [];
  308. for (const t of ts) {
  309. const u = await fn(t);
  310. us.push(u);
  311. }
  312. return us;
  313. }
  314. //# sourceMappingURL=requestPipeline.js.map