Skip to content

Commit

Permalink
feat(lambda): support async handler functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
dsanders11 committed Jun 24, 2019
1 parent 3ebc3ed commit 537db35
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 68 deletions.
74 changes: 45 additions & 29 deletions packages/apollo-server-lambda/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export class ApolloServer extends ApolloServerBase {
return (
event: APIGatewayProxyEvent,
context: LambdaContext,
callback: APIGatewayProxyCallback,
callback?: APIGatewayProxyCallback,
) => {
// We re-load the headers into a Fetch API-compatible `Headers`
// interface within `graphqlLambda`, but we still need to respect the
Expand Down Expand Up @@ -150,14 +150,20 @@ export class ApolloServer extends ApolloServerBase {
}, {});

if (event.httpMethod === 'OPTIONS') {
context.callbackWaitsForEmptyEventLoop = false;
return callback(null, {
const result = {
body: '',
statusCode: 204,
headers: {
...requestCorsHeadersObject,
},
});
};

if (callback) {
context.callbackWaitsForEmptyEventLoop = false;
return callback(null, result);
} else {
return Promise.resolve(result);
}
}

if (this.playgroundOptions && event.httpMethod === 'GET') {
Expand All @@ -173,41 +179,51 @@ export class ApolloServer extends ApolloServerBase {
...this.playgroundOptions,
};

return callback(null, {
const result = {
body: renderPlaygroundPage(playgroundRenderPageOptions),
statusCode: 200,
headers: {
'Content-Type': 'text/html',
...requestCorsHeadersObject,
},
});
};

if (callback) {
return callback(null, result);
} else {
return Promise.resolve(result);
}
}
}

const callbackFilter: APIGatewayProxyCallback = (error, result) => {
callback(
error,
result && {
...result,
headers: {
...result.headers,
...requestCorsHeadersObject,
},
},
);
};
if (callback) {
// Maintain existing behavior
context.callbackWaitsForEmptyEventLoop = false;
}

graphqlLambda(async () => {
// In a world where this `createHandler` was async, we might avoid this
// but since we don't want to introduce a breaking change to this API
// (by switching it to `async`), we'll leverage the
// `GraphQLServerOptions`, which are dynamically built on each request,
// to `await` the `promiseWillStart` which we kicked off at the top of
// this method to ensure that it runs to completion (which is part of
// its contract) prior to processing the request.
await promiseWillStart;
return this.createGraphQLServerOptions(event, context);
})(event, context, callbackFilter);
const resultPromise = promiseWillStart.then(() => {
return this.createGraphQLServerOptions(event, context).then(options => {
return graphqlLambda(options)(event, context).then(result => {
return result && {
...result,
headers: {
...result.headers,
...requestCorsHeadersObject,
},
};
});
});
});

if (callback) {
resultPromise.then(result => {
callback(null, result);
}).catch(error => {
callback(error);
})
} else {
return resultPromise;
}
};
}
}
81 changes: 42 additions & 39 deletions packages/apollo-server-lambda/src/lambdaApollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import {
HttpQueryError,
runHttpQuery,
} from 'apollo-server-core';
import { Headers, ValueOrPromise } from 'apollo-server-env';
import { Headers } from 'apollo-server-env';

export interface LambdaGraphQLOptionsFunction {
(event: lambda.APIGatewayProxyEvent, context: lambda.Context): ValueOrPromise<
GraphQLOptions
export interface APIGatewayProxyAsyncHandler {
(event: lambda.APIGatewayProxyEvent, context: lambda.Context): Promise<
lambda.APIGatewayProxyResult
>;
}

export function graphqlLambda(
options: GraphQLOptions | LambdaGraphQLOptionsFunction,
): lambda.APIGatewayProxyHandler {
options: GraphQLOptions,
): APIGatewayProxyAsyncHandler {
if (!options) {
throw new Error('Apollo Server requires options.');
}
Expand All @@ -25,48 +25,51 @@ export function graphqlLambda(
);
}

const graphqlHandler: lambda.APIGatewayProxyHandler = (
const graphqlHandler: APIGatewayProxyAsyncHandler = async (
event,
context,
callback,
): void => {
context.callbackWaitsForEmptyEventLoop = false;

context
) => {
if (event.httpMethod === 'POST' && !event.body) {
return callback(null, {
return {
body: 'POST body missing.',
statusCode: 500,
});
};
}
runHttpQuery([event, context], {
method: event.httpMethod,
options: options,
query:
event.httpMethod === 'POST' && event.body
? JSON.parse(event.body)
: event.queryStringParameters,
request: {
url: event.path,

try {
const {
graphqlResponse,
responseInit
} = await runHttpQuery([event, context], {
method: event.httpMethod,
headers: new Headers(event.headers),
},
}).then(
({ graphqlResponse, responseInit }) => {
callback(null, {
body: graphqlResponse,
statusCode: 200,
headers: responseInit.headers,
});
},
(error: HttpQueryError) => {
if ('HttpQueryError' !== error.name) return callback(error);
callback(null, {
options: options,
query:
event.httpMethod === 'POST' && event.body
? JSON.parse(event.body)
: event.queryStringParameters,
request: {
url: event.path,
method: event.httpMethod,
headers: new Headers(event.headers),
},
});

return {
body: graphqlResponse,
statusCode: 200,
headers: responseInit.headers,
};
} catch (error) {
if (error instanceof HttpQueryError) {
return {
body: error.message,
statusCode: error.statusCode,
headers: error.headers,
});
},
);
};
}

throw error;
}
};

return graphqlHandler;
Expand Down

0 comments on commit 537db35

Please sign in to comment.