如何在 GraphQL 中设置 http 状态代码

问题描述 投票:0回答:7

我想在 GraphQL 身份验证查询中设置一个 http 状态代码,具体取决于身份验证尝试是否成功 (200)、未经授权 (401) 或缺少参数 (422)。

我正在使用 Koa 和 Apollo 并像这样配置我的服务器:

const graphqlKoaMiddleware = graphqlKoa(ctx => {
  return ({
    schema,
    formatError: (err) => ({ message: err.message, status: err.status }),
    context: {
      stationConnector: new StationConnector(),
      passengerTypeConnector: new PassengerTypeConnector(),
      authConnector: new AuthConnector(),
      cookies: ctx.cookies
    }
  })
})

router.post("/graphql", graphqlKoaMiddleware)

如您所见,我已将

formatError
设置为返回消息和状态,但目前仅返回消息。该错误消息来自我在解析器函数中抛出的错误。

例如:

const resolvers = {
  Query: {
    me: async (obj, {username, password}, ctx) => {
      try {
        return await ctx.authConnector.getUser(ctx.cookies)  
      }catch(err){
        throw new Error(`Could not get user: ${err}`);
      }
    }
  }
}

此方法的唯一问题是它在错误消息中设置状态代码,而不是实际更新响应对象。

即使对于失败的查询/突变,GraphQL 是否也需要

200
响应,或者我可以如何更新响应对象状态代码?如果没有,如何设置上述错误对象状态代码?

javascript graphql apollo-server http-status-codes koa
7个回答
8
投票

除非 GraphQL 请求本身格式错误,否则 GraphQL 将返回 200 状态代码,即使解析器之一内部抛出错误也是如此。这是设计使然,因此实际上没有办法配置 Apollo 服务器来改变这种行为。

也就是说,您可以轻松连接自己的中间件。您可以导入 Apollo 中间件在后台使用的

runHttpQuery
函数。事实上,您几乎可以复制源代码并修改它以满足您的需求:

const graphqlMiddleware = options => {
  return (req, res, next) => {
    runHttpQuery([req, res], {
      method: req.method,
      options: options,
      query: req.method === 'POST' ? req.body : req.query,
    }).then((gqlResponse) => {
      res.setHeader('Content-Type', 'application/json')

      // parse the response for errors and set status code if needed

      res.write(gqlResponse)
      res.end()
      next()
    }, (error) => {
      if ( 'HttpQueryError' !== error.name ) {
        return next(error)
      }

      if ( error.headers ) {
        Object.keys(error.headers).forEach((header) => {
          res.setHeader(header, error.headers[header])
        })
      }

      res.statusCode = error.statusCode
      res.write(error.message)
      res.end()
      next(false)
    })
  }
}

2
投票

对于 apollo-server,安装 apollo-server-errors 包。对于身份验证错误,

import { AuthenticationError } from "apollo-server-errors";

然后,在你的解析器中

throw new AuthenticationError('unknown user');

这将返回 400 状态代码。

此博客

中阅读有关此主题的更多信息

1
投票

如您所见here

formatError
不支持状态代码,您可以做的是创建一个包含消息和状态字段的状态响应类型,并在解析器上返回相应的内容。

即使对于失败的查询/突变,GraphQL 是否也需要 200 响应? 不,如果查询失败,它将返回

null
以及您在服务器端抛出的错误。


0
投票

尝试添加响应并设置响应状态代码,假设您的 err.status 已经是像 401 等这样的整数:

const graphqlKoaMiddleware = graphqlKoa(ctx => {
return ({
schema,
response: request.resonse,
formatError: (err) => {
response.statusCode =  err.status;
return ({message: err.message, status: err.status})},
context: {
  stationConnector: new StationConnector(),
  passengerTypeConnector: new PassengerTypeConnector(),
  authConnector: new AuthConnector(),
  cookies: ctx.cookies
}
})})

0
投票

根据丹尼尔斯的回答,我已经成功编写了中间件。

import { HttpQueryError, runHttpQuery } from 'apollo-server-core';
import { ApolloServer } from 'apollo-server-express';


// Source taken from: https://github.com/apollographql/apollo-server/blob/928f70906cb881e85caa2ae0e56d3dac61b20df0/packages/apollo-server-express/src/ApolloServer.ts
// Duplicated apollo-express middleware
export const badRequestToOKMiddleware = (apolloServer: ApolloServer) => {
return async (req, res, next) => {
  runHttpQuery([req, res], {
    method: req.method,
    options: await apolloServer.createGraphQLServerOptions(req, res),
    query: req.method === 'POST' ? req.body : req.query,
    request: req,
  }).then(
    ({ graphqlResponse, responseInit }) => {
      if (responseInit.headers) {
        for (const [name, value] of Object.entries(responseInit.headers)) {
          res.setHeader(name, value);
        }
      }
      res.statusCode = (responseInit as any).status || 200;

      // Using `.send` is a best practice for Express, but we also just use
      // `.end` for compatibility with `connect`.
      if (typeof res.send === 'function') {
        res.send(graphqlResponse);
      } else {
        res.end(graphqlResponse);
      }
    },
    (error: HttpQueryError) => {
      if ('HttpQueryError' !== error.name) {
        return next(error);
      }

      if (error.headers) {
        for (const [name, value] of Object.entries(error.headers)) {
          res.setHeader(name, value);
        }
      }

      res.statusCode = error.message.indexOf('UNAUTHENTICATED') !== -1 ? 200 : error.statusCode;
      if (typeof res.send === 'function') {
        // Using `.send` is a best practice for Express, but we also just use
        // `.end` for compatibility with `connect`.
        res.send(error.message);
      } else {
        res.end(error.message);
      }
    },
  );
};
  }

app.use(apolloServer.graphqlPath, badRequestToOKMiddleware(apolloServer));

0
投票

apollo-server-express V3 支持此功能。创建您自己的插件。然后您可以查看引发的错误以确定状态代码。

  import {ApolloServerPlugin} from "apollo-server-plugin-base/src/index";
  
  const statusCodePlugin:ApolloServerPlugin  = {
    async requestDidStart(requestContext) {
      return {
        async willSendResponse(requestContext) {
          const errors = (requestContext?.response?.errors || []) as any[];
          for(let error of errors){
            if(error?.code === 'unauthorized'){
              requestContext.response.http.status = 401;
            }
            if(error?.code === 'access'){
              requestContext.response.http.status = 403;
            }
          }
        }
      }
    },
  };
  
  export default statusCodePlugin;

0
投票

您定义的 setHttpPlugin 是一个自定义的 Apollo Server 插件。 Apollo Server 提供了一个插件系统,允许您通过定义自定义插件来扩展其功能。

在 setHttpPlugin 中,您定义一个插件,该插件根据响应中是否存在错误来修改 HTTP 响应状态代码。

async function StartServer() {
  const setHttpPlugin = {
    async requestDidStart() {
      return {
        async willSendResponse({ response }) {
          response.http.status = response.errors[0].status || 500
        },
      }
    },
  }
  const app = express()

  const PORT = process.env.PORT || 3002
  const Server = new ApolloServer({
    typeDefs,
    resolvers,
    status400ForVariableCoercionErrors: true,
    csrfPrevention: false,
    context: async ({ req }) => {
      return req
    },
    formatError: (err) => {
      console.log(err)
      return {
        message: err.message || 'Interal server error plase try again',
        status: err.extensions.http.status || 500,
        code: err.extensions.code || 'INTERNAL_SERVER_ERROR',
      }
    },
    plugins: [ApolloServerPluginLandingPageGraphQLPlayground, setHttpPlugin],
  })}


export const createPostResolver = async (_, { postInfo }, context) => {
  const { id, error } = await ProtectRoutes(context)
  if (error) {
    throw new GraphQLError('Session has expired', {
      extensions: {
        code: 'BAD_REQUEST',
        http: {
          status: 401,
        },
      },
    })
  }}
© www.soinside.com 2019 - 2024. All rights reserved.