REST 简介
Roy Thomas Fielding 在 2000 年的博士论文中提出 Rest 架构。它是思考如何开发在互联网环境使用的软件的结果。Fielding 将他对互联网软件的架构原则,定名为 REST,即 Representational State Transfer。如果一个架构符合 REST 原则,那么就称为 RESTful 架构。
RESTful 架构是目前最流行的一种互联网软件架构。REST 的名称,表现层状态转化,表现层指资源(Resources) 的表现层。
资源
资源其实就是网络上的一个实体,你可以用一个 URI 指向它,每种资源对应一个特定的 URI。要获取这个资源,访问它的 URI 就可以,URI 称为每一个资源的地址或者独一无二的识别符。而上网也就是与互联网上一系列的资源互动。
资源是一种信息实体,可以有多种表现形式,资源的具体呈现出来的形式,叫做它的表现层(Representation)
例如:文本可以用 txt 格式表现,也可以用 HTML 格式、XML 格式、JSON 格式表现,甚至二进制格式。图片可以用 JPG 格式表现,也可以用 PNG 格式表现。
表现层
URI 只代表资源的实体,不代表它的形式,严格说,一些网址最后的 html 后缀名不是必要的,因为这个后缀名属于表现层范涛,而 URI 应该只代表资源的位置。它的具体表现形式,应该在 HTTP 请求的头信息中用 Accept 和 Content-Type 字段指定的。这两个字段才是对表现层的描述。
状态转化(State Transfer)
浏览网站时,代表客户端和服务器互动,这个过程势必涉及数据和状态变化。HTTP 是无状态协议,这意味着,所有状态都保存在服务器端。如果客户端想要操作服务器,必须通过某种手段,让服务器端发生 状态转化 (State Transfer)。这种转化是建立在表现层上的。所以是表现层状态转化。
客户端只能通过 HTTP 协议的 GET、POST、PUT、DELETE 来和服务器交互。GET 获取资源,POST 新建资源(更新资源),PUT 更新资源,DELETE 删除资源。
RESTful 架构:
- 每一个 URI 代表一种资源
- 客户端和服务器之间,传递这种资源的某种表现层
- 客户端通过 HTTP 对服务器端资源进行操作,实现 表现层状态转化。
RESTful 设计
以前我们在设计接口时:
- 列表
/bbs_list.do?keyword=xx
- 查看一篇帖子
/bbs_view.do?id=xx
- 查看一篇帖子的所有回帖
/bbs_replay_list.do?id=xx
- 回帖
/bbs_replay.do?id=xx
- 删帖
/bbs_delete.do?id=xx
在加上很多人的习惯不同,命名会出现 delete、remove 等等近义词。代码就非常混乱。大量的接口方法,URL 地址设计复杂,需要在 URL 里面表示出资源及其操作。
在 RESTful 架构中,每一个网址代表一种资源,网址中不能有动词,只能有名词。所用的名词往往与数据库的表名对应。一般的,数据库中的表都是一种记录的集合,所以 URI 中的名词应该也是复数。比如:
https://api.example.com/v1/zoos
动物园资源https://api.example.com/v1/animals
动物资源https://api.example.com/v1/employees
饲养员资源
参考例子:https://api.github.com
、RESTful API 指南
HTTP 动作设计
动作 | 意义 |
---|---|
GET (SELECT) | 从服务器取出资源(一项或多项) |
POST (CREATE) | 从服务器新建一个资源 |
PUT (UPDATE) | 在服务器更新资源(客户端提供改变后的完整资源),PUT 更新整个对象 |
PATCH (UPDATE) | 在服务器更新资源(客户端提供改变的属性[补丁]),PATCH 更新个别属性 |
DELETE (DELETE) | 从服务器删除资源 |
HEAD | 获得一个资源的元数据,比如一个资源的 hash 值或者最后修改日期 |
OPTIONS | 获得客户端针对一个资源能够实施的操作:(获取该资源的 api 能够对资源做什么操作的描述) |
例子:
URI | 表示含义 |
---|---|
GET /zoos | 列出所有动物园 (数组或者集合) |
POST /zoos | 新建一个动物园 |
GET /zoos/ID | 获取某个指定动物园的信息,/zoos/1 地址栏传参 |
PUT /zoos/ID | 更新某个指定动物园的信息(提供该动物园的全部信息) |
PATCH /zoos/ID | 更新某个指定动物园的信息(提供该动物园的部分信息) |
DELETE /zoos/ID | 删除某个动物园 |
GET /zoos/ID/animals | 列出某个指定动物园的所有动物 |
获取某个部门的员工
1 | GET /depts/ID/employees 标准 |
返回结果类型
URI | 返回结果类型 |
---|---|
GET /zoos | 返回资源对象的列表(数组/集合) |
POST /zoos | 返回新生成的资源对象 |
PUT /zoos/ID | 返回完整的资源对象 |
PATCH /zoos/ID | 返回完整的资源对象 |
DELETE /zoos/ID | 返回一个空文档 |
可以通过 URL 规定获取格式类型,但是建议使用 Accept 这个请求头:
Accept 表示客户端希望接受的数据类型,比如 Accept: application/json; 代表客户端希望接受的数据类型是 json 类型,后台返回 json 数据。
Content-Type 表示发送端(客户端|服务器) 发送的实体数据的数据类型,比如 ContextType: application/json; 表示发送端发送的数据格式是 json,后台就是要以这种格式来接受前端发送来的数据。
二者结合就是 及代表希望接受的数据类型是 json 格式,本次请求发送的数据的数据格式也是 json 格式。
Http 报头分为 通用包头、请求报头、响应报头和实体报头。请求方的 http 报头结构:通用报头|请求报头|实体报头。请求方的 http 报头结构:通用报头|请求报头|实体报头;响应方的 http 报头结构:通用报头|响应报头|实体报头
Accept 属于请求头,Content-Type 属于实体头。
设计误区
RESTful 架构最经典的设计误区,就是 URI 中包含动词。资源时一种实体,所以应该是名词。URI 中不应该有动词,动词应该放在 HTTP 协议中。
举例:某个 URI 是 /posts/show/1
其中 show 是动词,URI 就设计错了。正确的写法是 /posts/1
,然后用 GET 方法表示 show。如果有些动作是 HTTP 动词表示不了的,比如网上汇款,从账户1向账户2汇款500元,错误的 URI 是 POST /accouts/1/transfer/500/to/2
正确的写法是把 transfer 改成名词 transaction,应该把动作做成一种资源,也可以是一种服务:
1 | POST /transaction HTTP/1.1 |
另一种设计误区是在 URI 中加入版本号
1 | http://www.example.com/app/1.0/foo |
由于版本不同,可以理解为同一种资源的不同表现形式,应该采用同一个 URI,版本号应当在 HTTP 请求头信息的 Accept 字段中区分。
1 | Accept: vnd.example-com.foo+json; version=1.0 |
API 接口测试工具
Postman、Insomnia。
根据需求设计接口
- 获取所有员工(集合)
- 资源设计:/employees
- 动作设计:get
- 请求参数设计:无
- 返回结构设计:集合,状态码 200
1 | "employees", method = RequestMethod.GET) (value = |
- 获取某个员工的信息
- 资源设计:/employees/{id} —-> 路径占位符
- 动作设计:get
- 请求参数设计:无
- 返回结构设计:员工对象,状态码 200
1 | "employees/{id}", method = RequestMethod.GET) (value = |
由于以后会遇到很多花括号,比如 “employees/{id}/{name}…” 所以必须明确参数是哪个,因此需要 @PathVariable 注解来注入值,可以使用 @PathVariable(“id”) 指定参数是 URI 路径上的哪个参数,如果不指定,则会用参数名去路径上匹配。
但其实这样写比较麻烦,由于将来开发的时候,前后端分离,所以基本只会写接口。因此每个方法上都要写 @ResponseBody,这个时候可以把 @ResponseBody 贴到类上,而且在 Spring4.0 之后,除了一个新的注解 @RestController,将 @ResponseBody 和 @Controller 合并。因此之后只要是只设计接口的 Controller 直接使用 @RestController 即可。
而 @GetMapping 实际上又是 Method=RequestMethod.GET 的 RequestMapping,下面是 GETMapping 的声明。+
1 | (method = RequestMethod.GET) |
- 删除一个员工
- 资源设计:/employees/{id} —-> 路径占位符
- 动作设计:delete
- 请求参数设计:无
- 返回结构设计:空文档,状态码 204(需要手动设置,一般公司不会去主动设置,除非是大众公用的接口,才会规范)
1 | "{id}") ( |
- 获取某个员工某个月的薪资记录
- 资源设计:/employees/{id}/salaries/{month}
- 动作设计:get
- 请求参数:员工id,月份
- 返回结果:薪资对象,200
1 | "{id}/salaries/{month}") ( |
前台传入时间格式,需要解析,一般有两种方式,一个是贴注解 @DateTimeFormat、或者写一个 ControllerAdvice
返回格式需要使用 @JsonFormat 注解在 domain 中去规定。
- 给某个员工增加一条薪资记录
- 资源设计:/employees/{empId}/salaries
- 动作设计:post
- 请求参数:员工id,salary对象
- 返回结果:新生成薪资对象,201
1 | "{employeeId}/salaries") ( |
最终版本
1 |
|
状态码
状态码 | 表示 |
---|---|
200 OK - [GET] | 服务器成功返回用户请求的数据 |
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 Unprocesable entity - [POST/PUT/PATCH] | 当创建一个对象时,发生一个验证错误 |
500 INTERNAL SERVER ERROR - [*] | 服务器发生错误,用户将无法判断发出的请求是否成功 |
状态码错误演示
405
1 | "employees", method = RequestMethod.GET) (value = |
后台限制了请求使用的 HTTPMethod,前台请求时使用的 Method 不匹配
RequestMapping 配置的路径的时候,是有数组特性的,可以配置多个路径以及多个请求方法
415
后台限制了接受的参数类型,前台传入参数类型不匹配。
RequestMapping
params
1 | "list", params = "name=admin") (value = |
params 参数表示,请求时,必须带上指定参数名与参数值,否则报 400 错误。name=admin 或者 name!=admin
headers
1 | "list", headers = "content-type=application/json") (value = |
headers 参数表示,要求请求时必须带有规定的头信息。
consumes(消费)、produces(生产)
1 | // 表示后台方法专门消费某种格式的数据 输入格式定死,可以有多个资源,不同的输入格式。 |
上面的含义与 headers 一样,因为服务器要消费客户端传过来的数据,因此指定传入的数据类型。consumes 是 headers 规定 ContentType 的简写,而且 consumes 只跟 ContentType 有关系。相当设置了 headers=”ContentType=application/json”
produces 规定的是 accept 接受的数据类型,前台希望接收什么格式,就生产什么格式。相当于配置了 headers = “Accept=application/json”
不同数据类型请求参数的封装
- form-data 表单提交,其实是将表单数据序列化编程,URI?xx=xx&xx=xx 的形式。
- SpringMVC 默认的封装格式,无需任何注解可以直接将 form-data 转化为 javaBean
- JSON(application/json) 传输为 json 数据
格式为:
1 | { |
1 | "consumes", consumes = "application/json") (value = |
需要参数前贴 @RequestBody 注解,解析 Json 数据为 javaBean 对象。@RequestBody 注解默认是解析 json 数据。但如果请求数据为 xml 数据。
- xml
如果请求数据为 xml,在 @RequestBody 注解解析数据时,要告诉解析方式,需要在 domain 的 javabean 类中告诉 xml 跟元素,和匹配属性还是字段 XmlAccessType.PROPERTY
或者@XmlAccessorType(XmlAccessType.FIELD)
, @RequestBody 不可少
1 | <Employee> |
需要在 domain 中贴注解
1 | "Employee") // 根元素的名称 (name = |
如果 xml 中名字不同
1 | <Employee> |
1 | "Employee") // 根元素的名称 (name = |
Ajax 发送不同类型的请求
jquery 默认封装的 发送请求只有 get post,其他请求方式没有封装,需要使用 ajax 原生的发送请求方法
ajax 发送 DELETE 请求
1 | $("#deleteBtn").click(function (){ |
ajax 发送 PUT 请求
1 | $("#deleteBtn").click(function (){ |
PUT 请求 springMVC 不会帮我们将 data 参数封装到 后台的 javabean 中,只有 id = 1 封装到 Employee 了, name 和 age 都消失了,需要配置一个 springmvc 的 filter 才可以
1 | <!-- 处理 put 或者 patch 请求方式的过滤器 --> |
拦截到请求时,先执行 httpPutFormContentFilter,然后再执行 springDispatcherServlet。
form 表单提交 put/patch/delete 请求
解决问题:
因为表单无法提交 put/delete/patch 请求,所以后台 @PUTMapping 资源无法被表单提交访问。
1 | <form action="/employees/1" method="post"> |
1 | <!-- 专门处理 form 表单不能提交 put, delete, patch 请求的过滤器 --> |
此时配置了 web.xml 后就可以用表单提交 put/delete/patch 等请求了。
1 |
|
原理,将请求重新包装成新的请求再放行。