移动互联网时代,RESTful API成为越来越重要的移动端和服务器端交互的形式。尤其是在很多互联网公司或者传统行业拥抱移动互联网的时候,一套设计良好的Restful API能够帮助互联网产品支持单服务端+多客户端的场景。RESTful架构本身是一个风格而不是一个标准,这也就意味着在具体设计时会有不同的实现。

虽然 HTTP REST API 在互联网特别流行,但它们承载的流量却比传统的 RPC API 要少。例如,在美国高峰时期的约一半流量是视频内容,但因为效率问题,很少有人会考虑使用 REST API 来传输这些内容。在数据中心,许多公司使用基于 socket 的 RPC API 来传输大部分网络流量,这会比 REST API 高出几个数量级。

什么是RESTful API

表征性状态传输(Representational State Transfer,简称REST )是Roy Fielding博士于2000年在他的博士论文中提出来的一种软件架构风格。如果一个架构符合REST原则,就称它为RESTful架构。说到Roy Fielding几乎可以称之为HTTP协议之父,他是HTTP1.0和1.1的主要设计者,这篇基于HTTP协议的软件架构风格一出世就引起了关注并对互联网开发产生了深远的影响。发展到今日主流开源框架都提供了对REST的支持,大多数互联网公司都采用了RESTful架构。

资源(Resources)是REST的核心

REST开发又被称作“面向资源的开发”,这说明对于资源的抽象是设计RESTful API的核心内容。RESTful API建模的过程与面向对象建模类似,是以名词为核心的。这些名词就是资源,任何可命名的抽象概念都可以定义为一个资源。对于业务的抽象是设计一套好的RESTful API的基础,这就好比建房子打地基,如果地基没有打好,后面建的楼就很容易歪掉,其美观度,可维护性,可扩展性就会大大折扣。笔者会建议在设计初期一定要在资源的定义上多花功夫,抽象出适合业务发展的资源。也就是说一开始要把产品的RESTful风格定义下来,后面的扩展都可以基于这样的风格延续下去。

下面是几条小的建议:

  • 理清资源的层次结构,比如业务针对的范围是学校,那么学校会是一级资源(/school),老师(/school/teachers),学生(school/students)就是二级资源。
  • 资源尽量用准确的英文名词去表达,资源组都用复数来表达。一个好的资源定义一定是自解释的,也就是说你只需要很少的先验信息就能理解这个resource资源代表什么意思。

资源的状态转化(State Transfer)

访问一个网站,代表的就是客户端和服务器的一个互动过程,这个过程中势必就涉及到数据和状态的变化。而广为使用的HTTP协议又恰恰是无状态的协议,这就意味这所有的状态都保存在服务器端。如果某个客户端想要操作服务器端必须通过某种手段让服务器发生状态转换。那么客户端可以操作的就是上文所定义的资源,而资源的状态转化就转化为对资源的各种操作。这些操作是通过HTTP协议的四种常用的方法来实现:GET,POST,PUT,DELETE。他们分别对应四种基本操作:

  • GET-获取资源
  • POST-新建或者更新资源
  • PUT-更新资源
  • DELETE-删除资源
  • 还有其它不常用的HTTP方法:PATCH/HEAD/OPTIONS方法。

对于HTTP方法的应用业界一直有两种声音:

  • 一种是尽量使用所有的HTTP方法去操作资源,请求里面只能带资源不能出现任何动词,如果发现资源上的操作过多以至于HTTP的方法不够用,应该考虑设计出更多的资源。这种方式适用于新产品的开发,产品是从建模开始的,可以充分的考虑各种业务场景定义出相应的资源。
  • 另一种是就是灵活适用,用GET去获取资源,其它涉及更改的操作都用POST,并当POST不能表达相应的动作时在URL中添加动词。比如在二手商品市场我发布了一个售卖手机的资源。那么对于这个资源,卖家本人可以对这个商品有取消的操作:POST /resources/123/cancel;买家可以对这个商品有购买的操作:POST /resources/123/buy。这种方式适用于对现有非RESTful架构进行升级改造而修改模型影响更大的业务产品。当然这种选择也有可能是前后端程序猿们协商的结果,毕竟用一种最自然的沟通方式去架起前后端的桥梁才是目的所在。

合理使用URL路径参数和请求

在URL路径里的参数一定是代表某个资源的ID,路径参数也可以是多个代表几级资源的IDs,例如获取一个老师所带班级的详情/teachers/#teacher_id/classes/#class_id。

对于HTTP GET,请求参数一般是作为可选参数获取某个资源列表的子集,例如获取年青老师的列表/teachers?group=young。

对于HTTP POST,请求参数是在消息体里,代表需要新建或者更新的资源。

合理使用HTTP响应代码

HTTP响应状态代码,是HTTP协议这个统一接口中用来表达出错情况的标准机制。响应状态代码分成两部分:状态码和原因。两部分都是可定制的,也可以使用标准的状态码,只定制出错原因。在实际应用中也是两种选择:

  • 一种是对于应用出错的情况扩展状态码,定制出错原因。
  • 一种是对于容器处理的出错情况默认使用容器返回的错误码,例如tomcat容器返回的503,404;而对于应用本身返回的状态码一律返回200,对于应用出错码和原因都反应在返回消息体中。

定义一套标准的返回体数据结构

对于所有的RESTful HTTP请求定义一套标准的返回结构体,前端可以根据这样的固定格式做标准化的解析,对于系统的可维护性起到很大的帮助。这个结构体里应该包含返回的具体资源,结果状态码和错误原因(如果有的话)。对于返回的资源,数据类型也尽量做到统一,比如日期,枚举类型都返回统一的数据类型避免不同的API对同一种数据有不同的处理方式。资源属性尽量做到可读也能大大减少前后端的沟通成本。

关于Request 和 Response,不要忽略了http header中的Content-Type。以json为例,如果API要求客户端发送request时要传入json数据,则服务器端仅做好json数据的获取和解析即可,但如果服务端支持多种类型数据的传入,如同时支持json和form-data,则要根据客户端发送请求时header中的Content-Type,对不同类型是数据分别实现获取和解析;如果API响应客户端请求后,需要返回json数据,需要在header中添加Content-Type=application/json。

Serialization 和 Deserialization即序列化和反序列化。RESTful API以规范统一的格式作为数据的载体,常用的格式为json或xml,以json格式为例,当客户端向服务器发请求时,或者服务器相应客户端的请求,向客户端返回数据时,都是传输json格式的文本,而在服务器内部,数据处理时基本不用json格式的字符串,而是native类型的数据,最典型的如类的实例,即对象(object),json仅为服务器和客户端通信时,在网络上传输的数据的格式,服务器和客户端内部,均存在将json转为native类型数据和将native类型数据转为json的需求,其中,将native类型数据转为json即为序列化,将json转为native类型数据即为反序列化。虽然某些开发语言,如Python,其原生数据类型list和dict能轻易实现序列化和反序列化,但对于复杂的API,内部实现时总会以对象作为数据的载体,因此,确保序列化和反序列化方法的实现,是开发RESTful API最重要的一步准备工作

RESTful API的版本控制

一个简单的做法是直接在URL中插入版本号,这样可以允许多个版本的API同时运行。在已经发布的版本中尽量做到向后兼容,包括URL和参数,对于返回值也是尽量增加新的冗余参数以兼容不同客户端不同的升级频率。等到所有的客户端升级以后再去除冗余的过程。

RESTful API的安全性

安全性也是一个很大的话题,如果展开来讲又将是另外一篇文章。简单来讲主要是要考虑下面几个方面:

  • 对客户端做身份认证
  • 保证每个用户只能操作自己的资源,而不会CRUD属于别人的资源
  • 对于敏感数据做加密防止串改
  • 对于POST请求添加幂等机制以保证对资源的增加或者修改是安全的。

结语

最后作为结束语,想说明的是一套设计良好的RESTful一定是前后端反复沟通协商并不断迭代的过程。因为RESTful API作为前后端的桥梁我们需要同时考虑前后端的需求并达成一致的一个结果,桥梁之所以成为桥梁一定是双方都认可的沟通方式。架构是一门科学,也是一种艺术。