RESTful API

1. RESTful 接口实战

2. 前言

在学习 RESTful 风格接口之前,即使你不知道它是什么,但你肯定会好奇它能解决什么问题?有什么应用场景?听完下面描述我想你就会明白:

在互联网并没有完全流行的初期,移动端也没有那么盛行,页面请求和并发量也不高,那时候人们对接口的要求没那么高,一些动态页面(JSP)就能满足绝大多数的使用需求。

image-20201204001441126

但是随着互联网和移动设备的发展,人们对 Web 应用的使用需求也增加,传统的动态页面由于低效率而渐渐被 HTML + JavaScript(Ajax)的前后端分离所取代,并且安卓、iOS、小程序等形式客户端层出不穷,客户端的种类出现多元化。而客户端和服务端就需要接口进行通信,但接口的规范性就又成了一个问题:

image-20201204001702612

所以一套结构清晰、符合标准、易于理解、扩展方便,让大部分人都能够理解接受的接口风格就显得越来越重要,而 RESTful 风格的接口(RESTful API)刚好有以上特点,就逐渐被实践应用而变得流行起来。

image-20201204001618944

现在,REST 是目前最流行的接口设计规范,在很多公司有着广泛的应用,其中 Github 的 API 设计 就是很标准的 RESTful API,你可以参考学习。

在开发实践中我们很多人可能还是使用传统 API 进行请求交互,很多人其实并不特别了解 RESTful API,对 RESTful API 的认知可能会停留在:

  • 面向资源类型的
  • 是一种风格
  • (误区)接口传递参数使用斜杠(/)分割而不用问号(?)传参。

而其实一个很大的误区:不要认为没有查询字符串就是 RESTful API,也不要认为用了查询字符串就不是 RESTful API,更不要认为用了 JSON 传输的 API 就是 RESTful API。

本教程将带你了解 RESTful 并用 SpringBoot 实战 RESTful API,在实现本案例前,你需要保证你的电脑上:

  • 拥有 IDEA 用来编写项目代码
  • 拥有 Postman 模拟请求进行测试
  • 拥有关系数据库 MySQL 5.7
  • 拥有 Navicat 对 MySQL 进行管理

3. 一、REST 介绍

REST 涉及一些概念性的东西可能比较多,在实战 RESTful API 之前,要对 REST 相关的知识有个系统的认知。

3.1. REST 的诞生

REST(英文:Representational State Transfer,简称 REST,直译过来「表现层状态转换」)是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。

基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

它首次出现在 2000 年 Roy Thomas Fielding 的博士论文中,这篇论文定义并详细介绍了表述性状态转移(Representational State Transfer,REST)的架构风格,并且描述了如何使用 REST 来指导现代 Web 架构的设计和开发。用他自己的原话说:

我写这篇文章的目的是:在符合架构原理前提下,理解和评估基于网络的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。

需要注意的是 REST 并没有一个明确的标准,而更像是一种设计的风格,满足这种设计风格的程序或接口我们称之为 RESTful(从单词字面来看就是一个形容词)。所以 RESTful API 就是满足 REST 架构风格的接口。

在这里插入图片描述

Fielding 博士当时提出的是 REST 架构在很长的时间内并没有被关注太多,而近些年 REST 在国内才变得越来越流行。

3.2. REST 架构特征

既然知道 REST 和 RESTful 的联系和区别,现在就要开始好好了解 RESTful 的一些约束条件和规则。RESTful 是一种风格而不是标准,而这个风格大致有以下几个主要特征:

3.2.1. 以资源为基础

资源可以是一个图片、音乐、一个 XML 格式、HTML 格式或者 JSON 格式等网络上的一个实体,除了一些二进制的资源外,普通的文本资源更多以 JSON 为载体、面向用户的一组数据(通常从数据库中查询而得到)。

3.2.2. 统一接口

对资源的操作包括获取、创建、修改和删除,这些操作正好对应 HTTP 协议提供的 GET、POST、PUT 和 DELETE 方法。换言而知,使用 RESTful 风格的接口但从接口上你可能只能定位其资源,但是无法知晓它具体进行了什么操作,需要具体了解其发生了什么操作动作要从其 HTTP 请求方法类型上进行判断。具体的 HTTP 方法和方法含义如下:

  • GET(SELECT):从服务器取出资源(一项或多项)。
  • POST(CREATE):在服务器新建一个资源。
  • PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。
  • PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)。
  • DELETE(DELETE):从服务器删除资源。

当然也有很多在具体使用的时候使用 PUT 表示更新。从请求的流程来看,RESTful API 和传统 API 大致架构如下:

image-20201204001311359

URI 指向资源:URI = Universal Resource Identifier 统一资源标志符,用来标识抽象或物理资源的一个紧凑字符串。URI 包括 URL 和 URN,在这里更多时候可能代指 URL(统一资源定位符)。RESTful 是面向资源的,每种资源可能由一个或多个 URI 对应,但一个 URI 只指向一种资源。

无状态:服务器不能保存客户端的信息,每一次从客户端发送的请求中,要包含所有必须的状态信息,会话信息由客户端保存,服务器端根据这些状态信息来处理请求。当客户端可以切换到一个新状态的时候发送请求信息,当一个或者多个请求被发送之后,客户端就处于一个状态变迁过程中。每一个应用的状态描述可以被客户端用来初始化下一次的状态变迁。

3.3. REST 架构限制条件

Fielding 在论文中提出 REST 架构的 6 个限制条件,也可称为 RESTful 6 大原则。标准的 REST 约束应满足以下 6 个原则:

客户端-服务端(Client-Server):这个更专注客户端和服务端的分离,服务端独立可更好服务于前端、安卓、iOS 等客户端设备。

无状态(Stateless):服务端不保存客户端状态,客户端保存状态信息每次请求携带状态信息。

可缓存性(Cacheability):服务端需回复是否可以缓存以让客户端甄别是否缓存,提高效率。

统一接口(Uniform Interface):通过一定原则设计接口降低耦合,简化系统架构,这是 RESTful 设计的基本出发点。当然这个内容除了上述特点提到部分具体内容比较多,详细可以参考这篇 REST 论文内容。

分层系统(Layered System):客户端无法直接知道连接的是终端还是中间设备,分层允许你灵活地部署服务端项目。

按需代码(Code-On-Demand,可选):按需代码允许我们灵活地发送一些看似特殊的代码给客户端,例如 JavaScript 代码。

REST 架构的一些风格和限制条件就先介绍到这里,后面就对 RESTful 风格 API 具体介绍。


4. 二、RESTful API 设计规范

既然了解了 RESTful 的一些规则和特性,那么具体该怎么去设计一个 RESTful API 呢?要从 URL 路径、HTTP 请求动词、状态码和返回结果等方面详细考虑。至于其他的方面例如错误处理、过滤信息等规范这里就不详细介绍了。

4.1. URL 设计规范

URL 为统一资源定位器,接口属于服务端资源,首先要通过 URL 这个定位到资源才能去访问,而通常一个完整的 URL 组成由以下几个部分构成:

URI = scheme "://" host ":" port "/" path ["?" query]["#" fragment]

scheme: 指底层用的协议,如 http、https、ftp
host: 服务器的 IP 地址或者域名
port: 端口,http 默认为 80 端口
path: 访问资源的路径,就是各种 web 框架中定义的 route 路由
query: 查询字符串,为发送给服务器的参数,在这里更多发送数据分页、排序等参数
fragment: 锚点,定位到页面的资源

我们在设计 API 时 URL 的 path 是需要认真考虑的,而 RESTful 对 path 的设计做了一些规范,通常一个 RESTful API 的 path 组成如下:

/{version}/{resources}/{resource_id}

version:API 版本号,有些版本号放置在头信息中也可以,通过控制版本号有利于应用迭代。
resources:资源,RESTful API 推荐用小写英文单词的复数形式。
resource_id:资源的 id,访问或操作该资源。

当然,有时候可能资源级别较大,其下还可细分很多子资源也可以灵活设计 URL 的 path,例如:

/{version}/{resources}/{resource_id}/{subresources}/{subresource_id}

此外,有时可能增删改查无法满足业务要求,可以在 URL 末尾加上 action,例如:

/{version}/{resources}/{resource_id}/action

其中 action 就是对资源的操作。

从大体样式了解 URL 路径组成之后,对于 RESTful API 的 URL 具体设计的规范如下:

  1. 不用大写字母,所有单词使用英文且小写。
  2. 连字符用中杠 - 而不用下杠 _。
  3. 正确使用 / 表示层级关系,URL 的层级不要过深,并且越靠前的层级应该相对越稳定。
  4. 结尾不要包含正斜杠分隔符 /。
  5. URL 中不出现动词,用请求方式表示动作。
  6. 资源表示用复数不要用单数。
  7. 不要使用文件扩展名。

4.2. HTTP 动词

在 RESTful API 中,不同的 HTTP 请求方法有各自的含义,这里就展示 GET、POST、PUT、DELETE 几种请求 API 的设计与含义分析。针对不同操作,具体的含义如下:

GET /collection:从服务器查询资源的列表(数组)
GET /collection/resource:从服务器查询单个资源
POST /collection:在服务器创建新的资源
PUT /collection/resource:更新服务器资源
DELETE /collection/resource:从服务器删除资源

在非 RESTful 风格的 API 中,我们通常使用 GET 请求和 POST 请求完成增删改查以及其他操作,查询和删除一般使用 GET 方式请求,更新和插入一般使用 POST 请求。从请求方式上无法知道 API 具体是干嘛的,所有在 URL 上都会有操作的动词来表示 API 进行的动作,例如:query、add、update、delete 等等。

而 RESTful 风格的 API 则要求在 URL 上都以名词的方式出现,从几种请求方式上就可以看出想要进行的操作,这点与非 RESTful 风格的 API 形成鲜明对比。

在谈及 GET、POST、PUT、DELETE 的时候,就必须提一下接口的安全性和幂等性,其中安全性是指方法不会修改资源状态,即读的为安全的,写的操作为非安全的。而幂等性的意思是操作一次和操作多次的最终效果相同,客户端重复调用也只返回同一个结果。

上述四个 HTTP 请求方法的安全性和幂等性如下:

HTTP Method 安全性 幂等性 解释
GET 安全 幂等 读操作安全,查询一次多次结果一致
POST 非安全 非幂等 写操作非安全,每多插入一次都会出现新结果
PUT 非安全 幂等 写操作非安全,一次和多次更新结果一致
DELETE 非安全 幂等 写操作非安全,一次和多次删除结果一致

4.3. 状态码和返回数据

服务端处理完成后客户端也可能不知道具体成功了还是失败了,服务器响应时,包含状态码和返回数据两个部分。

4.3.1. 状态码

我们首先要正确使用各类状态码来表示该请求的处理执行结果。状态码主要分为五大类:

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

每一大类有若干小类,状态码的种类比较多,而主要常用状态码罗列在下面:

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)。
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*]:表示用户得到授权(与 401 错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求 JSON 格式,但是只有 XML 格式)。
  • 410 Gone - [GET]:用户请求的资源被永久删除,且不会再得到。
  • 422 Unprocessable Entity - [POST/PUT/PATCH]:当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

4.3.2. 返回结果

针对不同操作,服务器向用户返回数据,而各个团队或公司封装的返回实体类也不同,但都返回 JSON 格式数据给客户端。


5. 一个 RESTful API 案例

上面讲了 RESTful 理论知识,下面动手实现一个小案例吧!

5.1. 预备

在本案例的实战中,我们访问的 RESTful 接口都是对数据库真实的操作,新建数据库,创建一个数据库和表(根据自己喜好)。

选择 Maven 依赖的时候,只需要勾选其中 Spring 的 Web 模块、MySQL 驱动以及 MyBatis 框架。

本案例的 POJO 创建 Dog.java 实体对象,其具体构造为:

package com.restfuldemo.pojo;

public class Dog {
    private int id; // 唯一id标识
    private String name; // 名称
    private int age; // 年龄
    // 省略 get/set
}

上面创建好了项目,我们就开始构建 RESTful 风格的 API。在具体构建 RESTful API 的时候,需要对各种请求有更细致的认知,当然,本案例在实现各种请求的时候为了演示的便捷并没有完全遵循 RESTful API 规范,例如版本号等信息这里就不添加了,案例更侧重于使用 SpringBoot 实现这个接口。

本案例实现对 dog 资源的增删改查,如下是非 RESTful 和 RESTful 接口对比:

API name 非 RESTful RESTful
获取 dog /dogs/query/{dogid} GET:/dogs/{dogid}
插入 dog /dogs/add POST:/dogs
更新 dog /dogs/update/{dogid} PUT:/dogs/{dogid}
删除 dog /dogs/delete/{dogid} DELETE:/dogs/{dogid}

另外在使用 Postman 进行发送请求的时候,有三种常用的文件类型传递到后端:

在这里插入图片描述

form-data:就是 form 表单中的 multipart/form-data,会将表单数据处理为一条信息,用特定标签符将一条条信息分割开,而这个文件类型通常用来上传二进制文件。

x-www-form-urlencoded:就是 application/x-www-form-urlencoded,是 form 表单默认的 encType,form 表单会将表单内的数据转换为键值对,这种格式不能上传文件。

raw:可以上传任意格式的文本,可以上传 Text、JSON、XML 等,但目前大部分还是上传 JSON 格式数据。当后端需要接收 JSON 格式数据处理的时候,可以采用这种格式来测试。

因为 GET 请求查询参数在 URL 上,其他类型请求使用 x-www-form-urlencoded 方式向后端传值。

5.2. GET POST PUT DELETE 请求

GET 请求用来获取资源:GET 请求会向数据库发索取数据的请求,从而来获取资源,该请求就像数据库的 select 操作一样,只是用来查询数据,不会影响资源的内容。无论进行多少次操作,结果都是一样的。

并且 GET 请求会把请求的参数附加在 URL 后面,但是不同的浏览器对其有不同的大小长度限制。

在本案例中,我们设计两个 GET 请求的 API:

  • GET /dogs:用来返回 dog 资源的列表。
  • GET /dogs/{dogid}:用来查询此 id 的单个 dog 资源。

POST 请求用来新增一个资源:POST 请求向服务器发送数据,但是该请求会改变数据的内容(新添),就像数据库的 insert 操作一样,会创建新的内容。且 POST 请求的请求参数都是请求体中,其大小是没有限制的。

在本案例中,我们设计以下 POST 请求的 API:POST /dogs:服务端新增一个 dog 资源。

PUT 请求用来更新资源,PUT 请求是向服务器端发送数据的,与 POST 请求不同的是,PUT 请求侧重于数据的修改,就像数据库中 update 一样,而 POST 请求侧重于数据的增加。

在本案例中,我们设计以下 PUT 请求的 API:PUT /dogs/{dogid}:用来更新此 id 的单个 dog 资源。

DELETE 请求用来删除资源,DELETE 请求用途和它字面意思一致,用来删除资源。和数据库中 delete 相对应。

在本案例中,我们设计以下 DELETE 请求的 API:DELETE /dogs/{dogid}:用来删除此 id 的单个 dog 资源。

对应的 Mapper 文件为:

package com.restfuldemo.mapper;

import com.restfuldemo.pojo.Dog;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface DogMapper {

    @Select("select * from dog")
    List<Dog> getAllDog();

    @Select("select * from dog where id=#{id}")
    Dog getDogById(@Param("id") int id);

    @Insert("insert into dog (name,age) values (#{name},#{age})")
    boolean addDog(Dog dog);

    @Update("update dog set name=#{name},age=#{age} where id=#{id}")
    boolean updateDog(Dog dog);

    @Delete("delete from dog where id=#{id}")
    boolean deleteDogById(int id);
}

对应 Controller 文件为:

package com.restfuldemo.controller;

import com.restfuldemo.mapper.DogMapper;
import com.restfuldemo.pojo.Dog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Arrays;
import java.util.List;

@RestController
public class TestController {

    @Autowired(required = false)
    DogMapper dogMapper;

    @GetMapping("dogs")
    public List<Dog> getDogs() {
        return dogMapper.getAllDog();
    }

    @GetMapping("dogs/{id}")
    public Dog getDogById(@PathVariable("id") int id) {
        Dog dog = dogMapper.getDogById(id);
        return dog;
    }

    @PostMapping("dogs")
    public boolean addDog(Dog dog) {
        return dogMapper.addDog(dog);
    }

    @PutMapping("dogs/{id}")
    public boolean updateDog(@PathVariable("id") int id, @RequestParam("name") String name, @RequestParam("age") int age) {
        Dog dog = dogMapper.getDogById(id);
        dog.setName(name);
        dog.setAge(age);
        return dogMapper.updateDog(dog);
    }

    @DeleteMapping("dogs/{id}")
    public boolean deleteDog(@PathVariable("id") int id) {
        return dogMapper.deleteDogById(id);
    }
}

经过笔者测试一切都是 OK 的,如果要项目源文件请联系笔者发你哈!


6. 总结

RESTful 风格的 API 固然很好很规范,但大多数互联网公司并没有按照或者完全按照其规则来设计,因为 REST 是一种风格,而不是一种约束或规则,过于理想的 RESTful API 会付出太多的成本。

比如 RESTful API 也有一些缺点:

  • 操作方式繁琐,RESTful API 通常根据 GET、POST、PUT、DELETE 来区分操作资源的动作,而 HTTP Method 本身不可直接见,是隐藏的,而如果将动作放到 URL 的 path 上反而清晰可见,更利于团队的理解和交流。
  • 并且有些浏览器对 GET、POST 之外的请求支持不太友好,还需要特殊额外的处理。
  • 过分强调资源,而实际业务 API 可能有各种需求比较复杂,单单使用资源的增删改查可能并不能有效满足使用需求,强行使用 RESTful 风格 API 只会增加开发难度和成本。

所以,当你或你们的技术团队在设计 API 的时候,如果使用场景和 REST 风格很匹配,那么你们可以采用 RESTful 风格 API。但是如果业务需求和 RESTful 风格 API 不太匹配或者很麻烦,那也可以不用 RESTful 风格 API 或者可以借鉴一下,毕竟无论哪种风格的 API 都是为了方便团队开发、协商以及管理,不能墨守成规。

到这里 RESTful API 的介绍和实战就结束啦,本篇首先从 RESTful 的一些特点进行介绍,再到 SpringBoot 实战 RESTful API,最后也说了一些 RESTful API 并不完美的地方,相信睿智的你对 RESTful 一定有了很深刻的理解。在以后项目的 API 设计上定能有所优化。

不同的人对 RESTful API 可能有着不同的理解,但存在即合理,RESTful API 有着其鲜明的优势和特点,目前也是一种 API 设计的主要选型之一,所以掌握和理解 RESTful API 还是相当重要的!