runHttpQuery.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. "use strict";
  2. var __importDefault = (this && this.__importDefault) || function (mod) {
  3. return (mod && mod.__esModule) ? mod : { "default": mod };
  4. };
  5. Object.defineProperty(exports, "__esModule", { value: true });
  6. exports.mergeHTTPGraphQLHead = exports.newHTTPGraphQLHead = exports.prettyJSONStringify = exports.runHttpQuery = void 0;
  7. const ApolloServer_js_1 = require("./ApolloServer.js");
  8. const graphql_1 = require("graphql");
  9. const internalErrorClasses_js_1 = require("./internalErrorClasses.js");
  10. const negotiator_1 = __importDefault(require("negotiator"));
  11. const HeaderMap_js_1 = require("./utils/HeaderMap.js");
  12. function fieldIfString(o, fieldName) {
  13. const value = o[fieldName];
  14. if (typeof value === 'string') {
  15. return value;
  16. }
  17. return undefined;
  18. }
  19. function searchParamIfSpecifiedOnce(searchParams, paramName) {
  20. const values = searchParams.getAll(paramName);
  21. switch (values.length) {
  22. case 0:
  23. return undefined;
  24. case 1:
  25. return values[0];
  26. default:
  27. throw new internalErrorClasses_js_1.BadRequestError(`The '${paramName}' search parameter may only be specified once.`);
  28. }
  29. }
  30. function jsonParsedSearchParamIfSpecifiedOnce(searchParams, fieldName) {
  31. const value = searchParamIfSpecifiedOnce(searchParams, fieldName);
  32. if (value === undefined) {
  33. return undefined;
  34. }
  35. let hopefullyRecord;
  36. try {
  37. hopefullyRecord = JSON.parse(value);
  38. }
  39. catch {
  40. throw new internalErrorClasses_js_1.BadRequestError(`The ${fieldName} search parameter contains invalid JSON.`);
  41. }
  42. if (!isStringRecord(hopefullyRecord)) {
  43. throw new internalErrorClasses_js_1.BadRequestError(`The ${fieldName} search parameter should contain a JSON-encoded object.`);
  44. }
  45. return hopefullyRecord;
  46. }
  47. function fieldIfRecord(o, fieldName) {
  48. const value = o[fieldName];
  49. if (isStringRecord(value)) {
  50. return value;
  51. }
  52. return undefined;
  53. }
  54. function isStringRecord(o) {
  55. return (!!o && typeof o === 'object' && !Buffer.isBuffer(o) && !Array.isArray(o));
  56. }
  57. function isNonEmptyStringRecord(o) {
  58. return isStringRecord(o) && Object.keys(o).length > 0;
  59. }
  60. function ensureQueryIsStringOrMissing(query) {
  61. if (!query || typeof query === 'string') {
  62. return;
  63. }
  64. if (query.kind === graphql_1.Kind.DOCUMENT) {
  65. throw new internalErrorClasses_js_1.BadRequestError("GraphQL queries must be strings. It looks like you're sending the " +
  66. 'internal graphql-js representation of a parsed query in your ' +
  67. 'request instead of a request in the GraphQL query language. You ' +
  68. 'can convert an AST to a string using the `print` function from ' +
  69. '`graphql`, or use a client like `apollo-client` which converts ' +
  70. 'the internal representation to a string for you.');
  71. }
  72. else {
  73. throw new internalErrorClasses_js_1.BadRequestError('GraphQL queries must be strings.');
  74. }
  75. }
  76. async function runHttpQuery({ server, httpRequest, contextValue, schemaDerivedData, internals, sharedResponseHTTPGraphQLHead, }) {
  77. let graphQLRequest;
  78. switch (httpRequest.method) {
  79. case 'POST': {
  80. if (!isNonEmptyStringRecord(httpRequest.body)) {
  81. throw new internalErrorClasses_js_1.BadRequestError('POST body missing, invalid Content-Type, or JSON object has no keys.');
  82. }
  83. ensureQueryIsStringOrMissing(httpRequest.body.query);
  84. if (typeof httpRequest.body.variables === 'string') {
  85. throw new internalErrorClasses_js_1.BadRequestError('`variables` in a POST body should be provided as an object, not a recursively JSON-encoded string.');
  86. }
  87. if (typeof httpRequest.body.extensions === 'string') {
  88. throw new internalErrorClasses_js_1.BadRequestError('`extensions` in a POST body should be provided as an object, not a recursively JSON-encoded string.');
  89. }
  90. if ('extensions' in httpRequest.body &&
  91. httpRequest.body.extensions !== null &&
  92. !isStringRecord(httpRequest.body.extensions)) {
  93. throw new internalErrorClasses_js_1.BadRequestError('`extensions` in a POST body must be an object if provided.');
  94. }
  95. if ('variables' in httpRequest.body &&
  96. httpRequest.body.variables !== null &&
  97. !isStringRecord(httpRequest.body.variables)) {
  98. throw new internalErrorClasses_js_1.BadRequestError('`variables` in a POST body must be an object if provided.');
  99. }
  100. if ('operationName' in httpRequest.body &&
  101. httpRequest.body.operationName !== null &&
  102. typeof httpRequest.body.operationName !== 'string') {
  103. throw new internalErrorClasses_js_1.BadRequestError('`operationName` in a POST body must be a string if provided.');
  104. }
  105. graphQLRequest = {
  106. query: fieldIfString(httpRequest.body, 'query'),
  107. operationName: fieldIfString(httpRequest.body, 'operationName'),
  108. variables: fieldIfRecord(httpRequest.body, 'variables'),
  109. extensions: fieldIfRecord(httpRequest.body, 'extensions'),
  110. http: httpRequest,
  111. };
  112. break;
  113. }
  114. case 'GET': {
  115. const searchParams = new URLSearchParams(httpRequest.search);
  116. graphQLRequest = {
  117. query: searchParamIfSpecifiedOnce(searchParams, 'query'),
  118. operationName: searchParamIfSpecifiedOnce(searchParams, 'operationName'),
  119. variables: jsonParsedSearchParamIfSpecifiedOnce(searchParams, 'variables'),
  120. extensions: jsonParsedSearchParamIfSpecifiedOnce(searchParams, 'extensions'),
  121. http: httpRequest,
  122. };
  123. break;
  124. }
  125. default:
  126. throw new internalErrorClasses_js_1.BadRequestError('Apollo Server supports only GET/POST requests.', {
  127. extensions: {
  128. http: {
  129. status: 405,
  130. headers: new HeaderMap_js_1.HeaderMap([['allow', 'GET, POST']]),
  131. },
  132. },
  133. });
  134. }
  135. const graphQLResponse = await (0, ApolloServer_js_1.internalExecuteOperation)({
  136. server,
  137. graphQLRequest,
  138. internals,
  139. schemaDerivedData,
  140. sharedResponseHTTPGraphQLHead,
  141. }, { contextValue });
  142. if (graphQLResponse.body.kind === 'single') {
  143. if (!graphQLResponse.http.headers.get('content-type')) {
  144. const contentType = (0, ApolloServer_js_1.chooseContentTypeForSingleResultResponse)(httpRequest);
  145. if (contentType === null) {
  146. throw new internalErrorClasses_js_1.BadRequestError(`An 'accept' header was provided for this request which does not accept ` +
  147. `${ApolloServer_js_1.MEDIA_TYPES.APPLICATION_JSON} or ${ApolloServer_js_1.MEDIA_TYPES.APPLICATION_GRAPHQL_RESPONSE_JSON}`, { extensions: { http: { status: 406 } } });
  148. }
  149. graphQLResponse.http.headers.set('content-type', contentType);
  150. }
  151. return {
  152. ...graphQLResponse.http,
  153. body: {
  154. kind: 'complete',
  155. string: await internals.stringifyResult(orderExecutionResultFields(graphQLResponse.body.singleResult)),
  156. },
  157. };
  158. }
  159. const acceptHeader = httpRequest.headers.get('accept');
  160. if (!(acceptHeader &&
  161. new negotiator_1.default({
  162. headers: { accept: httpRequest.headers.get('accept') },
  163. }).mediaType([
  164. ApolloServer_js_1.MEDIA_TYPES.MULTIPART_MIXED_NO_DEFER_SPEC,
  165. ApolloServer_js_1.MEDIA_TYPES.MULTIPART_MIXED_EXPERIMENTAL,
  166. ]) === ApolloServer_js_1.MEDIA_TYPES.MULTIPART_MIXED_EXPERIMENTAL)) {
  167. throw new internalErrorClasses_js_1.BadRequestError('Apollo server received an operation that uses incremental delivery ' +
  168. '(@defer or @stream), but the client does not accept multipart/mixed ' +
  169. 'HTTP responses. To enable incremental delivery support, add the HTTP ' +
  170. "header 'Accept: multipart/mixed; deferSpec=20220824'.", { extensions: { http: { status: 406 } } });
  171. }
  172. graphQLResponse.http.headers.set('content-type', 'multipart/mixed; boundary="-"; deferSpec=20220824');
  173. return {
  174. ...graphQLResponse.http,
  175. body: {
  176. kind: 'chunked',
  177. asyncIterator: writeMultipartBody(graphQLResponse.body.initialResult, graphQLResponse.body.subsequentResults),
  178. },
  179. };
  180. }
  181. exports.runHttpQuery = runHttpQuery;
  182. async function* writeMultipartBody(initialResult, subsequentResults) {
  183. yield `\r\n---\r\ncontent-type: application/json; charset=utf-8\r\n\r\n${JSON.stringify(orderInitialIncrementalExecutionResultFields(initialResult))}\r\n---${initialResult.hasNext ? '' : '--'}\r\n`;
  184. for await (const result of subsequentResults) {
  185. yield `content-type: application/json; charset=utf-8\r\n\r\n${JSON.stringify(orderSubsequentIncrementalExecutionResultFields(result))}\r\n---${result.hasNext ? '' : '--'}\r\n`;
  186. }
  187. }
  188. function orderExecutionResultFields(result) {
  189. return {
  190. errors: result.errors,
  191. data: result.data,
  192. extensions: result.extensions,
  193. };
  194. }
  195. function orderInitialIncrementalExecutionResultFields(result) {
  196. return {
  197. hasNext: result.hasNext,
  198. errors: result.errors,
  199. data: result.data,
  200. incremental: orderIncrementalResultFields(result.incremental),
  201. extensions: result.extensions,
  202. };
  203. }
  204. function orderSubsequentIncrementalExecutionResultFields(result) {
  205. return {
  206. hasNext: result.hasNext,
  207. incremental: orderIncrementalResultFields(result.incremental),
  208. extensions: result.extensions,
  209. };
  210. }
  211. function orderIncrementalResultFields(incremental) {
  212. return incremental?.map((i) => ({
  213. hasNext: i.hasNext,
  214. errors: i.errors,
  215. path: i.path,
  216. label: i.label,
  217. data: i.data,
  218. items: i.items,
  219. extensions: i.extensions,
  220. }));
  221. }
  222. function prettyJSONStringify(value) {
  223. return JSON.stringify(value) + '\n';
  224. }
  225. exports.prettyJSONStringify = prettyJSONStringify;
  226. function newHTTPGraphQLHead(status) {
  227. return {
  228. status,
  229. headers: new HeaderMap_js_1.HeaderMap(),
  230. };
  231. }
  232. exports.newHTTPGraphQLHead = newHTTPGraphQLHead;
  233. function mergeHTTPGraphQLHead(target, source) {
  234. if (source.status) {
  235. target.status = source.status;
  236. }
  237. if (source.headers) {
  238. for (const [name, value] of source.headers) {
  239. target.headers.set(name, value);
  240. }
  241. }
  242. }
  243. exports.mergeHTTPGraphQLHead = mergeHTTPGraphQLHead;
  244. //# sourceMappingURL=runHttpQuery.js.map