GraphQL 突变“在将标头发送到客户端后无法设置标头”

Posted

技术标签:

【中文标题】GraphQL 突变“在将标头发送到客户端后无法设置标头”【英文标题】:GraphQL mutation "Cannot set headers after they are sent to the client" 【发布时间】:2020-03-31 23:27:15 【问题描述】:

我正在实施 graphql 登录突变来验证用户登录凭据。 Mutation 使用 bcrypt 验证密码,然后向客户端发送一个 cookie,客户端将根据 cookie 是买方用户还是所有者用户呈现用户配置文件)。

GraphQL 登录变更代码:

const Mutation = new GraphQLObjectType(
    name: 'Mutation',
    fields: 
    loginUser: 
            type: UserType,
            args: 
                email:  type: GraphQLString ,
                password:  type: GraphQLString 
            ,
            resolve: function (parent, args,  req, res ) 
                User.findOne( email: args.email , (err, user) => 
                    if (user) 
                        bcrypt.compare(args.password, user.password).then(isMatch => 
                            if (isMatch) 
                                if (!user.owner) 
                                    res.cookie('cookie', "buyer",  maxAge: 900000, httpOnly: false, path: '/' );
                                 else 
                                    res.cookie('cookie', "owner",  maxAge: 900000, httpOnly: false, path: '/' );
                                
                                return res.status(200).json('Successful login');
                             else 
                                console.log('Incorrect password');
                            
                        );
                    
                );
            
        
     
);

Server.js:

app.use("/graphql",
  (req, res) => 
    return graphqlHTTP(
      schema,
      graphiql: true,
      context:  req, res ,
    )(req, res);
  );

错误信息:

(node:10630) UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
[0]     at ServerResponse.setHeader (_http_outgoing.js:470:11)
[0]     at ServerResponse.header (/Users/xxx/xxx/server/node_modules/express/lib/response.js:771:10)
[0]     at ServerResponse.append (/Users/xxx/xxx/server/node_modules/express/lib/response.js:732:15)
[0]     at ServerResponse.res.cookie (/Users/xxx/xxx/server/node_modules/express/lib/response.js:857:8)
[0]     at bcrypt.compare.then.isMatch (/Users/xxx/xxx/server/schema/schema.js:89:41)

我已经对此错误进行了一些研究,但似乎找不到相关的答案。问题似乎在于响应主体多次执行,因此“在将标头发送到客户端后无法设置标头”。由于我同时发送 res.cookie() 和 res.status(200),我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

express-graphql 已设置状态并为您发送响应 - 无需在解析器中调用 res.statusres.json

GraphQL 始终返回状态 200,除非请求的查询无效,在这种情况下,它返回状态 400。如果在执行请求时发生错误,它们将包含在响应中(在 errors 数组中单独从返回的data),但状态仍将是 200。这完全是设计使然 - 请参阅更多讨论 here。

您的解析器不应调用res.json,而是应返回一个适当类型的值(在本例中为UserType),或者一个将解析为该值的Promise。

此外,您不应该在解析器中使用回调,因为它们与 Promises 不兼容。如果您使用的 bcrypt 库支持使用 Promises,请使用适当的 API。如果没有,请切换到可以使用的库(例如 bcryptjs)或将回调包装在 Promise 中。无论您使用什么 ORM,同上。

最后,您的解析器应该如下所示:

resolve: function (parent, args,  req, res ) 
  const user = await User.findOne( email: args.email )
  if (user) 
    const isMatch = await bcrypt.compare(args.password, user.password)
    if (isMatch) 
      const cookieValue = user.owner ? 'owner' : 'buyer'
      res.cookie('cookie', cookieValue,  maxAge: 900000, httpOnly: false, path: '/' )
      return user
    
  
  // If you want an error returned in the response, just throw it
  throw new Error('Invalid credentials')

【讨论】:

太好了,我现在可以设置 cookie,它验证了我的登录。也感谢代码清理。 @jche 如果是同样的错误,你可能仍然在那里得到回调或未正确链接的 Promise。如果您已取出所有回调但仍无法弄清楚,请随时使用更新后的代码更新您的原始帖子,我可以看看。 是的,对不起,我忘了取出 res.status(200)。现在可以使用了,谢谢。

以上是关于GraphQL 突变“在将标头发送到客户端后无法设置标头”的主要内容,如果未能解决你的问题,请参考以下文章

为啥 GraphQl 说我忘记为突变发送参数 [重复]

在graphql中突变后查询

GraphQL:许多小的突变,还是一个大的突变?

CQRS 命令和 GraphQL 突变

GraphQL - 如何在查询多个突变期间检索先前突变的 id

使用输入对象的graphql突变