Restful API 设计实践

前言

第一次接触Restful API设计规范是在本科的软工课上,当时懵懵懂懂的跟着助教的指导了解了Restful API的一些内容。后来在实验室产品的开发中第一版后端的API设计被同学吐槽不够Restful。由于接下来一段时间要做后端的开发,所以趁机把Restful API的设计准则认真研究了下,发现好多都是我们之前踩的坑,所以今天把这个内容总结以下,以防之后继续踩坑。

Restful API规范主要是为了能让我们设计的API易于用户理解和掌握。其准则大致分为三大部分: URI的设计,状态码的正确使用和服务器的Response限定。需要在最开始最开始指出的是:在Restful规范里面,不提倡像如下这样,发生了错误仍然返回200,却把错误信息放在body里面。

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "failure",
  "data": {
    "error": "Expected at least two items in list."
  }
}

其实这个问题我们的项目里大量的存在(羞愧),而且我发现貌似不少公司也是这么干的==,都是一律返回200,然后自定义错误码来进行处理。至于对不对完全是实际应用说了算,只能说这样做不是完全符合Restful的规范。关于这个问题,我们在文章的最后加入了专门的讨论。

接下来我们按照三部分进行介绍,其中说到一些点的时候,我也会介绍下我们项目中的中招血泪史。

URI设计

常用动词及含义

Restful API的设计是以资源为中心。URI应当只表示资源,而对资源的操作由不同的动词来进行区分。比如 GET /articles 就是对 articles这个资源进行GET操作。常用的动词如下及含义如下:

GET:读取(Read)
POST:新建(Create)
PUT:更新(Update)
PATCH:更新(Update),通常是部分更新
DELETE:删除(Delete)

URI中不要包含动词

由于URI只表示资源,所以最好不要在URI中包含动词,尽量只包含名词。比如,创建一篇文章,不要用

POST: /articles/createNewArticle/

而是采用如下的形式,表示对article这个资源的新建操作

POST: /articles/

URI中资源名词为复数

URI表示资源,那么我们对资源的操作可能是单个单个的操作,也可能是批量的操作。所以统一使用复数会不容易产生误解。比如我们对article资源的操作有如下。用articles可以统一的表示两种情况。

// 获取某一篇文章
GET /articles/?author_id=2&article_name=book
// 获取该作者所有文章
GET /articles/?author_id=2

Don't nest resources

这个有的人翻译为不要多级嵌套API,但是我感觉好像英文表述更加的清晰一些。就是不要在一个URI中涉及到多个资源。比如我们想获取author_id为12的作者的所有文章,resource nested的API设计可能如下。里面涉及到了author这种资源表示,也涉及到了articles这种资源表示。

GET /author/12/articles/

一个更加Restful的设计如下。时刻记住我们以资源为中心去定义URI,而不是以我们要完成的操作为中心去定义URI。

GET /articles/?author_id=12

Handle trailing slashes gracefully

即URI最后的“/”一定要统一,要么都有,要么都没有!!这点我体会太深了,之前我就犯过这个问题,当时其他的URI最后都有"/",但有一个URI的最后少一个“/”, 导致前端的同学死活访问不了,但是查看文档,前端使用又是对的。看路由部分的源码才发现最是URI最后少了一个"/" TAT。

GET /articles/
GET /authors/2/

或者

GET /articles
GET /authors/2

擅用查询字符串

我们用名词来表示资源,用动词来表示对资源的操作。但是很多时候,我们要对资源进行更复杂的操作,比如我们对author_id=12的作者的一篇文章进行更新,那么我们应该擅用查询字符串来用作filter。

PUT /articles/?authord_id=12&article_name=book

正确使用状态码

服务器响应时,包含状态码和数据两个部分。我们首先要正确使用各类状态码。状态码主要分为五大类:

1xx:相关信息
2xx:操作成功
3xx:重定向
4xx:客户端错误
5xx:服务器错误

这五大类包含了100多种状态码,覆盖绝大部分常见情况,为了不困惑用户,我们应当尽可能精确的返回状态码。常用的状态码有如下几类:

2xx状态码

GET: 200 OK
POST: 201 Created
PUT: 200 OK
PATCH: 200 OK
DELETE: 204 No Content

201表示该资源已经被创建,204表示该资源已从服务器上消失。此外还有个202状态码,表示服务器已经接收到请求,但还未处理结束,通常用于异步的操作。

4xx状态码

4xx状态码主要表示客户端错误,常用的有如下几类:

400 Bad Requests: 服务器不理解客户端请求,未做任何处理
401 Unauthorized:用户未提供身份凭证依据,或者用户未通过身份认证
403 Forbidden: 用户通过了身份认证,但是不具备访问资源的权限
404 Not Found: 所请求的资源不存在,或者不可用
405 Method Not Allowed: 用户已经通过了身份认证,但是访问的Http方法不属于他的权限范围内
410 Gone: 所请求的资源已从这个地址转移,不再可用
415 Unsupported Media Type: 客户端要求的返回格式不支持。比如客户端要求xml返回,但是服务器端只支持Json返回
422 Unprocessable Entity: 无法处理用户上传的附件,导致请求失败
429 Too Many Requests: 用户的请求次数超过限额

5xx状态码

5xx状态码通常用于表示服务器的错误,但是我们一般不向用户透露过多服务器的错误信息,所以基本只需要两个状态码就够。

500: 客户端请求有效,但是服务器处理时发生意外
503: 服务器无法处理请求,一般处于网站维护状体

服务器Response

不要返回纯文本(plain text)

尽可能返回Json格式的内容,同时要在header里面指定content-type是application/json

不要在处理错误的时候返回200

敲黑板!!像下面这样是不符合Restful规范的,即一律返回200,把自定义状态放在body里面。

HTTP/1.1 200 OK
Content-Type: application/json

{
  "status": "failure",
  "data": {
    "error": "Expected at least two items in list."
  }
}

符合规范的设计应该是下文所示,状态码返回正确,而把放错误的detail在body里面。

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "Invalid payoad.",
  "detail": {
     "surname": "This field is required."
  }
}

提供一个所有API的说明链接

这个的确挺方便用户的,一般我们都用API文档来进行所有API的说明,但是最好能提供一个链接来展示所有的API。比如github的api.github.com链接中就列出了所有的API及说明。

关于实际使用中的返回码问题讨论

这篇文章发出去之后,关于请求错误时到底是按照Restful的准则去返回特定的返回码还是返回200,然后在body里写入自定义的错误码引起了一番讨论。实际上在生产实践中,后者使用的确更多一些。因为本身标准的状态码含义太过宽泛,如果是对外部大众用户,那其实还好,因为比较统一,而且我们也不想对外过多的暴露错误信息细节。但是如果是面向开发者使用的,那用标准码就不太利于开发者定位错误。关于这个问题,我觉得如下的这篇回答说的比较明白:

https://www.zhihu.com/question/310737821/answer/585641618​www.zhihu.com图标

即严格区分开系统错误业务错误。系统错误用标准码,如400,401,403, 404,500,502,503等,都保留给“系统错误”。而业务错误,可以用统一的一个码,比如599.然后在header/body里面自定义业务错误码(貌似一般放在body里面比较多)。所以一个业务错误的返回应该长这样。使用同一个一个码599主要是为了方便监控,这样我们只要碰到599,我们就知道这是业务错误,碰到其他码,我们就知道是系统错误。

600                              <-- Status Code,表示这是一个业务错误,监控看这里
X-Biz-Err: ERR_USER_NOT_FOUND    <-- 这是一个自定义的表达业务错误代码的header
{                                <-- 返回体,json格式
  "msg": "找不到该用户"            <-- 对于一个错误,保证总有个错误信息向用户展示
  "data": { ... }                <-- 如果有必要,附带一些前端需要向用户展现的报错的其他数据
}
#或者把错误码放在body里面
600                              <-- Status Code,表示这是一个业务错误,监控看这里
{                                <-- 返回体,json格式
  "error_code": "ERR_USER_NOT_FOUND"    <-- 这是一个自定义的表达业务错误代码的header
  "msg": "找不到该用户"            <-- 对于一个错误,保证总有个错误信息向用户展示
  "data": { ... }                <-- 如果有必要,附带一些前端需要向用户展现的报错的其他数据
}
作者:大宽宽
链接:https://www.zhihu.com/question/310737821/answer/585641618
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

而Restful的命名规范在实际中还是采纳的比较多的。

总结

总结而言,Restful API的设计规范里强调,URI表示资源,对资源的操作用GET/POST/PUT/PATCH/DELETE五个动作表示,同时要正确的使用返回码,以及在URI设计的时候一定要保持一致(比如最后的"/")。这样设计出来的API能够更加容易被用户理解和使用。把这篇记录在这里,防止之后项目里再次踩坑。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值