GraphQL API中的HTTP状态代码处理

时间:2020-01-14 08:05:12

标签: graphql http-status-codes

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

因为GraphQL可以在一个响应中返回多个响应,所以这很有意义。当用户在一个请求中请求两个资源并且仅有权访问第一个资源时,您可以发送回第一个资源,并为第二个资源返回一个forbidden错误。

但是,这只是我在阅读多个GraphQL库和博客文章的文档时想到的。 我在https://spec.graphql.org/https://graphql.org/此处的官方规范中都找不到有关HTTP状态代码的任何信息

所以我还有几个问题:

  1. 如果发生服务器意外错误,是否可以返回HTTP 500状态代码?
  2. 如果凭据错误,是否可以返回HTTP 401状态代码?
  3. 是否应该像这样在GraphQL响应的errors键中包含潜在 HTTP状态代码
{
  "errors" => [{
    "message" => "Graphql::Forbidden",
    "locations" => [],
    "extensions" => {
      "error_class" => "Graphql::Forbidden", "status" => 403
    }
  }]
}
  1. 我应该将诸如错误的字段名之类的常见错误与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 个答案:

答案 0 :(得分:16)

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

最终结果是客户端错误被强类型输入,并且很容易与最终用户不关心的其他错误区分开。采用这些约定有很多好处,但是最终由您自己决定是否适合您的服务器。