首页 > 解决方案 > Apollo Server & 4xx 状态码

问题描述

目前,我的 Apollo 服务器(在 HapiJS 上运行)为每个请求返回 HTTP 200,包括失败的请求。

我希望 GraphQL 服务器为不成功的请求返回 HTTP 4xx。主要原因是我想为我的 ELB 设置监控。

我知道 Apollo Server 有一个引擎平台,但我想使用我当前的基础架构来实现它。

关于我如何做到这一点的任何想法?我试图为我的 HapiJS 服务器捕获“onPreResponse”事件,但我无法在那里修改状态代码。

标签: hapijsapollo-server

解决方案


看完这个答案。这是通过修改hapiApollo.ts文件的hapijs插件graphqlHapi的解决方案。

server.ts

import { makeExecutableSchema } from 'apollo-server';
import { ApolloServer, gql } from 'apollo-server-hapi';
import Hapi from 'hapi';
import { graphqlHapi } from './hapiApollo';

const typeDefs = gql`
  type Query {
    _: String
  }
`;
const resolvers = {
  Query: {
    _: () => {
      throw new Error('some error');
    },
  },
};
const schema = makeExecutableSchema({ typeDefs, resolvers });
const port = 3000;
async function StartServer() {
  const app = new Hapi.Server({ port });
  graphqlHapi.register(app, { path: '/graphql', graphqlOptions: { schema } });
  app.ext('onPreResponse', (request: any, h: any) => {
    const response = request.response;
    if (!response.isBoom) {
      return h.continue;
    }
    return h.response({ message: response.message }).code(400);
  });

  await app.start();
}

StartServer()
  .then(() => {
    console.log(`apollo server is listening on http://localhost:${port}/graphql`);
  })
  .catch((error) => console.log(error));

hapiApollo.ts

import Boom from 'boom';
import { Server, Request, RouteOptions } from 'hapi';
import { GraphQLOptions, runHttpQuery, convertNodeHttpToRequest } from 'apollo-server-core';
import { ValueOrPromise } from 'apollo-server-types';

export interface IRegister {
  (server: Server, options: any, next?: Function): void;
}

export interface IPlugin {
  name: string;
  version?: string;
  register: IRegister;
}

export interface HapiOptionsFunction {
  (request?: Request): ValueOrPromise<GraphQLOptions>;
}

export interface HapiPluginOptions {
  path: string;
  vhost?: string;
  route?: RouteOptions;
  graphqlOptions: GraphQLOptions | HapiOptionsFunction;
}

const graphqlHapi: IPlugin = {
  name: 'graphql',
  register: (server: Server, options: HapiPluginOptions, next?: Function) => {
    if (!options || !options.graphqlOptions) {
      throw new Error('Apollo Server requires options.');
    }
    server.route({
      method: ['GET', 'POST'],
      path: options.path || '/graphql',
      vhost: options.vhost || undefined,
      options: options.route || {},
      handler: async (request, h) => {
        try {
          const { graphqlResponse, responseInit } = await runHttpQuery([request, h], {
            method: request.method.toUpperCase(),
            options: options.graphqlOptions,
            query:
              request.method === 'post'
                ? // TODO type payload as string or Record
                  (request.payload as any)
                : request.query,
            request: convertNodeHttpToRequest(request.raw.req),
          });

          // add our custom error handle logic
          const graphqlResponseObj = JSON.parse(graphqlResponse);
          if (graphqlResponseObj.errors && graphqlResponseObj.errors.length) {
            throw new Error(graphqlResponseObj.errors[0].message);
          }

          const response = h.response(graphqlResponse);
          Object.keys(responseInit.headers as any).forEach((key) =>
            response.header(key, (responseInit.headers as any)[key]),
          );
          return response;
        } catch (error) {
          // handle our custom error
          if (!error.name) {
            throw Boom.badRequest(error.message);
          }

          if ('HttpQueryError' !== error.name) {
            throw Boom.boomify(error);
          }

          if (true === error.isGraphQLError) {
            const response = h.response(error.message);
            response.code(error.statusCode);
            response.type('application/json');
            return response;
          }

          const err = new Boom(error.message, { statusCode: error.statusCode });
          if (error.headers) {
            Object.keys(error.headers).forEach((header) => {
              err.output.headers[header] = error.headers[header];
            });
          }
          // Boom hides the error when status code is 500
          err.output.payload.message = error.message;
          throw err;
        }
      },
    });

    if (next) {
      next();
    }
  },
};

export { graphqlHapi };

现在,当 GraphQL 解析器抛出错误时,客户端将收到带有 Http 状态码 400 的自定义响应,而不是带有 GraphQL 错误响应的 200 状态码。

来自浏览器的一般:

Request URL: http://localhost:3000/graphql
Request Method: POST
Status Code: 400 Bad Request
Remote Address: 127.0.0.1:3000
Referrer Policy: no-referrer-when-downgrade

响应正文是:{"message":"some error"}


推荐阅读