apollo-server/packages/apollo-server-hapi/src/ApolloServer.ts
2018-06-29 13:57:03 +02:00

154 lines
4.6 KiB
TypeScript

import * as hapi from 'hapi';
import { ApolloServerBase } from 'apollo-server-core';
import { parseAll } from 'accept';
import {
renderPlaygroundPage,
RenderPageOptions as PlaygroundRenderPageOptions,
} from '@apollographql/graphql-playground-html';
import { processRequest as processFileUploads } from 'apollo-upload-server';
import { graphqlHapi } from './hapiApollo';
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { GraphQLOptions, FileUploadOptions } from 'apollo-server-core';
function handleFileUploads(uploadsConfig: FileUploadOptions) {
return async (request: hapi.Request) => {
if (request.mime === 'multipart/form-data') {
Object.defineProperty(request, 'payload', {
value: await processFileUploads(request, uploadsConfig),
writable: false,
});
}
};
}
export class ApolloServer extends ApolloServerBase {
// This translates the arguments from the middleware into graphQL options It
// provides typings for the integration specific behavior, ideally this would
// be propagated with a generic to the super class
async createGraphQLServerOptions(
request: hapi.Request,
h: hapi.ResponseToolkit,
): Promise<GraphQLOptions> {
return super.graphQLServerOptions({ request, h });
}
protected supportsSubscriptions(): boolean {
return true;
}
protected supportsUploads(): boolean {
return true;
}
public async applyMiddleware({
app,
cors,
path,
disableHealthCheck,
gui,
onHealthCheck,
}: ServerRegistration) {
if (!path) path = '/graphql';
await app.ext({
type: 'onRequest',
method: async function(request, h) {
if (request.path !== path) {
return h.continue;
}
if (this.uploadsConfig) {
await handleFileUploads(this.uploadsConfig)(request);
}
// Note: if you enable a gui in production and expect to be able to see your
// schema, you'll need to manually specify `introspection: true` in the
// ApolloServer constructor; by default, the introspection query is only
// enabled in dev.
const guiEnabled =
!!gui || (gui === undefined && process.env.NODE_ENV !== 'production');
// enableGUI takes precedence over the server tools setting
if (guiEnabled && request.method === 'get') {
// perform more expensive content-type check only if necessary
const accept = parseAll(request.headers);
const types = accept.mediaTypes as string[];
const prefersHTML =
types.find(
(x: string) => x === 'text/html' || x === 'application/json',
) === 'text/html';
if (prefersHTML) {
const playgroundRenderPageOptions: PlaygroundRenderPageOptions = {
endpoint: path,
subscriptionEndpoint: this.subscriptionsPath,
version: this.playgroundVersion,
};
return h
.response(renderPlaygroundPage(playgroundRenderPageOptions))
.type('text/html')
.takeover();
}
}
return h.continue;
}.bind(this),
});
if (!disableHealthCheck) {
await app.route({
method: '*',
path: '/.well-known/apollo/server-health',
options: {
cors: cors !== undefined ? cors : true,
},
handler: async function(request, h) {
if (onHealthCheck) {
try {
await onHealthCheck(request);
} catch {
const response = h.response({ status: 'fail' });
response.code(503);
response.type('application/health+json');
return response;
}
}
const response = h.response({ status: 'pass' });
response.type('application/health+json');
return response;
},
});
}
await app.register({
plugin: graphqlHapi,
options: {
path,
graphqlOptions: this.createGraphQLServerOptions.bind(this),
route: {
cors: cors !== undefined ? cors : true,
},
},
});
this.graphqlPath = path;
}
}
export interface ServerRegistration {
app?: hapi.Server;
path?: string;
cors?: boolean | hapi.RouteOptionsCors;
onHealthCheck?: (request: hapi.Request) => Promise<any>;
disableHealthCheck?: boolean;
gui?: boolean;
uploads?: boolean | Record<string, any>;
}
export const registerServer = () => {
throw new Error(
'Please use server.applyMiddleware instead of registerServer. This warning will be removed in the next release',
);
};