diff --git a/api/graphql.js b/api/graphql.js index 2b48b77..4a51b3d 100644 --- a/api/graphql.js +++ b/api/graphql.js @@ -1,5 +1,25 @@ +const beeline = require("honeycomb-beeline")({ + writeKey: process.env["HONEYCOMB_WRITE_KEY"], + dataset: + process.env["NODE_ENV"] === "production" + ? "Dress to Impress (2020)" + : "Dress to Impress (2020, dev)", + serviceName: "impress-2020-gql-server", +}); + const { ApolloServer } = require("../src/server/lib/apollo-server-vercel"); const { config } = require("../src/server"); const server = new ApolloServer(config); -module.exports = server.createHandler(); +const serverHandler = server.createHandler(); + +export default async (req, res) => { + await serverHandler(req, res); + + // As a sneaky trick, we require the Honeycomb trace to finish before the + // request formally finishes. This... is technically a slowdown, I'm not sure + // how much of one. Hopefully not too much? + // https://vercel.com/docs/platform/limits#streaming-responses + await beeline.flush(); + res.end(); +}; diff --git a/src/server/index.js b/src/server/index.js index 30fa2f3..60abbc8 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -1,12 +1,3 @@ -require("honeycomb-beeline")({ - writeKey: process.env["HONEYCOMB_WRITE_KEY"], - dataset: - process.env["NODE_ENV"] === "production" - ? "Dress to Impress (2020)" - : "Dress to Impress (2020, dev)", - serviceName: "impress-2020-gql-server", -}); - const { gql, makeExecutableSchema } = require("apollo-server"); import { addBeelineToSchema, beelinePlugin } from "./lib/beeline-graphql"; diff --git a/src/server/lib/apollo-server-vercel.js b/src/server/lib/apollo-server-vercel.js index 082c2bc..4ecae57 100644 --- a/src/server/lib/apollo-server-vercel.js +++ b/src/server/lib/apollo-server-vercel.js @@ -17,35 +17,37 @@ function graphqlVercel(options) { ); } - const graphqlHandler = (req, res) => { + const graphqlHandler = async (req, res) => { if (req.httpMethod === "POST" && !req.body) { - return res.status(500).send("POST body missing."); + res.status(500).write("POST body missing."); + return; } - runHttpQuery([req, res], { - method: req.method, - options: options, - query: req.method === "POST" && req.body ? req.body : req.query, - request: { - url: req.path, + let result; + try { + result = await runHttpQuery([req, res], { method: req.method, - headers: new Headers(req.headers), - }, - }).then( - ({ graphqlResponse, responseInit }) => { - setHeaders(res, new Headers(responseInit.headers)).send( - graphqlResponse - ); - }, - (error) => { - if ("HttpQueryError" !== error.name) { - console.error(error); - return; - } - setHeaders(res, new Headers(error.headers)) - .status(error.statusCode) - .send(error.message); + options: options, + query: req.method === "POST" && req.body ? req.body : req.query, + request: { + url: req.path, + method: req.method, + headers: new Headers(req.headers), + }, + }); + } catch (error) { + if ("HttpQueryError" !== error.name) { + console.error(error); + return; } - ); + setHeaders(res, new Headers(error.headers)) + .status(error.statusCode) + .write(error.message); + return; + } + + const { graphqlResponse, responseInit } = result; + setHeaders(res, new Headers(responseInit.headers)); + res.write(graphqlResponse); }; return graphqlHandler; @@ -122,7 +124,7 @@ class ApolloServer extends ApolloServerBase { } } - return (req, res) => { + return async (req, res) => { // Make a request-specific copy of the CORS headers, based on the server // global CORS headers we've set above. const requestCorsHeaders = new Headers(corsHeaders); @@ -164,7 +166,8 @@ class ApolloServer extends ApolloServerBase { ); if (res.method === "OPTIONS") { - setHeaders(res, requestCorsHeadersObject).status(204).send(""); + setHeaders(res, requestCorsHeadersObject).status(204); + return; } if (this.playgroundOptions && req.method === "GET") { @@ -177,7 +180,7 @@ class ApolloServer extends ApolloServerBase { ...this.playgroundOptions, }; - return setHeaders( + setHeaders( res, new Headers({ "Content-Type": "text/html", @@ -185,11 +188,12 @@ class ApolloServer extends ApolloServerBase { }) ) .status(200) - .send(renderPlaygroundPage(playgroundRenderPageOptions)); + .write(renderPlaygroundPage(playgroundRenderPageOptions)); + return; } } - graphqlVercel(async () => { + await graphqlVercel(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 diff --git a/src/server/lib/beeline-graphql.js b/src/server/lib/beeline-graphql.js index 6fe2188..6f3bb86 100644 --- a/src/server/lib/beeline-graphql.js +++ b/src/server/lib/beeline-graphql.js @@ -18,8 +18,6 @@ export function addBeelineToSchema(schema) { result = oldResolve(source, args, { ...context, span }, info); if (result.then) await result; } catch (error) { - // TODO(matchu): Not sure why this is here, it seems like we should - // re-throw the error, and even then this seems to not be happening? span.addContext({ "error.message": error.message }); span.addContext({ "error.stacktrace": error.stack }); } @@ -75,8 +73,10 @@ export const beelinePlugin = { const trace = beeline.startTrace(); return { didResolveOperation({ operationName }) { - trace.payload["name"] = operationName; - trace.payload["operation_name"] = operationName; + beeline.addContext({ + name: operationName, + operation_name: operationName, + }); }, willSendResponse() { beeline.finishTrace(trace);