GraphQL API 中的 HTTP 状态代码处理

Posted

技术标签:

【中文标题】GraphQL API 中的 HTTP 状态代码处理【英文标题】:HTTP status code handling in GraphQL APIs 【发布时间】:2020-04-30 22:27:33 【问题描述】:

很多资源都说,GraphQL 应该始终以 200 状态码响应,即使发生错误:

https://www.graph.cool/docs/faq/api-eep0ugh1wa/#how-does-error-handling-work-with-graphcool https://github.com/rmosolgo/graphql-ruby/issues/1130#issuecomment-347373937 https://blog.hasura.io/handling-graphql-hasura-errors-with-react/

因为 GraphQL 可以在一个响应中返回多个响应,所以这是有道理的。当用户在一个请求中请求两个资源并且只能访问第一个资源时,您可以发回第一个资源并为第二个资源返回 forbidden 错误。

但是,这只是我在阅读多个 GraphQL 库的文档和博客文章的过程中发现的。 我在https://spec.graphql.org/ 或https://graphql.org/ 的官方规范中没有找到任何关于 HTTP 状态代码的信息

所以我还有几个问题:

    如果我遇到意外的服务器错误,是否可以返回 HTTP 500 状态代码? 如果凭据错误,是否可以返回 HTTP 401 状态代码? 我是否应该像这样在 GraphQL 响应的 errors 键中包含 潜在 HTTP 状态代码

  "errors" => [
    "message" => "Graphql::Forbidden",
    "locations" => [],
    "extensions" => 
      "error_class" => "Graphql::Forbidden", "status" => 403
    
  ]

    我是否应该将字段名称错误等常见错误与 HTTP 状态代码 400 Bad Request 匹配?

  "errors" => [
    "message" => "Field 'foobar' doesn't exist on type 'UserConnection'",
    "locations" => [
      "line" => 1,
      "column" => 11
    ],
    "path" => ["query", "users", "foobar"],
    "extensions" => 
      "status" => 400, "code" => "undefinedField", "typeName" => "UserConnection", "fieldName" => "foobar"
    
  ]

如果您能在 GraphQL 中处理 HTTP 状态代码时分享您的经验/资源/最佳实践,我会非常棒。

【问题讨论】:

【参考方案1】:

GraphQL 与传输无关。虽然 GraphQL 服务通常是通过 HTTP 接受请求的 Web 服务,但它们也可以并且确实接受通过其他传输的请求。事实上,GraphQL 服务可以在完全没有网络请求的情况下执行查询——它所需要的只是一个查询,以及一个可选的变量对象和操作名称。

因此,GraphQL 规范不关心方法、状态代码或任何其他特定于 HTTP 的内容(它只在讨论序列化时提到了 HTTP)。关于这些事情的任何实践充其量都是随着时间的推移而演变的约定,或者只是为 GraphQL 编写的一些原始库的产物。因此,对您问题的任何回答都将主要基于意见。

也就是说,因为你的 GraphQL 服务不应该关心它的查询是如何被接收的,可以说它的代码和任何处理接收请求并发回响应的代码之间应该是分开的(就像 Node.js 中的 Express 应用程序)。换句话说,我们可以说永远让您的解析器代码改变诸如响应的状态代码之类的东西。这是社区当前的想法,大多数库只返回两个代码之一 - 如果请求本身因某种原因无效,则返回 400,否则返回 200。

如果您的整个 GraphQL 端点受到某些身份验证逻辑的保护(例如您的服务器检查某些标头值),那么 GraphQL 请求可能会返回 401 状态。但这是我们在 Web 服务器级别处理的事情,而不是作为架构的一部分。如果您的 Web 服务器代码出现严重错误并且它必须返回 500 状态,或者位于您前面的 nginx 服务器返回 494(请求标头太大)等,也没有什么不同。

传统上,在执行过程中遇到的错误应该被抛出,仅此而已。 GraphQL 扩展可用于在收集和序列化错误时提供额外的上下文——错误的名称、堆栈跟踪等。但是,将 HTTP 状态代码包含在这些内容中几乎没有意义再次,错误与 HTTP 无关。这样做会不必要地混淆不相关的概念——如果你想识别错误的类型,最好使用GENERIC_SERVERINVALID_INPUT等描述性代码。

但是,有关错误处理的约定也在发生变化。一些服务希望更好地将客户端错误与其他执行错误区分开来。验证错误或其他将向最终用户显示的错误作为data 的一部分返回,而不是被视为执行错误,这种情况变得越来越普遍。

type Mutation 
  login(username: String!, password: String!): LoginPayload!


type LoginPayload 
  user: User
  error: Error

您可以使用 Shopify 等公共 API 来查看此类有效负载类型。这种方法的一个变体是利用联合来表示许多可能的响应。

type Mutation 
  login(username: String!, password: String!): LoginPayload!


union LoginPayload = User | InvalidCredentialsError | ExceededLoginAttemptsError

最终结果是客户端错误是强类型的,并且很容易与最终用户不关心的其他错误区分开来。采用此类约定有很多好处,但它们是否适合您的服务器最终取决于您。

【讨论】:

感谢详细的解释!我想我要切断控制器和 GraphQL 之间的界限:在执行任何 GraphQL 相关代码之前进行身份验证,因此我提供适当的 HTTP 状态代码,例如 401 unauthorized。除了严重的服务器错误500 之外,之后的所有内容都会得到 200 响应。让我们看看这是否值得

以上是关于GraphQL API 中的 HTTP 状态代码处理的主要内容,如果未能解决你的问题,请参考以下文章

GraphQL、react-apollo、Apollo 1,全局处理 200 HTTP 代码状态的 data.error 错误。不是网络一。

API设计: GraphQL

React Native 状态管理(Redux、Context API 或 Graphql)

Anilist api v2 GRAPHQL

GraphQL graphene-django 基本使用文档

源 GraphQL API:HTTP 错误 400 错误请求