文档章节

一种RESTful接口的约定

scheung
 scheung
发布于 2017/08/02 00:06
字数 3500
阅读 5
收藏 2

1 概述

1.1 撰写目的

本文用于定义一种统一的RESTful接口设计方案,希望具有参考价值。本文所描述的方案比较学院派,在上一家公司提出没有被采纳,在所了解到的有限的若干家声称采用了RESTful风格的公司里,发现他们也偏离甚远。当然,他们这么做是有理由的,我也理解,这只是取舍问题。这篇文章其实是旧文了,2016年年底就已经写好,但是一直躺在电脑的硬盘里,不想白费了当时的功夫,因此在此公开。

1.2 为什么采用REST

目的是为了服务端与客户端的解耦。SOA仅仅是从结构上将前后端分离,但是实际上数据逻辑还是没有实现解耦,服务端接口升级往往会影响客户端,两者的行为需要严格约定。而REST采用HTTP协议进行约定,客户端仅仅需要按照HTTP协议来理解服务端返回的数据,虽然与业务相关的数据结构还是需要约定,但是这确实进一步解耦了服务端与客户端。

另外,由于严格遵照HTTP协议进行数据返回,对于安全的接口,可以在返回的Header里设置缓存策略(接口安全性的概念在下文会解释)。

1.3 文档结构

第二部分将阐述关于RESTful的若干个关键的概念,明确第二部分阐述的几个概念有利于设计、实现优雅规范的接口。

第三部分就URL命名的问题进行约定。

第四部分对消息实体进行约定。

第五部分对『向RESTful接口发起请求』进行阐述,约定要实现的方法,约定请求的头部和body的格式。

第六部分对接口的响应格式进行约定,包括响应消息的头部、状态码、JSON实体。

第七部分对版本控制的问题进行约定。

第八部分对RESTful接口的实现提出了实现工具的建议。

2 关键概念

明确一些关键的概念是很重要的,虽然RESTful风格的API设计方案并没有统一的标准,但是还是需要符合一定的原则进行设计,否则就不能称为RESTful风格的API。因为许多人并没有对REST进行充分的了解就宣称自己的API是RESTful风格的API,以至于RESTful的提出者Fielding博士本人无法忍受,在2008年为此专门写了一篇博客『REST APIs must be hypertext-driven』,hypertext-driven与HATEOAS是同一个概念的不同表述,在下文会进行阐述。

2.1 RESTful

REST不是一种协议,也不是一种文件格式,更不是一种开发框架。它是一系列的设计约束的集合:无状态性、将超媒体作为应用状态的引擎等。REST是Representation State Transfer的缩写,中文是『表述性状态转移』,这里就涉及到资源的表述与状态两个概念。

简单地说,资源可以看作是服务器上存储的所有数据,资源的表述则是服务器对外提供的指向这些资源的方式,使用JSON、XML等均可,一个资源可以有多种表述;资源的状态则是服务器的数据存储状态,例如在t时刻,服务器中存储了m条数据,这时候客户端向服务端提交了一个创建数据的请求,服务器处理了此请求并创建了一条数据,那么在t+1时刻,服务器中就存储了m+1条数据,这两个时刻的资源状态就是不一样的,t时刻发生的请求导致了资源状态的改变。

2.2 HATEOAS

Hypermedia As The Engine Of Application State,超媒体作为应用程序状态的引擎。这是REST区别于其他SOA风格的主要特点。客户端与服务端进行互动的时候,完全是通过服务端动态提供的超媒体进行的。除了对超媒体的一般理解,客户端不需要知道其他额外的知识。相反,在一些SOA接口的设计中,客户端与服务端的通信是要事先进行约定的,例如通过文档或者接口描述语言(Interface Description Language, IDL)。而基于HTTP协议的REST设计里,一般采用的就是请求与响应的Header来体现HATEOAS原则(具体请参考:https://en.wikipedia.org/wiki/HATEOAS)。这里也隐含这样一层含义:REST应尽可能地利用HTTP标准中现有的东西,例如Header、标准方法与状态码。

从标准的角度看,HTTP标准是一项RFC标准,世界认可;而其他自定义的SOA标准则可能是一项个人标准或者公司标准,最多是一项互联网草案(这对大部分公司来说都不可能),而一项标准越是被广为认可接受,其实现的通用性就越强。个人标准和公司标准都五花八门,这样对每一个标准都要参照其相关文档实现相应的行为逻辑是很麻烦的。

2.3 安全性

一个方法被调用1次与被调用0次是一样的,此方法就是安全的,否则就是不安全的。例如,一个方法A仅仅是读取数据,并不创建或者修改数据,不论A方法被调用多少次,都不对数据记录产生任何影响,A方法是安全的。而假如有另一个方法B对数据进行删除,B方法被调用1次后,数据会被删除(或者标识位被修改),系统里的数据发生了变化,那么B方法是不安全的。

2.4 幂等性

一个方法被同样地调用1次与被调用多次是一样的,即同样的输入会得到同样的输出,此方法就是幂等的,否则就不是幂等的。

2.3节中A方法与B方法都是幂等的,一个安全的方法一定是幂等的,一个幂等的方法不一定是安全的。

假设一个方法C对某个全局计数器执行自增操作并写入数据库,每次调用C方法都会对系统数据产生影响,那么C方法就不是幂等的。

3 URL命名

URL用于标识资源,因此URL应该以名词进行命名,例如/users, /users/children等。

一般URL会内嵌参数,例如要获取id为313的user的信息,那么URL应该为/users/313,前面的user采用复数,如果要列出其所有后代,则URL应为/users/313/children,children为复数形式,如果要获取其id为499的后代,则URL应为/users/313/children/499

4 消息实体

消息实体,就是请求和响应消息中的entity-body(也称为body),消息实体采用JSON字符串格式。

5 请求

5.1 方法

使用HTTP标准定义的请求方法。

5.1.1 get

获取资源,单个参数一般写在URL上,多个参数则作为query parameter附在URL后面,例如:

  • 单个参数:/user/123, 表示id为123的user
  • 多个参数:/user?name=tom&phone=13787890987&gender=male

get方法应为幂等的,并且不对数据记录产生影响。对于汉字与特殊字符,应该进行urlencode。

5.1.2 post

创建资源,请求的headers里设置Content-typeapplication/json,参数为json类型。

根据约定,在创建成功之后,返回的状态码应该是201(Created),并且在response的Header里设置Location为新创建的资源的URL,例如,创建了一个新的user,该user创建后id为888,那么Header里应该设置Location/users/888,当然,这应该是一个完整的URL,这里只是给出了一个相对路径的URI以作为说明。返回了这些数据后,客户端可以自定义后续行为,或者查看创建后的user,或者刷新当前的user列表,这些行为服务端并不关心。

如果重复提交了相同的数据,第一次应该返回201,以后则应返回409(Conflict),并且在response的Header里设置Location指向已经存在的资源,说明冲突的来源。

5.1.3 put

更新资源,对现有资源进行修改,请求的headers与post一样,参数也是。此方法应该是幂等的。

5.1.4 delete

删除资源。此方法应是幂等的。

5.2 Header

Content-type应设为application/json。

另外应设置一个version,指明所使用的接口版本。这不属于HTTP协议中的一部分,是自定义的,出于版本控制的考量,具体见第七章。

5.3 body

采用JSON字符串,具体的结构有待商定,这不属于HTTP协议的一部分,是自定义的。

这里主要放置业务相关的数据。

6 响应

6.1 Header

根据响应的状态码不同,相应地设置头部,具体见下一节。

但是在我所了解的公司里,做法都是统一返回200,然后在返回的JSON字符串里设置消息码。我是不能理解的。据一位前端同学说,前端代码接收到了请求以后,不方便获取Http状态码。其实我也写过前端,不深入,但是一些基本的知识还是有的,我觉得这并不难做到,估计是他的代码封装的时候没有考虑到这一点,现在要改比较麻烦,所以不想大动干戈、伤筋动骨。

6.2 状态码

状态码语义使用场景
200OK正常返回消息,什么问题也没有
201Created创建资源成功,Header里应设置Location指向新创建的资源
202Accepted请求已被接收,但是处理过程较长,不能马上返回结果
304Not Modified没有任何修改发生
401Unauthorized缺乏权限,指已经登录但是缺乏请求这个资源的权限
403Forbidden拒绝访问,可用于未登录时拦截返回的状态码,此时Header里应设置Location为登录页面的URL
404Not Found不存在所请求的资源
406Not Acceptable请求没有被接收,参数约束校验不通过,或者其他业务类型的错误都可以返回这个状态码,response的body里应有表示错误信息的JSON实体。
409Conflict请求的资源有冲突,例如多次提交一样的创建请求,response的Header里应设置Location为产生冲突的资源的URL
500Internal Server Error服务器的非业务类错误,response的body里应有表示错误信息的JSON实体

6.3 body采用JSON字符串。

JSON的结构分为两种:成功、失败。

一般而言,只有返回200的时候才需要读取成功的JSON,只有返回406和500的时候才需要读取失败的JSON,对于其他的状态码,客户端不需要服务器提供额外的消息。

对于成功的JSON,里面应该只包含一个result对象,而失败的JSON应该使用这样的结构:

{
    error: {
        code: xxx,
        message: "xxx",
        data: {...}
    }
}

失败的JSON只有一个error对象,包含错误码、消息及相关数据,message应该是直接可读的消息,客户端毋需理解发生了什么错误,客户端只需将消息展示出来即可。在收到406的时候,客户端只需知道发生的错误是由客户端造成的即可,具体是什么类型并不需要知道,将消息直接展示出来,让使用的人知道是什么即可,所以message应该是人类可以理解的文本。同理,收到500的时候,只需知道这个错误是服务端的问题即可,客户端也毋需知道具体的错误类型,最多就将错误码和消息展示出来,让使用者有反馈的依据即可。

7 版本控制

考虑到接口有可能升级,升级的类型有几种:

  1. 新增功能接口
  2. 原有接口返回数据增加字段
  3. 现有接口返回数据变更现有字段格式或删除现有字段
  4. 现有接口变更业务逻辑
  5. 删除接口

其中,前两种升级并不会影响客户端,因此毋需处理。而后面三种会导致使用旧接口的客户端不能正常工作。

一般服务端升级与客户端升级都不是同步的,客户端升级往往会滞后,因此在服务端升级后应该保留旧版本的接口继续运行一段时间,让未升级的客户端可以继续工作一段时间,同时可以上线新版本的客户端。过一段时间后再将旧版本的接口下线。

而版本控制应该是向下兼容的,即假设当前版本是1.2,如果客户端请求1.3版本的服务,应当用当前版本提供服务。如果没有注明请求的版本号,应当提供当前版本的服务。

一般情况下,客户端请求需要带版本号,但是服务端并不需要对此进行处理,除非是同时运行新旧版本的同一个接口,才需要做差异处理。

8 实现

8.1 Spring HATEOAS

Spring HATEOAS可以很方便地与Spring MVC结合来开发RESTful接口。具体参照其文档: http://docs.spring.io/spring-hateoas/docs/0.20.0.RELEASE/reference/html/#fundamentals.jaxb-json

原文链接: https://bungder.github.io/2017/07/24/REST/

我的技术博客: https://bungder.github.io

© 著作权归作者所有

共有 人打赏支持
scheung
粉丝 1
博文 1
码字总数 3500
作品 0
广州
RESTful 接口实现简明指南

在前后端分离的 Web 应用架构中,前端专注于页面,同时与后端进行数据交互;而后端则专注于提供 API 接口。在这样的结构下,REST 是一个很流行的前后端交互形式的约定。这只是一套约定,并不...

cccyb
01/31
0
0
Nodejs Restful Api几点讨论

关于 rest api 版本控制 很多事情,标准和最佳实践是一种平衡 看那本rest api会被玩死,并不实用 rest本身是好东西,其实我只要取其精华就好了 没必要完全按照它做,理解状态变化就好了 标准...

i5ting
2015/08/24
0
10
快嘉接口 sdk 生成 Maven 插件 - sdkg

随着移动应用APP普及、前后端技术分离,微服务化架构盛行,RESTful风格的接口已经在大行其道;而今年开始AI、物联网和云计算的蓬勃发展又加速了人机互联、物物互联的趋势,将RESTful风格的接...

fastjrun
06/19
0
0
WEB开发中,使用JSON-RPC好,还是RESTful API好?

两者没有高下之分,无非是一种约定俗成的标准。习惯用RPC就用RPC,能理解REST就用REST。 JSON-RPC比较符合直观,格式也相对宽松; REST最近正流行,有自己的一套设计规范。 REST面对的疑问跟...

slagga
03/01
0
0
我们必须要知道的RESTful服务最佳实践

看过很多RESTful相关的文章总结,参齐不齐,结合工作中的使用,非常有必要归纳一下关于RESTful架构方式了,RESTful只是一种架构方式的约束,给出一种约定的标准,完全严格遵守RESTful标准并不...

操张林
06/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

配置Spring的注解支持

声明:本栏目所使用的素材都是凯哥学堂VIP学员所写,学员有权匿名,对文章有最终解释权;凯哥学堂旨在促进VIP学员互相学习的基础上公开笔记。 配置Spring的注解支持 以上也提到了使用注解来配...

凯哥学堂
37分钟前
0
0
关于Spring Aop存在的一点问题的思考

在本人前面的文章Spring Aop原理之切点表达式解析中讲解了Spring是如何解析切点表达式的,在分析源码的时候,出现了如下将要讲述的问题,我认为是不合理的,后来本人单纯使用aspectj进行试验...

爱宝贝丶
38分钟前
0
0
JavaScript 概述

JavaScript是面向Web的编程语言。绝大多数现代网站都使用了JavaScript,并且所有的现代Web浏览器——基于桌面系统、游戏机、平板电脑和智能手机的浏览器——均包含了JavaScript解释器。这使得...

Mr_ET
今天
0
0
Java Run-Time Data Areas(Java运行时数据区/内存分配)

Java运行时数据区(内存分配) 本文转载官网 更多相关内容可查看官网 中文翻译可参考 2.5. Run-Time Data Areas The Java Virtual Machine defines various run-time data areas that are use...

lichuangnk
今天
0
0
docker learn :services docker-compose.yml

docker-compose.yml定义了服务的运行参数 version: "3" services: web: # replace username/repo:tag with your name and image details image: hub.c.163.com/dog948453219/friendlyhello d......

writeademo
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部