runHttpQuery.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. "use strict";
  2. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  3. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  4. return new (P || (P = Promise))(function (resolve, reject) {
  5. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  6. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  7. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  8. step((generator = generator.apply(thisArg, _arguments || [])).next());
  9. });
  10. };
  11. Object.defineProperty(exports, "__esModule", { value: true });
  12. exports.cloneObject = exports.processHTTPRequest = exports.runHttpQuery = exports.throwHttpGraphQLError = exports.HttpQueryError = void 0;
  13. const apollo_server_env_1 = require("apollo-server-env");
  14. const graphqlOptions_1 = require("./graphqlOptions");
  15. const apollo_server_errors_1 = require("apollo-server-errors");
  16. const requestPipeline_1 = require("./requestPipeline");
  17. class HttpQueryError extends Error {
  18. constructor(statusCode, message, isGraphQLError = false, headers) {
  19. super(message);
  20. this.name = 'HttpQueryError';
  21. this.statusCode = statusCode;
  22. this.isGraphQLError = isGraphQLError;
  23. this.headers = headers;
  24. }
  25. }
  26. exports.HttpQueryError = HttpQueryError;
  27. function throwHttpGraphQLError(statusCode, errors, options, extensions) {
  28. const defaultHeaders = { 'Content-Type': 'application/json' };
  29. const headers = apollo_server_errors_1.hasPersistedQueryError(errors)
  30. ? Object.assign(Object.assign({}, defaultHeaders), { 'Cache-Control': 'private, no-cache, must-revalidate' }) : defaultHeaders;
  31. const result = {
  32. errors: options
  33. ? apollo_server_errors_1.formatApolloErrors(errors, {
  34. debug: options.debug,
  35. formatter: options.formatError,
  36. })
  37. : errors,
  38. };
  39. if (extensions) {
  40. result.extensions = extensions;
  41. }
  42. throw new HttpQueryError(statusCode, prettyJSONStringify(result), true, headers);
  43. }
  44. exports.throwHttpGraphQLError = throwHttpGraphQLError;
  45. function runHttpQuery(handlerArguments, request) {
  46. return __awaiter(this, void 0, void 0, function* () {
  47. let options;
  48. const debugDefault = process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test';
  49. try {
  50. options = yield graphqlOptions_1.resolveGraphqlOptions(request.options, ...handlerArguments);
  51. }
  52. catch (e) {
  53. return throwHttpGraphQLError(500, [e], { debug: debugDefault });
  54. }
  55. if (options.debug === undefined) {
  56. options.debug = debugDefault;
  57. }
  58. if (typeof options.context === 'function') {
  59. try {
  60. options.context();
  61. }
  62. catch (e) {
  63. e.message = `Context creation failed: ${e.message}`;
  64. if (e.extensions &&
  65. e.extensions.code &&
  66. e.extensions.code !== 'INTERNAL_SERVER_ERROR') {
  67. return throwHttpGraphQLError(400, [e], options);
  68. }
  69. else {
  70. return throwHttpGraphQLError(500, [e], options);
  71. }
  72. }
  73. }
  74. const config = {
  75. schema: options.schema,
  76. schemaHash: options.schemaHash,
  77. logger: options.logger,
  78. rootValue: options.rootValue,
  79. context: options.context || {},
  80. validationRules: options.validationRules,
  81. executor: options.executor,
  82. fieldResolver: options.fieldResolver,
  83. cache: options.cache,
  84. dataSources: options.dataSources,
  85. documentStore: options.documentStore,
  86. extensions: options.extensions,
  87. persistedQueries: options.persistedQueries,
  88. tracing: options.tracing,
  89. formatError: options.formatError,
  90. formatResponse: options.formatResponse,
  91. debug: options.debug,
  92. plugins: options.plugins || [],
  93. };
  94. return processHTTPRequest(config, request);
  95. });
  96. }
  97. exports.runHttpQuery = runHttpQuery;
  98. function processHTTPRequest(options, httpRequest) {
  99. return __awaiter(this, void 0, void 0, function* () {
  100. let requestPayload;
  101. switch (httpRequest.method) {
  102. case 'POST':
  103. if (!httpRequest.query || Object.keys(httpRequest.query).length === 0) {
  104. throw new HttpQueryError(500, 'POST body missing. Did you forget use body-parser middleware?');
  105. }
  106. requestPayload = httpRequest.query;
  107. break;
  108. case 'GET':
  109. if (!httpRequest.query || Object.keys(httpRequest.query).length === 0) {
  110. throw new HttpQueryError(400, 'GET query missing.');
  111. }
  112. requestPayload = httpRequest.query;
  113. break;
  114. default:
  115. throw new HttpQueryError(405, 'Apollo Server supports only GET/POST requests.', false, {
  116. Allow: 'GET, POST',
  117. });
  118. }
  119. options = Object.assign(Object.assign({}, options), { plugins: [checkOperationPlugin, ...options.plugins] });
  120. function buildRequestContext(request) {
  121. const context = cloneObject(options.context);
  122. return {
  123. logger: options.logger || console,
  124. schema: options.schema,
  125. schemaHash: options.schemaHash,
  126. request,
  127. response: {
  128. http: {
  129. headers: new apollo_server_env_1.Headers(),
  130. },
  131. },
  132. context,
  133. cache: options.cache,
  134. debug: options.debug,
  135. metrics: {},
  136. };
  137. }
  138. const responseInit = {
  139. headers: {
  140. 'Content-Type': 'application/json',
  141. },
  142. };
  143. let body;
  144. try {
  145. if (Array.isArray(requestPayload)) {
  146. const requests = requestPayload.map(requestParams => parseGraphQLRequest(httpRequest.request, requestParams));
  147. const responses = yield Promise.all(requests.map((request) => __awaiter(this, void 0, void 0, function* () {
  148. try {
  149. const requestContext = buildRequestContext(request);
  150. return yield requestPipeline_1.processGraphQLRequest(options, requestContext);
  151. }
  152. catch (error) {
  153. return {
  154. errors: apollo_server_errors_1.formatApolloErrors([error], options),
  155. };
  156. }
  157. })));
  158. body = prettyJSONStringify(responses.map(serializeGraphQLResponse));
  159. }
  160. else {
  161. const request = parseGraphQLRequest(httpRequest.request, requestPayload);
  162. try {
  163. const requestContext = buildRequestContext(request);
  164. const response = yield requestPipeline_1.processGraphQLRequest(options, requestContext);
  165. if (response.errors && typeof response.data === 'undefined') {
  166. return throwHttpGraphQLError((response.http && response.http.status) || 400, response.errors, undefined, response.extensions);
  167. }
  168. if (response.http) {
  169. for (const [name, value] of response.http.headers) {
  170. responseInit.headers[name] = value;
  171. }
  172. }
  173. body = prettyJSONStringify(serializeGraphQLResponse(response));
  174. }
  175. catch (error) {
  176. if (error instanceof requestPipeline_1.InvalidGraphQLRequestError) {
  177. throw new HttpQueryError(400, error.message);
  178. }
  179. else if (error instanceof apollo_server_errors_1.PersistedQueryNotSupportedError ||
  180. error instanceof apollo_server_errors_1.PersistedQueryNotFoundError) {
  181. return throwHttpGraphQLError(200, [error], options);
  182. }
  183. else {
  184. throw error;
  185. }
  186. }
  187. }
  188. }
  189. catch (error) {
  190. if (error instanceof HttpQueryError) {
  191. throw error;
  192. }
  193. return throwHttpGraphQLError(500, [error], options);
  194. }
  195. responseInit.headers['Content-Length'] = Buffer.byteLength(body, 'utf8').toString();
  196. return {
  197. graphqlResponse: body,
  198. responseInit,
  199. };
  200. });
  201. }
  202. exports.processHTTPRequest = processHTTPRequest;
  203. function parseGraphQLRequest(httpRequest, requestParams) {
  204. let queryString = requestParams.query;
  205. let extensions = requestParams.extensions;
  206. if (typeof extensions === 'string' && extensions !== '') {
  207. try {
  208. extensions = JSON.parse(extensions);
  209. }
  210. catch (error) {
  211. throw new HttpQueryError(400, 'Extensions are invalid JSON.');
  212. }
  213. }
  214. if (queryString && typeof queryString !== 'string') {
  215. if (queryString.kind === 'Document') {
  216. throw new HttpQueryError(400, "GraphQL queries must be strings. It looks like you're sending the " +
  217. 'internal graphql-js representation of a parsed query in your ' +
  218. 'request instead of a request in the GraphQL query language. You ' +
  219. 'can convert an AST to a string using the `print` function from ' +
  220. '`graphql`, or use a client like `apollo-client` which converts ' +
  221. 'the internal representation to a string for you.');
  222. }
  223. else {
  224. throw new HttpQueryError(400, 'GraphQL queries must be strings.');
  225. }
  226. }
  227. const operationName = requestParams.operationName;
  228. let variables = requestParams.variables;
  229. if (typeof variables === 'string' && variables !== '') {
  230. try {
  231. variables = JSON.parse(variables);
  232. }
  233. catch (error) {
  234. throw new HttpQueryError(400, 'Variables are invalid JSON.');
  235. }
  236. }
  237. return {
  238. query: queryString,
  239. operationName,
  240. variables,
  241. extensions,
  242. http: httpRequest,
  243. };
  244. }
  245. const checkOperationPlugin = {
  246. requestDidStart() {
  247. return {
  248. didResolveOperation({ request, operation }) {
  249. if (!request.http)
  250. return;
  251. if (request.http.method === 'GET' && operation.operation !== 'query') {
  252. throw new HttpQueryError(405, `GET supports only query operation`, false, {
  253. Allow: 'POST',
  254. });
  255. }
  256. },
  257. };
  258. },
  259. };
  260. function serializeGraphQLResponse(response) {
  261. return {
  262. errors: response.errors,
  263. data: response.data,
  264. extensions: response.extensions,
  265. };
  266. }
  267. function prettyJSONStringify(value) {
  268. return JSON.stringify(value) + '\n';
  269. }
  270. function cloneObject(object) {
  271. return Object.assign(Object.create(Object.getPrototypeOf(object)), object);
  272. }
  273. exports.cloneObject = cloneObject;
  274. //# sourceMappingURL=runHttpQuery.js.map