GraphQL Resolver抛出错误时重定向

7
我正在使用graphql-server-express构建一个GraphQL服务器,该服务器消耗REST API。 我遇到的情况是当用户未经身份验证即无权访问资源时,REST调用可能会返回301或401状态代码。 我在解析GraphQL查询时,在客户端设置了一个cookie并将其转发到REST API。
当出现此类错误时,是否可以通过对GraphQL端点的调用发送301重定向给客户端?
我尝试过像res.sendStatus(301)这样的内容放在formatError中,但这样做效果不佳,因为graphql-server-express会尝试在此之后设置标头。
我还尝试过使用以下内容截断graphqlExpress中间件:
export default graphqlExpress((req, res) => {
  res.sendStatus(301);
  return;
});

虽然客户端收到了正确的结果,但服务器仍然会打印错误信息(在这种情况下是 TypeError: Cannot read property 'formatError' of undefined – 很可能是因为中间件接收到了空选项)。

有没有一种好的方法来解决这个问题?谢谢!


重定向到哪里?在GraphQL中重定向并没有太多意义。如果客户端发送了一个带有两个根节点的查询,其中一个成功(无需登录),而另一个失败了,你会发送什么,300还是200? - Ruslan Talpa
如果至少有一个字段失败,应该发送重定向。 - amann
但是GraphQL查询是由JS代码执行的,而不是直接由浏览器执行,仅仅发送重定向并不意味着浏览器会加载登录页面。您的GraphQL客户端需要检测此响应代码,然后硬编码以在发生时执行某些操作。 - Ruslan Talpa
最好的做法是在你的“json错误”响应中发送相同的信息,让客户端对其做出反应。改变响应代码对你没有任何帮助。 - Ruslan Talpa
你说得对。我有些误以为301状态码会导致客户端更改位置,但例如fetch只是跟随重定向并加载该资源。所以可能最好的方法就像你提到的,在我的解析器中抛出自定义重定向错误,客户端知道如何处理 - 也许使用apollo client afterware(在链接的示例中,他们实际上正在使用响应的状态代码来响应注销)。 - amann
2个回答

9
这是我实现它的方法。
在服务器端:
// Setup
export default class UnauthorizedError extends Error {
  constructor({statusCode = 401, url}) {
    super('Unauthorized request to ' + url);
    this.statusCode = statusCode;
  }
}

// In a resolver
throw new UnauthorizedError({url});

// Setup of the request handler
graphqlExpress(async (req, res) => ({
  schema: ...,
  formatError(error) {
    if (error.originalError instanceof UnauthorizedError) {
      res.status(error.originalError.statusCode);
      res.set('Location', 'http://domain.tld/login');
    } else {
      res.status(500);
    }

    return error;
  },
});

在客户端:

const networkInterface = createNetworkInterface();

networkInterface.useAfter([{
  applyAfterware({response}, next) {
    if ([401, 403].includes(response.status)) {
      document.location = response.headers.get('Location');
    } else {
      next();
    }
  }
}]);

在Apollo Client 2.0中,你可以在客户端使用apollo-link-error来处理这个问题。

1

在GraphQL解析器中处理重定向的另一种方法是将“状态”设置为302(重定向的HTTP状态代码),并在响应中设置“位置”,如下所示的代码:

this.Query = {
  downloadFile: (parent, { url }, { res }) => {
    res.status(302);
    res.set('Location', url);

    return;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接