Django-rest framework开发:Restful 接口规范
REST简介 |
REST这个词,是Roy Thomas Fielding在他2000年的博士论文(Architectural Styles and
the Design of Network-based Software Architectures)中提出的。
Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。REST迅速取代了复杂而笨重的SOAP,成为Web API的首选风格。RESTful作为目前最流行的 API 设计风格,一定有着它独有的魅力:强大、简洁、易上手。
Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。如果一个架构符合REST风格,就称它为RESTful架构。
要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。 如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。
资源(Resources)
REST的名称"表征状态转化"中,省略了主语。“表征"其实指的是"资源”(Resources)的"表征"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。
表征(Representation)
“资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表征”(Representation)。
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表征"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表征"的描述。
状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表征之上的,所以就是"表征状态转化"。
客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
综合上面的解释,我们总结一下什么是RESTful架构:
-
每一个URI代表一种资源;
-
客户端和服务器之间,传递这种资源的某种表征;
-
客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表征状态转化"。
REST风格 |
url链接一般都采用https协议进行传输
用api关键字标识接口url
- https://api.baidu.com(应该尽量将API部署在专用域名之下。)
- https://www.baidu.com/api/(确定API很简单,不会有进一步扩展)
在url链接中标识数据版本
- https://api.baidu.com/v1/
- https://api.baidu.com/v2/
接口一般都是完成前后台数据的交互,交互的数据我们称之为资源。所以网址中不能有动词,只能有名词,而且所用的名词往往是复数形式。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
-
https://api.example.com/v1/zoos
-
https://api.example.com/v1/animals
-
https://api.example.com/v1/employees
特殊的接口可以出现动词,因为这些接口一般没有一个明确的资源,或是动词就是接口的核心含义
- https://api.baidu.com/place/search
- https://api.baidu.com/login
资源操作由请求方式决定
对于资源的具体操作类型,由请求方式来标识:
-
GET:取出资源(一项或多项)。
-
POST:新建一个资源。
-
PUT:更新资源(提供待改变的整体资源,类似整张表)。
-
PATCH:更新资源(提供待改变的部分属性,类似某个字段)。
-
DELETE:删除资源。
过滤信息
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。
下面是一些常见的参数
- ?limit=10:指定返回记录的数量
- ?offset=10:指定返回记录的开始位置。
- ?page=2&per_page=100:指定第几页,以及每页的记录数。
- ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
- ?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals
与 GET /animals?zoo_id=ID
的含义是相同的。
服务器返回响应状态码
- 200:请求成功
- 201:创建成功
- 301:永久重定向
- 302:暂时重定向
- 401:请求无权限
- 403:得到授权(与401错误相对),但是访问是被禁止
- 404:请求路径不存在
- 405:请求方法不存在
- 500:服务器异常
- 502:错误的网关,上游服务器响应无效
- 504:网关超时,上游服务器无响应
状态码的完整信息可参见Status Code Definitions
响应结果
-
响应数据要有状态码、状态信息以及数据本身
-
需要url请求的资源需要访问资源的请求链接
Hypermedia API
返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。
{"link": {
"rel": "collection https://www.example.com/zoos",
"href": "https://api.example.com/zoos",
"title": "List of zoos",
"type": "application/vnd.yourformat+json"
}}
上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问https://api.github.com/会得到一个所有可用API的网址列表。
{
"current_user_url": "https://api.github.com/user",
"current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
"authorizations_url": "https://api.github.com/authorizations",
......
}
从上面可以看到,如果想获取当前用户的信息,应该去访问https://api.github.com/user,然后就得到了下面结果。
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3/users/#get-the-authenticated-user"
}
上面代码表示,服务器给出了提示信息,以及文档的网址。
其他建议:
-
API的身份认证应该使用OAuth 2.0框架。
-
服务器返回的数据格式,应该尽量使用JSON,避免使用XML。
REST应用层级 |
假设我们在楼下的蛋糕店买蛋糕。“一个芝士蛋糕,一杯拿铁,两条吸管,谢谢”,我对前台的服务员说,然后我们找了个角落坐了下来。
Level 0 - 面向前台
“刚才我们向前台点了一杯拿铁,这个过程可以用这段文字来描述”,说着,我在纸上写下了这段JSON。
{
"addOrder": {
"orderName": "latte"
}
}
“我们通过这段文字,告诉前台,新增一笔订单,订单是一杯拿铁咖啡”,接着,前台给我们返回这么一串回复:
{
"orderId": "123456"
}
“那我们就等着前台喊‘订单123456的客户可以取餐了’,然后就可以开吃了!”
“哈哈,你真聪明,不过,在这之前,假设我们有一张会员卡,我们想查询一下这张会员卡的余额,这时候,要向前台发起另一个询问”,我继续在纸上写着:
{
"queryBalance": {
"cardId": "886333"
}
}
“查询卡号为886333的卡的余额?”
“真棒!接着,查询的结果返回来了”
{
"balance": "0"
}
“切,没钱…”
“哈哈,没钱,现在我们要跟前台说,这杯咖啡不要了”,我在纸上写到:
{
"deleteOrder": {
"orderId": "123456"
}
}
Level 1 - 面向资源
“现在这家蛋糕店越做越大,来喝咖啡的人越来越多,单靠前台显然是不行的,店主决定进行分工,每个资源都有专人负责,我们可以直接面向资源操作。”
比如还是下单,请求的内容不变,但是我们多了一条消息”
/orders
{
"addOrder": {
"orderName": "latte"
}
}
“/orders这个表示我们这个请求是发给哪个资源的,订单是一种资源,我们可以理解为是咖啡厅专门管理订单的人,他可以帮我们处理所有有关订单的操作,包括新增订单、修改订单、取消订单等操作”
“接着还是会返回订单的编号给我们”
{
"orderId": "123456"
}
“下面,我们还是要查询会员卡余额,这次请求的资源变成了cards”
/cards
{
"queryBalance": {
"cardId": "886333"
}
}
“接下来是取消订单”
/orders
{
"deleteOrder": {
"orderId": "123456"
}
}
Level 2 - 打上标签
“接下来,店主还想继续优化他的咖啡厅的服务流程,他发现负责处理订单的员工,每次都要去订单内容里面看是新增订单还是删除订单,还是其他的什么操作,十分不方便,于是规定,所有新增资源的请求,都在请求上面写上大大的‘POST’,表示这是一笔新增资源的请求”
“其他种类的请求,比如查询类的,用‘GET’表示,删除类的,用‘DELETE’表示”,添加类的,用‘POST’表示
“还有修改类的,修改分为两种,第一种,如果这个修改,是修改整个订单上的食物,那么用‘PUT’表示;第二种,如果这个修改,只是修改订单上某一种食物,比如是换一杯咖啡,那么这种请求用‘PATCH’表示
“来,我们再来重复上面那个过程,加一杯拿铁”
POST /orders
{
"orderName": "latte"
}
“请求的内容简洁多啦,不用告诉店员是addOrder,看到POST就知道是新增”
返回的内容还是一样"
{
"orderId": "123456"
}
“接着是查询会员卡余额,这次也简化了很多”
GET /cards
{
"cardId": "886333"
}
“这个请求我们还可以进一步优化为这样”
GET /cards/886333
“没错,接着,取消订单”
DELETE /orders/123456
Level 3 - 完美服务
为避免顾客不知道操作方式,店长决定,顾客下了单之后,不仅给他们返回订单的编号,还给顾客返回所有可以对这个订单做的操作,比如告诉用户如何删除订单。现在,我们还是发出请求,请求内容和上一次一样”
POST /orders
{
"orderName": "latte"
}
“但是这次返回时多了些内容”
{
"orderId": "123456",
"link": {
"rel": "cancel",
"url": "/order/123456"
}
}
“这次返回时多了一项link信息,里面包含了一个rel属性和url属性,rel是relationship的意思,这里的关系是cancel,url则告诉你如何执行这个cancel操作,接着你就可以这样子来取消订单啦”
DELETE /orders/123456
上面讲的Level0~Level3,来自Leonard Richardson提出的Richardson Maturity Model:
Level0和Level1最大的区别,就是Level1拥有了Restful的第一个特征——面向资源,这对构建可伸缩、分布式的架构是至关重要的。同时,如果把Level0的数据格式换成Xml,那么其实就是SOAP,SOAP的特点是关注行为和处理,和面向资源的RESTful有很大的不同。
Level0和Level1,其实都很挫,他们都只是把HTTP当做一个传输的通道,没有把HTTP当做一种传输协议。
Level2,真正将HTTP作为了一种传输协议,最直观的一点就是Level2使用了HTTP动词,GET/PUT/POST/DELETE/PATCH…,这些都是HTTP的规范,规范的作用自然是重大的,用户看到一个POST请求,就知道它不是幂等的,使用时要小心,看到PUT,就知道他是幂等的,调用多几次都不会造成问题,当然,这些的前提都是API的设计者和开发者也遵循这一套规范,确保自己提供的PUT接口是幂等的。
Level3,关于这一层,有一个古怪的名词,叫HATEOAS(Hypertext As The Engine Of Application State),中文翻译为“将超媒体格式作为应用状态的引擎”,核心思想就是每个资源都有它的状态,不同状态下,可对它进行的操作不一样。理解了这一层,再来看看REST的全称,Representational State Transfer,中文翻译为“表征状态转化”,是不是好理解多了?
Level3的Restful API,给使用者带来了很大的便利,使用者只需要知道如何获取资源的入口,之后的每个URI都可以通过请求获得,无法获得就说明无法执行那个请求。
现在绝大多数的RESTful接口都做到了Level2的层次,做到Level3的比较少。当然,这个模型并不是一种规范,只是用来理解Restful的工具。所以,做到了Level2,也就是面向资源和使用Http动词,就已经很Restful了。Restful本身也不是一种规范,我比较倾向于用“风格"来形容它。如果你想深入了解Level3,可以阅读《Rest in Practice》第五章。
推荐阅读
-
Django-rest framework开发:Restful 接口规范
-
Spring Boot +mybatis +oracle开发restful风格的查询接口
-
Spring MVC风格的restful接口开发
-
SpringBoot Web开发——构建Restful风格接口
-
Spring MVC风格的restful接口开发
-
Yii2 基于RESTful架构的 advanced版API接口开发 配置、实现、测试,yii2restful
-
高性能web服务器框架Tornado简单实现restful接口及开发实例
-
高性能web服务器框架Tornado简单实现restful接口及开发实例
-
Spring MVC风格的restful接口开发
-
Yii2 advanced版API接口开发 基于RESTful架构的 配置、实现、测试