mirror of
https://github.com/vale981/apollo-server
synced 2025-03-15 07:16:40 -04:00

This commit follows-up on #1795, which introduced the new request pipeline, and implements the triggers for its new life-cycle hooks within the various integration packages. Previously, the only implementation was within the `apollo-server` package and didn't get triggered for those invoking the `applyMiddleware` method on integrations (e.g. `apollo-server-express`, `...-hapi` and `...-koa`). While in an ideal world, ALL existing `applyMiddleware` functions would be marked as `async` functions and allow us to `await ApolloServer#willStart`, in practice the only `applyMiddleware` which is currently `async` is the one within the Hapi implementation. Therefore, we'll instead kick off the `willStart` lifecycle hook as soon as `applyMiddleware` is started, return as quickly as we have before and then (essentially) yield the completion of Apollo Server's `willStart` prior to serving the first request — thus ensuring the completion of server-startup activities. Similarly, we'll do the same for `createHandler` methods on integrations which don't utilize Node.js server frameworks but don't have `async` handlers (e.g. AWS, Lambda, etc.).
174 lines
4.8 KiB
TypeScript
174 lines
4.8 KiB
TypeScript
import { ApolloServerBase, GraphQLOptions } from 'apollo-server-core';
|
|
import { processRequest as processFileUploads } from '@apollographql/apollo-upload-server';
|
|
import { ServerResponse } from 'http';
|
|
import { send } from 'micro';
|
|
import { renderPlaygroundPage } from '@apollographql/graphql-playground-html';
|
|
import { parseAll } from 'accept';
|
|
|
|
import { graphqlMicro } from './microApollo';
|
|
import { MicroRequest } from './types';
|
|
|
|
export interface ServerRegistration {
|
|
path?: string;
|
|
disableHealthCheck?: boolean;
|
|
onHealthCheck?: (req: MicroRequest) => Promise<any>;
|
|
}
|
|
|
|
export class ApolloServer extends ApolloServerBase {
|
|
// Extract Apollo Server options from the request.
|
|
async createGraphQLServerOptions(
|
|
req: MicroRequest,
|
|
res: ServerResponse,
|
|
): Promise<GraphQLOptions> {
|
|
return super.graphQLServerOptions({ req, res });
|
|
}
|
|
|
|
// Prepares and returns an async function that can be used by Micro to handle
|
|
// GraphQL requests.
|
|
public createHandler({
|
|
path,
|
|
disableHealthCheck,
|
|
onHealthCheck,
|
|
}: ServerRegistration = {}) {
|
|
// We'll kick off the `willStart` right away, so hopefully it'll finish
|
|
// before the first request comes in.
|
|
const promiseWillStart = this.willStart();
|
|
|
|
return async (req, res) => {
|
|
this.graphqlPath = path || '/graphql';
|
|
|
|
await promiseWillStart;
|
|
|
|
await this.handleFileUploads(req);
|
|
|
|
(await this.handleHealthCheck({
|
|
req,
|
|
res,
|
|
disableHealthCheck,
|
|
onHealthCheck,
|
|
})) ||
|
|
this.handleGraphqlRequestsWithPlayground({ req, res }) ||
|
|
(await this.handleGraphqlRequestsWithServer({ req, res })) ||
|
|
send(res, 404, null);
|
|
};
|
|
}
|
|
|
|
// This integration supports file uploads.
|
|
protected supportsUploads(): boolean {
|
|
return true;
|
|
}
|
|
|
|
// This integration supports subscriptions.
|
|
protected supportsSubscriptions(): boolean {
|
|
return true;
|
|
}
|
|
|
|
// If health checking is enabled, trigger the `onHealthCheck`
|
|
// function when the health check URL is requested.
|
|
private async handleHealthCheck({
|
|
req,
|
|
res,
|
|
disableHealthCheck,
|
|
onHealthCheck,
|
|
}: {
|
|
req: MicroRequest;
|
|
res: ServerResponse;
|
|
disableHealthCheck?: boolean;
|
|
onHealthCheck?: (req: MicroRequest) => Promise<any>;
|
|
}): Promise<boolean> {
|
|
let handled = false;
|
|
|
|
if (
|
|
!disableHealthCheck &&
|
|
req.url === '/.well-known/apollo/server-health'
|
|
) {
|
|
// Response follows
|
|
// https://tools.ietf.org/html/draft-inadarei-api-health-check-01
|
|
res.setHeader('Content-Type', 'application/health+json');
|
|
|
|
if (onHealthCheck) {
|
|
try {
|
|
await onHealthCheck(req);
|
|
} catch (error) {
|
|
send(res, 503, { status: 'fail' });
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
if (!handled) {
|
|
send(res, 200, { status: 'pass' });
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
// If the `playgroundOptions` are set, register a `graphql-playground` instance
|
|
// (not available in production) that is then used to handle all
|
|
// incoming GraphQL requests.
|
|
private handleGraphqlRequestsWithPlayground({
|
|
req,
|
|
res,
|
|
}: {
|
|
req: MicroRequest;
|
|
res: ServerResponse;
|
|
}): boolean {
|
|
let handled = false;
|
|
|
|
if (this.playgroundOptions && req.method === 'GET') {
|
|
const accept = parseAll(req.headers);
|
|
const types = accept.mediaTypes as string[];
|
|
const prefersHTML =
|
|
types.find(
|
|
(x: string) => x === 'text/html' || x === 'application/json',
|
|
) === 'text/html';
|
|
|
|
if (prefersHTML) {
|
|
const middlewareOptions = {
|
|
endpoint: this.graphqlPath,
|
|
subscriptionEndpoint: this.subscriptionsPath,
|
|
...this.playgroundOptions,
|
|
};
|
|
send(res, 200, renderPlaygroundPage(middlewareOptions));
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
return handled;
|
|
}
|
|
|
|
// Handle incoming GraphQL requests using Apollo Server.
|
|
private async handleGraphqlRequestsWithServer({
|
|
req,
|
|
res,
|
|
}: {
|
|
req: MicroRequest;
|
|
res: ServerResponse;
|
|
}): Promise<boolean> {
|
|
let handled = false;
|
|
const url = req.url.split('?')[0];
|
|
if (url === this.graphqlPath) {
|
|
const graphqlHandler = graphqlMicro(
|
|
this.createGraphQLServerOptions.bind(this),
|
|
);
|
|
const responseData = await graphqlHandler(req, res);
|
|
send(res, 200, responseData);
|
|
handled = true;
|
|
}
|
|
return handled;
|
|
}
|
|
|
|
// If file uploads are detected, prepare them for easier handling with
|
|
// the help of `apollo-upload-server`.
|
|
private async handleFileUploads(req: MicroRequest) {
|
|
const contentType = req.headers['content-type'];
|
|
if (
|
|
this.uploadsConfig &&
|
|
contentType &&
|
|
contentType.startsWith('multipart/form-data')
|
|
) {
|
|
req.filePayload = await processFileUploads(req, this.uploadsConfig);
|
|
}
|
|
}
|
|
}
|