如何设计 HTTP 接口异常状态的返回
上周五,我在做一个 HTTP 接口的迁移。刚好跟同事进行了一场关于 HTTP 接口的状态返回的讨论。很巧合地是,我们的观点互不相同,在他看来:
所有业务返回的响应状态码都应该是200,包含业务异常的响应状态码也是200,错误的消息放在 Body 里面描述;未到达业务层的请求,例如:5XX, 403 这样的错误应该算作是 “连接层” 的错误。
这样做的优点是:
对于 5XX, 403 这样的错误可以归纳为 “连接” 错误。可以统一处理?
客户端只需在接受到 200 的状态码时才做业务处理。
但是在我看来,这有一个很大的缺点:
对于业务异常无法在第一层的 status code 里面发现,需要进一步解析 body, 响应的 body 有 2 个 Schema, 即 error message 和业务响应实体的 schema。这样做的厂商其实很多,例如: LeanColud 的获取数据的接口。
GET /1.1/classes/<className>/<objectId>
在使用这个接口获取一个不存在的 objectId
时,响应的格式是:
Response Status Code: 200
Response Body: {}
这个接口把 className
与 objectId
都做成了 path parameter, 通常地,我们不应该用 404 来描述一个 URL 不存在的情况吗?
如果我要使用这个接口,我在客户端接收到响应之后大概要做以下的处理:
判断 http status code 是否是 200。
解析 http body 是否是一个 error message。
解析 http body 为业务实体。
判断解析后的业务实体是否是”空”。
所以,我的观点刚好相反:所有响应的状态要充分利用 http status code 来描述响应的异常情况, 并且配合 http body 来描述更详细的 error message。
我同事他认为这样做的缺点是:
http status code 是非常有限的,且不能扩展。
那么我们该怎么处理这种 http status code 表达能力不足的情况呢? 答案是: 使用 http status code 来代表某一个类型的异常,同时用 http response body 来描述更详细的异常消息。 例如:
Response Status Code: 400
Response Body:
{
"code": "missing_parameter_name",
"message": "请求中必须包含参数 name"
}
Response Status Code: 400
Response Body:
{
"code": "id_not_matched",
"message": "id 格式应该为: \w+"
}
上面的例子中我们使用了 400 来描述了一个 Bad Request
, 并且将更加详细的业务异常表达出来了! 来看看我如何改造 LeanCloud 的接口返回:
Response Status Code: 200
Response Body:
{
"objectId": "xxx",
"name": "这是一个业务实体"
}
Response Status Code: 404
Response Body:
{
"error": "resource_not_found",
"message": "无法在 className 下找打 objectId 资源"
}
总结一下我这种设计方式的优点:
可以根据 http status code 判断响应是否正常。
无需将一个 response body 适配多个 schema 解析。
客户端友好,无需过多包装 response body,在正常返回的情况下数据可以直接使用,无需拆箱。
总结
接口设计可以千变万化,可以说没有哪一种方案是完美的,我们在设计的接口的时候也要尽可能地站在使用者的角度考虑下面的原则:
客户端友好,响应的层次尽可能简洁。
数据不要过渡封装,最好能做到即拿即用。
接口名称简洁明了。
对于 HTTP 接口的更多实践经验可以查看我的文章 RESTful Best Practices