ApolloServer.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. import { isNodeLike } from '@apollo/utils.isnodelike';
  2. import { InMemoryLRUCache, PrefixingKeyValueCache, } from '@apollo/utils.keyvaluecache';
  3. import { makeExecutableSchema } from '@graphql-tools/schema';
  4. import resolvable from './utils/resolvable.js';
  5. import { GraphQLError, assertValidSchema, print, printSchema, } from 'graphql';
  6. import loglevel from 'loglevel';
  7. import Negotiator from 'negotiator';
  8. import { newCachePolicy } from './cachePolicy.js';
  9. import { determineApolloConfig } from './determineApolloConfig.js';
  10. import { ensureError, ensureGraphQLError, normalizeAndFormatErrors, } from './errorNormalize.js';
  11. import { ApolloServerErrorCode, ApolloServerValidationErrorCode, } from './errors/index.js';
  12. import { runPotentiallyBatchedHttpQuery } from './httpBatching.js';
  13. import { pluginIsInternal } from './internalPlugin.js';
  14. import { preventCsrf, recommendedCsrfPreventionRequestHeaders, } from './preventCsrf.js';
  15. import { APQ_CACHE_PREFIX, processGraphQLRequest } from './requestPipeline.js';
  16. import { newHTTPGraphQLHead, prettyJSONStringify } from './runHttpQuery.js';
  17. import { HeaderMap } from './utils/HeaderMap.js';
  18. import { UnreachableCaseError } from './utils/UnreachableCaseError.js';
  19. import { computeCoreSchemaHash } from './utils/computeCoreSchemaHash.js';
  20. import { isDefined } from './utils/isDefined.js';
  21. import { SchemaManager } from './utils/schemaManager.js';
  22. const NoIntrospection = (context) => ({
  23. Field(node) {
  24. if (node.name.value === '__schema' || node.name.value === '__type') {
  25. context.reportError(new GraphQLError('GraphQL introspection is not allowed by Apollo Server, but the query contained __schema or __type. To enable introspection, pass introspection: true to ApolloServer in production', {
  26. nodes: [node],
  27. extensions: {
  28. validationErrorCode: ApolloServerValidationErrorCode.INTROSPECTION_DISABLED,
  29. },
  30. }));
  31. }
  32. },
  33. });
  34. function defaultLogger() {
  35. const loglevelLogger = loglevel.getLogger('apollo-server');
  36. loglevelLogger.setLevel(loglevel.levels.INFO);
  37. return loglevelLogger;
  38. }
  39. export class ApolloServer {
  40. constructor(config) {
  41. const nodeEnv = config.nodeEnv ?? process.env.NODE_ENV ?? '';
  42. this.logger = config.logger ?? defaultLogger();
  43. const apolloConfig = determineApolloConfig(config.apollo, this.logger);
  44. const isDev = nodeEnv !== 'production';
  45. if (config.cache &&
  46. config.cache !== 'bounded' &&
  47. PrefixingKeyValueCache.prefixesAreUnnecessaryForIsolation(config.cache)) {
  48. throw new Error('You cannot pass a cache returned from ' +
  49. '`PrefixingKeyValueCache.cacheDangerouslyDoesNotNeedPrefixesForIsolation`' +
  50. 'to `new ApolloServer({ cache })`, because Apollo Server may use it for ' +
  51. 'multiple features whose cache keys must be distinct from each other.');
  52. }
  53. const state = config.gateway
  54. ?
  55. {
  56. phase: 'initialized',
  57. schemaManager: new SchemaManager({
  58. gateway: config.gateway,
  59. apolloConfig,
  60. schemaDerivedDataProvider: (schema) => ApolloServer.generateSchemaDerivedData(schema, config.documentStore),
  61. logger: this.logger,
  62. }),
  63. }
  64. :
  65. {
  66. phase: 'initialized',
  67. schemaManager: new SchemaManager({
  68. apiSchema: ApolloServer.constructSchema(config),
  69. schemaDerivedDataProvider: (schema) => ApolloServer.generateSchemaDerivedData(schema, config.documentStore),
  70. logger: this.logger,
  71. }),
  72. };
  73. const introspectionEnabled = config.introspection ?? isDev;
  74. this.cache =
  75. config.cache === undefined || config.cache === 'bounded'
  76. ? new InMemoryLRUCache()
  77. : config.cache;
  78. this.internals = {
  79. formatError: config.formatError,
  80. rootValue: config.rootValue,
  81. validationRules: [
  82. ...(config.validationRules ?? []),
  83. ...(introspectionEnabled ? [] : [NoIntrospection]),
  84. ],
  85. dangerouslyDisableValidation: config.dangerouslyDisableValidation ?? false,
  86. fieldResolver: config.fieldResolver,
  87. includeStacktraceInErrorResponses: config.includeStacktraceInErrorResponses ??
  88. (nodeEnv !== 'production' && nodeEnv !== 'test'),
  89. persistedQueries: config.persistedQueries === false
  90. ? undefined
  91. : {
  92. ...config.persistedQueries,
  93. cache: new PrefixingKeyValueCache(config.persistedQueries?.cache ?? this.cache, APQ_CACHE_PREFIX),
  94. },
  95. nodeEnv,
  96. allowBatchedHttpRequests: config.allowBatchedHttpRequests ?? false,
  97. apolloConfig,
  98. plugins: config.plugins ?? [],
  99. parseOptions: config.parseOptions ?? {},
  100. state,
  101. stopOnTerminationSignals: config.stopOnTerminationSignals,
  102. gatewayExecutor: null,
  103. csrfPreventionRequestHeaders: config.csrfPrevention === true || config.csrfPrevention === undefined
  104. ? recommendedCsrfPreventionRequestHeaders
  105. : config.csrfPrevention === false
  106. ? null
  107. : (config.csrfPrevention.requestHeaders ??
  108. recommendedCsrfPreventionRequestHeaders),
  109. status400ForVariableCoercionErrors: config.status400ForVariableCoercionErrors ?? false,
  110. __testing_incrementalExecutionResults: config.__testing_incrementalExecutionResults,
  111. stringifyResult: config.stringifyResult ?? prettyJSONStringify,
  112. };
  113. }
  114. async start() {
  115. return await this._start(false);
  116. }
  117. startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests() {
  118. this._start(true).catch((e) => this.logStartupError(e));
  119. }
  120. async _start(startedInBackground) {
  121. if (this.internals.state.phase !== 'initialized') {
  122. throw new Error(`You should only call 'start()' or ` +
  123. `'startInBackgroundHandlingStartupErrorsByLoggingAndFailingAllRequests()' ` +
  124. `once on your ApolloServer.`);
  125. }
  126. const schemaManager = this.internals.state.schemaManager;
  127. const barrier = resolvable();
  128. this.internals.state = {
  129. phase: 'starting',
  130. barrier,
  131. schemaManager,
  132. startedInBackground,
  133. };
  134. try {
  135. await this.addDefaultPlugins();
  136. const toDispose = [];
  137. const executor = await schemaManager.start();
  138. if (executor) {
  139. this.internals.gatewayExecutor = executor;
  140. }
  141. toDispose.push(async () => {
  142. await schemaManager.stop();
  143. });
  144. const schemaDerivedData = schemaManager.getSchemaDerivedData();
  145. const service = {
  146. logger: this.logger,
  147. cache: this.cache,
  148. schema: schemaDerivedData.schema,
  149. apollo: this.internals.apolloConfig,
  150. startedInBackground,
  151. };
  152. const taggedServerListeners = (await Promise.all(this.internals.plugins.map(async (plugin) => ({
  153. serverListener: plugin.serverWillStart && (await plugin.serverWillStart(service)),
  154. installedImplicitly: isImplicitlyInstallablePlugin(plugin) &&
  155. plugin.__internal_installed_implicitly__,
  156. })))).filter((maybeTaggedServerListener) => typeof maybeTaggedServerListener.serverListener === 'object');
  157. taggedServerListeners.forEach(({ serverListener: { schemaDidLoadOrUpdate } }) => {
  158. if (schemaDidLoadOrUpdate) {
  159. schemaManager.onSchemaLoadOrUpdate(schemaDidLoadOrUpdate);
  160. }
  161. });
  162. const serverWillStops = taggedServerListeners
  163. .map((l) => l.serverListener.serverWillStop)
  164. .filter(isDefined);
  165. if (serverWillStops.length) {
  166. toDispose.push(async () => {
  167. await Promise.all(serverWillStops.map((serverWillStop) => serverWillStop()));
  168. });
  169. }
  170. const drainServerCallbacks = taggedServerListeners
  171. .map((l) => l.serverListener.drainServer)
  172. .filter(isDefined);
  173. const drainServers = drainServerCallbacks.length
  174. ? async () => {
  175. await Promise.all(drainServerCallbacks.map((drainServer) => drainServer()));
  176. }
  177. : null;
  178. let taggedServerListenersWithRenderLandingPage = taggedServerListeners.filter((l) => l.serverListener.renderLandingPage);
  179. if (taggedServerListenersWithRenderLandingPage.length > 1) {
  180. taggedServerListenersWithRenderLandingPage =
  181. taggedServerListenersWithRenderLandingPage.filter((l) => !l.installedImplicitly);
  182. }
  183. let landingPage = null;
  184. if (taggedServerListenersWithRenderLandingPage.length > 1) {
  185. throw Error('Only one plugin can implement renderLandingPage.');
  186. }
  187. else if (taggedServerListenersWithRenderLandingPage.length) {
  188. landingPage =
  189. await taggedServerListenersWithRenderLandingPage[0].serverListener
  190. .renderLandingPage();
  191. }
  192. const toDisposeLast = this.maybeRegisterTerminationSignalHandlers(['SIGINT', 'SIGTERM'], startedInBackground);
  193. this.internals.state = {
  194. phase: 'started',
  195. schemaManager,
  196. drainServers,
  197. landingPage,
  198. toDispose,
  199. toDisposeLast,
  200. };
  201. }
  202. catch (maybeError) {
  203. const error = ensureError(maybeError);
  204. try {
  205. await Promise.all(this.internals.plugins.map(async (plugin) => plugin.startupDidFail?.({ error })));
  206. }
  207. catch (pluginError) {
  208. this.logger.error(`startupDidFail hook threw: ${pluginError}`);
  209. }
  210. this.internals.state = {
  211. phase: 'failed to start',
  212. error,
  213. };
  214. throw error;
  215. }
  216. finally {
  217. barrier.resolve();
  218. }
  219. }
  220. maybeRegisterTerminationSignalHandlers(signals, startedInBackground) {
  221. const toDisposeLast = [];
  222. if (this.internals.stopOnTerminationSignals === false ||
  223. (this.internals.stopOnTerminationSignals === undefined &&
  224. !(isNodeLike &&
  225. this.internals.nodeEnv !== 'test' &&
  226. !startedInBackground))) {
  227. return toDisposeLast;
  228. }
  229. let receivedSignal = false;
  230. const signalHandler = async (signal) => {
  231. if (receivedSignal) {
  232. return;
  233. }
  234. receivedSignal = true;
  235. try {
  236. await this.stop();
  237. }
  238. catch (e) {
  239. this.logger.error(`stop() threw during ${signal} shutdown`);
  240. this.logger.error(e);
  241. process.exit(1);
  242. }
  243. process.kill(process.pid, signal);
  244. };
  245. signals.forEach((signal) => {
  246. process.on(signal, signalHandler);
  247. toDisposeLast.push(async () => {
  248. process.removeListener(signal, signalHandler);
  249. });
  250. });
  251. return toDisposeLast;
  252. }
  253. async _ensureStarted() {
  254. while (true) {
  255. switch (this.internals.state.phase) {
  256. case 'initialized':
  257. throw new Error('You need to call `server.start()` before using your Apollo Server.');
  258. case 'starting':
  259. await this.internals.state.barrier;
  260. break;
  261. case 'failed to start':
  262. this.logStartupError(this.internals.state.error);
  263. throw new Error('This data graph is missing a valid configuration. More details may be available in the server logs.');
  264. case 'started':
  265. case 'draining':
  266. return this.internals.state;
  267. case 'stopping':
  268. case 'stopped':
  269. this.logger.warn('A GraphQL operation was received during server shutdown. The ' +
  270. 'operation will fail. Consider draining the HTTP server on shutdown; ' +
  271. 'see https://go.apollo.dev/s/drain for details.');
  272. throw new Error(`Cannot execute GraphQL operations ${this.internals.state.phase === 'stopping'
  273. ? 'while the server is stopping'
  274. : 'after the server has stopped'}.'`);
  275. default:
  276. throw new UnreachableCaseError(this.internals.state);
  277. }
  278. }
  279. }
  280. assertStarted(expressionForError) {
  281. if (this.internals.state.phase !== 'started' &&
  282. this.internals.state.phase !== 'draining' &&
  283. !(this.internals.state.phase === 'starting' &&
  284. this.internals.state.startedInBackground)) {
  285. throw new Error('You must `await server.start()` before calling `' +
  286. expressionForError +
  287. '`');
  288. }
  289. }
  290. logStartupError(err) {
  291. this.logger.error('An error occurred during Apollo Server startup. All GraphQL requests ' +
  292. 'will now fail. The startup error was: ' +
  293. (err?.message || err));
  294. }
  295. static constructSchema(config) {
  296. if (config.schema) {
  297. return config.schema;
  298. }
  299. const { typeDefs, resolvers } = config;
  300. const augmentedTypeDefs = Array.isArray(typeDefs) ? typeDefs : [typeDefs];
  301. return makeExecutableSchema({
  302. typeDefs: augmentedTypeDefs,
  303. resolvers,
  304. });
  305. }
  306. static generateSchemaDerivedData(schema, providedDocumentStore) {
  307. assertValidSchema(schema);
  308. return {
  309. schema,
  310. documentStore: providedDocumentStore === undefined
  311. ? new InMemoryLRUCache()
  312. : providedDocumentStore,
  313. documentStoreKeyPrefix: providedDocumentStore
  314. ? `${computeCoreSchemaHash(printSchema(schema))}:`
  315. : '',
  316. };
  317. }
  318. async stop() {
  319. switch (this.internals.state.phase) {
  320. case 'initialized':
  321. case 'starting':
  322. case 'failed to start':
  323. throw Error('apolloServer.stop() should only be called after `await apolloServer.start()` has succeeded');
  324. case 'stopped':
  325. if (this.internals.state.stopError) {
  326. throw this.internals.state.stopError;
  327. }
  328. return;
  329. case 'stopping':
  330. case 'draining': {
  331. await this.internals.state.barrier;
  332. const state = this.internals.state;
  333. if (state.phase !== 'stopped') {
  334. throw Error(`Surprising post-stopping state ${state.phase}`);
  335. }
  336. if (state.stopError) {
  337. throw state.stopError;
  338. }
  339. return;
  340. }
  341. case 'started':
  342. break;
  343. default:
  344. throw new UnreachableCaseError(this.internals.state);
  345. }
  346. const barrier = resolvable();
  347. const { schemaManager, drainServers, landingPage, toDispose, toDisposeLast, } = this.internals.state;
  348. this.internals.state = {
  349. phase: 'draining',
  350. barrier,
  351. schemaManager,
  352. landingPage,
  353. };
  354. try {
  355. await drainServers?.();
  356. this.internals.state = { phase: 'stopping', barrier };
  357. await Promise.all([...toDispose].map((dispose) => dispose()));
  358. await Promise.all([...toDisposeLast].map((dispose) => dispose()));
  359. }
  360. catch (stopError) {
  361. this.internals.state = {
  362. phase: 'stopped',
  363. stopError: stopError,
  364. };
  365. barrier.resolve();
  366. throw stopError;
  367. }
  368. this.internals.state = { phase: 'stopped', stopError: null };
  369. }
  370. async addDefaultPlugins() {
  371. const { plugins, apolloConfig, nodeEnv } = this.internals;
  372. const isDev = nodeEnv !== 'production';
  373. const alreadyHavePluginWithInternalId = (id) => plugins.some((p) => pluginIsInternal(p) && p.__internal_plugin_id__ === id);
  374. const pluginsByInternalID = new Map();
  375. for (const p of plugins) {
  376. if (pluginIsInternal(p)) {
  377. const id = p.__internal_plugin_id__;
  378. if (!pluginsByInternalID.has(id)) {
  379. pluginsByInternalID.set(id, {
  380. sawDisabled: false,
  381. sawNonDisabled: false,
  382. });
  383. }
  384. const seen = pluginsByInternalID.get(id);
  385. if (p.__is_disabled_plugin__) {
  386. seen.sawDisabled = true;
  387. }
  388. else {
  389. seen.sawNonDisabled = true;
  390. }
  391. if (seen.sawDisabled && seen.sawNonDisabled) {
  392. throw new Error(`You have tried to install both ApolloServerPlugin${id} and ` +
  393. `ApolloServerPlugin${id}Disabled in your server. Please choose ` +
  394. `whether or not you want to disable the feature and install the ` +
  395. `appropriate plugin for your use case.`);
  396. }
  397. }
  398. }
  399. {
  400. if (!alreadyHavePluginWithInternalId('CacheControl')) {
  401. const { ApolloServerPluginCacheControl } = await import('./plugin/cacheControl/index.js');
  402. plugins.push(ApolloServerPluginCacheControl());
  403. }
  404. }
  405. {
  406. const alreadyHavePlugin = alreadyHavePluginWithInternalId('UsageReporting');
  407. if (!alreadyHavePlugin && apolloConfig.key) {
  408. if (apolloConfig.graphRef) {
  409. const { ApolloServerPluginUsageReporting } = await import('./plugin/usageReporting/index.js');
  410. plugins.unshift(ApolloServerPluginUsageReporting({
  411. __onlyIfSchemaIsNotSubgraph: true,
  412. }));
  413. }
  414. else {
  415. this.logger.warn('You have specified an Apollo key but have not specified a graph ref; usage ' +
  416. 'reporting is disabled. To enable usage reporting, set the `APOLLO_GRAPH_REF` ' +
  417. 'environment variable to `your-graph-id@your-graph-variant`. To disable this ' +
  418. 'warning, install `ApolloServerPluginUsageReportingDisabled`.');
  419. }
  420. }
  421. }
  422. {
  423. const alreadyHavePlugin = alreadyHavePluginWithInternalId('SchemaReporting');
  424. const enabledViaEnvVar = process.env.APOLLO_SCHEMA_REPORTING === 'true';
  425. if (!alreadyHavePlugin && enabledViaEnvVar) {
  426. if (apolloConfig.key) {
  427. const { ApolloServerPluginSchemaReporting } = await import('./plugin/schemaReporting/index.js');
  428. plugins.push(ApolloServerPluginSchemaReporting());
  429. }
  430. else {
  431. throw new Error("You've enabled schema reporting by setting the APOLLO_SCHEMA_REPORTING " +
  432. 'environment variable to true, but you also need to provide your ' +
  433. 'Apollo API key, via the APOLLO_KEY environment ' +
  434. 'variable or via `new ApolloServer({apollo: {key})');
  435. }
  436. }
  437. }
  438. {
  439. const alreadyHavePlugin = alreadyHavePluginWithInternalId('InlineTrace');
  440. if (!alreadyHavePlugin) {
  441. const { ApolloServerPluginInlineTrace } = await import('./plugin/inlineTrace/index.js');
  442. plugins.push(ApolloServerPluginInlineTrace({ __onlyIfSchemaIsSubgraph: true }));
  443. }
  444. }
  445. const alreadyHavePlugin = alreadyHavePluginWithInternalId('LandingPageDisabled');
  446. if (!alreadyHavePlugin) {
  447. const { ApolloServerPluginLandingPageLocalDefault, ApolloServerPluginLandingPageProductionDefault, } = await import('./plugin/landingPage/default/index.js');
  448. const plugin = isDev
  449. ? ApolloServerPluginLandingPageLocalDefault()
  450. : ApolloServerPluginLandingPageProductionDefault();
  451. if (!isImplicitlyInstallablePlugin(plugin)) {
  452. throw Error('default landing page plugin should be implicitly installable?');
  453. }
  454. plugin.__internal_installed_implicitly__ = true;
  455. plugins.push(plugin);
  456. }
  457. }
  458. addPlugin(plugin) {
  459. if (this.internals.state.phase !== 'initialized') {
  460. throw new Error("Can't add plugins after the server has started");
  461. }
  462. this.internals.plugins.push(plugin);
  463. }
  464. async executeHTTPGraphQLRequest({ httpGraphQLRequest, context, }) {
  465. try {
  466. let runningServerState;
  467. try {
  468. runningServerState = await this._ensureStarted();
  469. }
  470. catch (error) {
  471. return await this.errorResponse(error, httpGraphQLRequest);
  472. }
  473. if (runningServerState.landingPage &&
  474. this.prefersHTML(httpGraphQLRequest)) {
  475. let renderedHtml;
  476. if (typeof runningServerState.landingPage.html === 'string') {
  477. renderedHtml = runningServerState.landingPage.html;
  478. }
  479. else {
  480. try {
  481. renderedHtml = await runningServerState.landingPage.html();
  482. }
  483. catch (maybeError) {
  484. const error = ensureError(maybeError);
  485. this.logger.error(`Landing page \`html\` function threw: ${error}`);
  486. return await this.errorResponse(error, httpGraphQLRequest);
  487. }
  488. }
  489. return {
  490. headers: new HeaderMap([['content-type', 'text/html']]),
  491. body: {
  492. kind: 'complete',
  493. string: renderedHtml,
  494. },
  495. };
  496. }
  497. if (this.internals.csrfPreventionRequestHeaders) {
  498. preventCsrf(httpGraphQLRequest.headers, this.internals.csrfPreventionRequestHeaders);
  499. }
  500. let contextValue;
  501. try {
  502. contextValue = await context();
  503. }
  504. catch (maybeError) {
  505. const error = ensureError(maybeError);
  506. try {
  507. await Promise.all(this.internals.plugins.map(async (plugin) => plugin.contextCreationDidFail?.({
  508. error,
  509. })));
  510. }
  511. catch (pluginError) {
  512. this.logger.error(`contextCreationDidFail hook threw: ${pluginError}`);
  513. }
  514. return await this.errorResponse(ensureGraphQLError(error, 'Context creation failed: '), httpGraphQLRequest);
  515. }
  516. return await runPotentiallyBatchedHttpQuery(this, httpGraphQLRequest, contextValue, runningServerState.schemaManager.getSchemaDerivedData(), this.internals);
  517. }
  518. catch (maybeError_) {
  519. const maybeError = maybeError_;
  520. if (maybeError instanceof GraphQLError &&
  521. maybeError.extensions.code === ApolloServerErrorCode.BAD_REQUEST) {
  522. try {
  523. await Promise.all(this.internals.plugins.map(async (plugin) => plugin.invalidRequestWasReceived?.({ error: maybeError })));
  524. }
  525. catch (pluginError) {
  526. this.logger.error(`invalidRequestWasReceived hook threw: ${pluginError}`);
  527. }
  528. }
  529. return await this.errorResponse(maybeError, httpGraphQLRequest);
  530. }
  531. }
  532. async errorResponse(error, requestHead) {
  533. const { formattedErrors, httpFromErrors } = normalizeAndFormatErrors([error], {
  534. includeStacktraceInErrorResponses: this.internals.includeStacktraceInErrorResponses,
  535. formatError: this.internals.formatError,
  536. });
  537. return {
  538. status: httpFromErrors.status ?? 500,
  539. headers: new HeaderMap([
  540. ...httpFromErrors.headers,
  541. [
  542. 'content-type',
  543. chooseContentTypeForSingleResultResponse(requestHead) ??
  544. MEDIA_TYPES.APPLICATION_JSON,
  545. ],
  546. ]),
  547. body: {
  548. kind: 'complete',
  549. string: await this.internals.stringifyResult({
  550. errors: formattedErrors,
  551. }),
  552. },
  553. };
  554. }
  555. prefersHTML(request) {
  556. const acceptHeader = request.headers.get('accept');
  557. return (request.method === 'GET' &&
  558. !!acceptHeader &&
  559. new Negotiator({
  560. headers: { accept: acceptHeader },
  561. }).mediaType([
  562. MEDIA_TYPES.APPLICATION_JSON,
  563. MEDIA_TYPES.APPLICATION_GRAPHQL_RESPONSE_JSON,
  564. MEDIA_TYPES.MULTIPART_MIXED_EXPERIMENTAL,
  565. MEDIA_TYPES.MULTIPART_MIXED_NO_DEFER_SPEC,
  566. MEDIA_TYPES.TEXT_HTML,
  567. ]) === MEDIA_TYPES.TEXT_HTML);
  568. }
  569. async executeOperation(request, options = {}) {
  570. if (this.internals.state.phase === 'initialized') {
  571. await this.start();
  572. }
  573. const schemaDerivedData = (await this._ensureStarted()).schemaManager.getSchemaDerivedData();
  574. const graphQLRequest = {
  575. ...request,
  576. query: request.query && typeof request.query !== 'string'
  577. ? print(request.query)
  578. : request.query,
  579. };
  580. const response = await internalExecuteOperation({
  581. server: this,
  582. graphQLRequest,
  583. internals: this.internals,
  584. schemaDerivedData,
  585. sharedResponseHTTPGraphQLHead: null,
  586. }, options);
  587. return response;
  588. }
  589. }
  590. export async function internalExecuteOperation({ server, graphQLRequest, internals, schemaDerivedData, sharedResponseHTTPGraphQLHead, }, options) {
  591. const requestContext = {
  592. logger: server.logger,
  593. cache: server.cache,
  594. schema: schemaDerivedData.schema,
  595. request: graphQLRequest,
  596. response: {
  597. http: sharedResponseHTTPGraphQLHead ?? newHTTPGraphQLHead(),
  598. },
  599. contextValue: cloneObject(options?.contextValue ?? {}),
  600. metrics: {},
  601. overallCachePolicy: newCachePolicy(),
  602. requestIsBatched: sharedResponseHTTPGraphQLHead !== null,
  603. };
  604. try {
  605. return await processGraphQLRequest(schemaDerivedData, server, internals, requestContext);
  606. }
  607. catch (maybeError) {
  608. const error = ensureError(maybeError);
  609. await Promise.all(internals.plugins.map(async (plugin) => plugin.unexpectedErrorProcessingRequest?.({
  610. requestContext,
  611. error,
  612. })));
  613. server.logger.error(`Unexpected error processing request: ${error}`);
  614. throw new Error('Internal server error');
  615. }
  616. }
  617. export function isImplicitlyInstallablePlugin(p) {
  618. return '__internal_installed_implicitly__' in p;
  619. }
  620. export const MEDIA_TYPES = {
  621. APPLICATION_JSON: 'application/json; charset=utf-8',
  622. APPLICATION_JSON_GRAPHQL_CALLBACK: 'application/json; callbackSpec=1.0; charset=utf-8',
  623. APPLICATION_GRAPHQL_RESPONSE_JSON: 'application/graphql-response+json; charset=utf-8',
  624. MULTIPART_MIXED_NO_DEFER_SPEC: 'multipart/mixed',
  625. MULTIPART_MIXED_EXPERIMENTAL: 'multipart/mixed; deferSpec=20220824',
  626. TEXT_HTML: 'text/html',
  627. };
  628. export function chooseContentTypeForSingleResultResponse(head) {
  629. const acceptHeader = head.headers.get('accept');
  630. if (!acceptHeader) {
  631. return MEDIA_TYPES.APPLICATION_JSON;
  632. }
  633. else {
  634. const preferred = new Negotiator({
  635. headers: { accept: head.headers.get('accept') },
  636. }).mediaType([
  637. MEDIA_TYPES.APPLICATION_JSON,
  638. MEDIA_TYPES.APPLICATION_GRAPHQL_RESPONSE_JSON,
  639. MEDIA_TYPES.APPLICATION_JSON_GRAPHQL_CALLBACK,
  640. ]);
  641. if (preferred) {
  642. return preferred;
  643. }
  644. else {
  645. return null;
  646. }
  647. }
  648. }
  649. function cloneObject(object) {
  650. return Object.assign(Object.create(Object.getPrototypeOf(object)), object);
  651. }
  652. //# sourceMappingURL=ApolloServer.js.map