From 98ca26a303cbdbd59bef893d00bd4cb7d88007f2 Mon Sep 17 00:00:00 2001 From: Evans Hauser Date: Thu, 10 May 2018 17:28:06 -0700 Subject: [PATCH] apollo-server-hapi: initial implementation of apollo-server base class --- packages/apollo-server-hapi/package.json | 8 +- .../apollo-server-hapi/src/ApolloServer.ts | 86 +++++++++++++++++++ packages/apollo-server-hapi/src/hapiApollo.ts | 12 ++- packages/apollo-server-hapi/src/index.ts | 14 +++ 4 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 packages/apollo-server-hapi/src/ApolloServer.ts diff --git a/packages/apollo-server-hapi/package.json b/packages/apollo-server-hapi/package.json index 81300921..647b17e3 100644 --- a/packages/apollo-server-hapi/package.json +++ b/packages/apollo-server-hapi/package.json @@ -25,12 +25,16 @@ }, "homepage": "https://github.com/apollographql/apollo-server#readme", "dependencies": { - "apollo-server-core": "^1.3.6", + "accept": "^3.0.2", + "apollo-server-core": "2.0.0-beta.0", "apollo-server-module-graphiql": "^1.3.4", - "boom": "^7.1.0" + "boom": "^7.1.0", + "graphql-playground-html": "^1.5.6" }, "devDependencies": { "@types/graphql": "0.12.7", + "@types/hapi": "^17.0.12", + "@types/node": "^10.0.6", "apollo-server-integration-testsuite": "^1.3.6", "hapi": "17.4.0" }, diff --git a/packages/apollo-server-hapi/src/ApolloServer.ts b/packages/apollo-server-hapi/src/ApolloServer.ts new file mode 100644 index 00000000..f17356e7 --- /dev/null +++ b/packages/apollo-server-hapi/src/ApolloServer.ts @@ -0,0 +1,86 @@ +import * as hapi from 'hapi'; +import { createServer, Server as HttpServer } from 'http'; +import { ApolloServerBase, EngineLauncherOptions } from 'apollo-server-core'; +import { parseAll } from 'accept'; +import { renderPlaygroundPage } from 'graphql-playground-html'; + +import { graphqlHapi } from './hapiApollo'; + +export interface ServerRegistration { + app: hapi.Server; + server: ApolloServerBase; + path?: string; + subscriptions?: boolean; +} + +export interface HapiListenOptions { + port?: number | string; + host?: string; // default: ''. This is where engineproxy listens. + pipePath?: string; + graphqlPaths?: string[]; // default: ['/graphql'] + innerHost?: string; // default: '127.0.0.1'. This is where Node listens. + launcherOptions?: EngineLauncherOptions; +} + +export const registerServer = async ({ + app, + server, + path, +}: ServerRegistration) => { + if (!path) path = '/graphql'; + + await app.ext({ + type: 'onRequest', + method: function(request, h) { + if (request.path !== path) { + return h.continue; + } + if (!server.disableTools && request.method === 'get') { + //perform more expensive content-type check only if necessary + const accept = parseAll(request.app); + const types = accept.mediaTypes as string[]; + const prefersHTML = + types.find( + (x: string) => x === 'text/html' || x === 'application/json', + ) === 'text/html'; + + if (prefersHTML) { + return h + .response( + renderPlaygroundPage({ + subscriptionsEndpoint: server.subscriptions && path, + endpoint: path, + version: '', + }), + ) + .type('text/html'); + } + } + return h.continue; + }, + }); + + await app.register({ + plugin: graphqlHapi, + options: { + path: path, + graphqlOptions: server.request.bind(server), + route: { + cors: true, + }, + }, + }); + + server.use({ path, getHttp: () => app.listener }); + + const listen = server.listen; + server.listen = async options => { + //requires that autoListen is false, so that + //hapi sets up app.listener without start + await app.start(); + + //starts the hapi listener at a random port when engine proxy used, + //otherwise will start the server at the provided port + return listen({ ...options }); + }; +}; diff --git a/packages/apollo-server-hapi/src/hapiApollo.ts b/packages/apollo-server-hapi/src/hapiApollo.ts index 22e0e6bc..592c66d1 100644 --- a/packages/apollo-server-hapi/src/hapiApollo.ts +++ b/packages/apollo-server-hapi/src/hapiApollo.ts @@ -1,5 +1,5 @@ import * as Boom from 'boom'; -import { Server, Response, Request, ReplyNoContinue } from 'hapi'; +import { Server, Request } from 'hapi'; import * as GraphiQL from 'apollo-server-module-graphiql'; import { GraphQLOptions, @@ -39,13 +39,17 @@ const graphqlHapi: IPlugin = { method: ['GET', 'POST'], path: options.path || '/graphql', vhost: options.vhost || undefined, - config: options.route || {}, + options: options.route || {}, handler: async (request, h) => { try { const gqlResponse = await runHttpQuery([request], { method: request.method.toUpperCase(), options: options.graphqlOptions, - query: request.method === 'post' ? request.payload : request.query, + query: + request.method === 'post' + ? //TODO type payload as string or Record + (request.payload as any) + : request.query, }); const response = h.response(gqlResponse); @@ -98,7 +102,7 @@ const graphiqlHapi: IPlugin = { server.route({ method: 'GET', path: options.path || '/graphiql', - config: options.route || {}, + options: options.route || {}, handler: async (request, h) => { const graphiqlString = await GraphiQL.resolveGraphiQLString( request.query, diff --git a/packages/apollo-server-hapi/src/index.ts b/packages/apollo-server-hapi/src/index.ts index 20a147a0..3ea899d1 100644 --- a/packages/apollo-server-hapi/src/index.ts +++ b/packages/apollo-server-hapi/src/index.ts @@ -1,3 +1,14 @@ +// Expose types which can be used by both middleware flavors. +export { GraphQLOptions } from 'apollo-server-core'; +export { + ApolloError, + toApolloError, + SyntaxError, + ValidationError, + AuthenticationError, + ForbiddenError, +} from 'apollo-server-core'; + export { IRegister, HapiOptionsFunction, @@ -7,3 +18,6 @@ export { graphqlHapi, graphiqlHapi, } from './hapiApollo'; + +// ApolloServer integration +export { registerServer } from './ApolloServer';