Code 状态码码是接口设计中的常见概念,本文主要讨论接口开发中 Code 码设计。从客户端和服务器端开发的角度,给出具体的工程实践建议和思考。
本文从笔者之前的一份接口文档定义开始说起,文档中定义的服务端接口输出格式如下
接口输出格式
返回数据由两部分构成,第一部分是对结果集的说明,第二部分是 data 节点
{
"code": 4302,
"message": "no sign",
"time": 1487832032,
"data": []
}
第一部分,无论错误与否,都会有如下片段。
code:信息代号
message:信息描述
time:接口返回时间
第二部分是具体数据如下:
data 节点
我们可以 看到 code=4302,4302 并不是一个 HTTP 协议状态码,而是一个业务状态码,是业务领域的含义,并非我们常见的 HTTP 协议层面的响应状态码。
业务状态码与 HTTP 状态码
在 REST 接口设计规范中,我们通常都会被引导为这里的 Code 应该是 HTTP 协议状态码 200,404 或者 501 等。
实际上这是实践中的一种折中的方式,Code 会包含 HTTP 状态码和业务状态码
业界为什么会有这种实践,与客户端的解析数据方式有很大关系,下文中会给出答案。
说到这里,我们引出了两个概念,一个是业务状态码,一个是 HTTP 请求状态码。
两个概念很好理解
业务状态码
状态码对应.jpg业务状态码是服务端给出的关于业务描述的码,用于客户端明确得知本次请求的资源的状态情况。上文例子中的 4032 被认为是一个缺少签名 sign 的业务状态码。有业务状态码输出表明当次 HTTP 请求是通的。
业务状态码是可变的,没有业界标准,是一种资源状态描述,与 HTTP 响应状态码也不存在对应关系。
如下文图片 HTTP-200 显示,接口是通的 HTTP 状态响应返回 200,但是业务没有执行成功,code 用 1 表示。
HTTP-200.pngHTTP/1.1 200 OK
Server: nginx
Date: Wed, 13 Nov 2019 01:27:03 GMT
Content-Type: application/json; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/5.6.15
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, PUT, GET, DELETE, OPTIONS
Access-Control-Allow-Headers: token, app-key, content-type, etcp-base
Access-Control-Max-Age: 86400
X-Frame-Options: SAMEORIGIN
X-Content-Type-Options: nosniff
{
"code": 1,
"message": "states is wrong",
"data": []
}
HTTP 状态码
HTTP 请求状态码是 HTTP 协议的一部分,用于表明 HTTP 响应状态。
rest响应401.pngHTTP 状态码是 HTTP 协议的工程实现,不符合协议规定的服务器端实现,我们可以认为 服务器的 HTTP 实现是错误的。
这里举一个简单的幂等性例子,我们知道 DELETE 方法是幂等的,如果之前已经删除过特定的资源,再次请求时也应该返回 200 的响应码,而不是 404 资源不存在的响应。
服务器端的开发实践
为什么上文中着重介绍状态码的两种分类,因为在业界开发中,这两种码会交叉使用,都有具体的使用场景,语义上不应该被混淆。
这里抛出几个问题
如何用 Code 码表明此次访问是连接成功的
如何用 Code 码表明此次访问达到了客户端预想的结果
客户端应该先接收 HTTP 状态码还是业务状态码
客户端 HTTP 请求
先对本文中的客户端做一个简单定义,即调用服务器端接口的调用者,主要是前端 WebView,安卓和 iOS 工程师,统称大前端。前端 WebView 的请求会涉及到跨域 CORS
其实简单来说,客户端工程师最关心两个问题:
第一,接口有没有通。
第二,接口有没有返回我想要的数据。
有经验的客户端工程师会关心接口如果不通,返回提示是否可以指导我排除错误,或者说跟踪到问题所在。接下来接口设计是否合理,是否有隐患,就看工程师职业水平和职业素养了。
客户端排除法
客户端 HTTP 请求的通用方法是采用排除法,什么是排除法,客户端在请求服务端的 REST 接口时,会先在网络层面判断接口是否通,包括 404 或者 200。客户端只关心本身有用的 Code,其余都按异常处理。
网络层判断这个任务客户端会交给具体的 HTTP 拦截器 (Intercept),之后才会接受当次接口的描述信息也就是 data 和 code,做业务前端处理。
axios 就是一个主要用于浏览器请求的 HTTP 客户端,包含请求响应拦截器(Intercept request and response)
Promise based HTTP client for the browser and node.js
以下代码是两段响应拦截,分别是拦截 HTTP 协议的 401 验证不通过和自定义业务代码的验证不通过。
HTTP 401
axios.interceptors.response.use(function (res) {
return res.data;
}, function (res) {
let response = res && res.response || {};
if (response.status == 401) {
tool.showToast('登录已过期,请重新登录。');
tool.removeReUserInfo();
location.hash = "#/login";
} else {
tool.showToast('请求数据失败,请稍后再试。');
}
});
自定义业务代码
axios.interceptors.response.use(function (res) {
Indicator.close(); //首先关闭所有的提示窗口
var data = res.data;
if (data.code == 4034) { //签名不合法
tool.showToast('签名不合法。');
} else if (data.code == 4033) { //token失效
tool.showToast('登录已过期,自动登录中。');
tool.removeUserInfo();
location.hash = "#/login";
} else {
return data;
}
}, function (err) {
// alert(JSON.stringify(err));
Indicator.close(); //首先关闭所有的提示窗口
tool.showToast('请求数据失败,请稍后再试。');
});
安卓客户端拦截器
okhttp 是一个安卓平台的 HTTP 客户端,其中包含一个网络拦截器(Network Interceptors)。网络状态码和业务状态码的截取都交给拦截器处理处理。
图片.png设计倡导
这里我们重新梳理之前提出的三个问题,给出一些解决思路,同时总结一些经验
如何用 Code 码表明此次访问是连接成功的?
这里应该以 HTTP 状态码为依据,主要有 200, 401 ,表明请求是【触碰到关于的数据处理的业务部分了】如
HTTP/1.1 200 OK
{
"code": 0,
"message": "客户端已是最新版本",
"data": {
"code": 10
},
"debug_stack": []
}
HTTP/1.1 401 Unauthorized
Server: nginx
{
"name": "Unauthorized",
"message": "Token is expired",
"code": 401,
"status": 401,
"type": "yii\\web\\HttpException"
}
如何用 Code 码表明此次访问达到了客户端预想的结果?
这里以业务状态码的数据为依据,获取到的就是真实的。Code 可以用 0 表示。
{
"code": 0,
"message": "客户端已是最新版本",
"data": {
"code": 10
},
"debug_stack": []
}
客户端应该先接收 HTTP 状态码还是业务状态码?
当然是先接收 HTTP 状态码,其次是业务状态码,不混淆,也不能混淆。从软件分层的角度来说,接收 HTTP 状态码在接收业务状态码的上层,通常由拦截器来做,比如 token 过期的 401 阻挡。
一般情况下,0 表示成功,1 表示业务操作失败。业务复杂时,需要维护多种业务状态码。下图是微信平台的业务状态码枚举,场景较多。
微信错误码.png接口字段整齐
这里所说的字段整齐是指服务提供方给到的数据结构是完整的,最通用的,现在大部分接口格式如下
三个字段应该都存在,可以为空,避免 NULL。
{
"code": 200,
"data": null,
"message": "成功"
}
对于提供接口开发的服务者而言,code 和 message 字段都会给出,存在异议的字段是 data。更严谨的说法是 请求的资源描述中伙包含资源状态编码和描述信息,如 message。
当 data 没有数据时,有的工程师喜欢把 data 置为 null,或者直接不返回 data 字段。这两种方式都不合理,都会增加调用方的判断成本,尤其是 null,如果调用方写法不严谨的话,很容易引发程序异常。
接口总会有返回值,data 字段就是实际的返回值,可以是空字符串,空数组,bool 类型。
图灵奖得主 Tony Hoare 曾经公开表达过 null 是一个糟糕的设计,null 总是代表着不确定性。
null.jpeg业务状态码不等于异常
业务状态码和异常是两个概念,切忌混淆。业务状态码指正常的业务处理结果的显示说明,而异常通常由于语法错误,数据缺失造成程序不能正常执行完成。不能通过业务状态码而屏蔽异常。
总结
本文从接口文档开始,引出了状态码的概念,细分为网络状态码和业务状态码。结合服务器端和客户端的编程角度,介绍了各自的使用场景。在分布式服务化的网络架构中,清晰的网络状态码和业务状态码有助于服务链路的跟踪和服务的链路跟踪,尤其是异常的定位和捕获。业务状态码应该趋于同一化,与网络状态码相互补充。
end 2019 年 11 月
参考文档中给出一些资源,有兴趣的读者可以参考阅读。
网友评论