如何在 GraphQL 中设置 http 状态码

Posted

技术标签:

【中文标题】如何在 GraphQL 中设置 http 状态码【英文标题】:How to set http status code in GraphQL 【发布时间】:2018-10-01 01:07:16 【问题描述】:

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

我正在使用Koa and 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 响应,或者我可以如何更新响应对象状态代码?如果不是,如何设置上述错误对象状态码?

【问题讨论】:

【参考方案1】:

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;

【讨论】:

【参考方案2】:

根据 Daniels 的回答,我已经设法编写了中间件。

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));

【讨论】:

【参考方案3】:

假设您的 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

))

【讨论】:

【参考方案4】:

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

import  AuthenticationError  from "apollo-server-errors";

然后,在您的解析器中 throw new AuthenticationError('unknown user');

这将返回 400 状态代码。

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

【讨论】:

【参考方案5】:

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

也就是说,您可以轻松连接自己的中间件。您可以导入 Apollo 中间件在后台使用的 runHttpQuery 函数。事实上,您几乎可以复制 source code 并修改它以满足您的需要:

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)
    )
  

【讨论】:

被低估的答案 好的,所以我正在尝试这样做,但我不知道如何将这个东西与 Apollo 挂钩。我们是否有任何示例代码可以指出如何将它们连接在一起,或者作者可以提供一个示例吗?谢谢。 您提供的中间件如何使用?【参考方案6】:

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

即使查询/突变失败,GraphQL 是否也需要 200 响应? 不,如果查询失败,它将返回 null 以及您在服务器端抛出的错误。

【讨论】:

以上是关于如何在 GraphQL 中设置 http 状态码的主要内容,如果未能解决你的问题,请参考以下文章

如何在graphql查询中设置loading true。?

如何在graphql突变中设置多对多关系?

如何在 Rails 中设置 GraphQL Relay API

GraphQL:我需要在我的项目中设置啥?

在 Bottle 中设置 HTTP 状态码?

在我想使用的 GraphQL Playground 中设置测试标头会导致“无法访问服务器”