RESTful风格
学习RESTful接口之前,我们需要先了解一些关于HTTP的知识。
一 http
我们都知道,HTTP就是HyperText Transfer Protocol(超文本传输协议)的缩写。它是一种关于“传输”的协议,既然是传输,那么至少要在两个对象之间进行,在HTTP中对应的就是客户端和服务端。客户端和服务端对应两个动作——请求和响应。客户端向服务端发送请求,而服务端则根据客户端的请求做出相应的响应。
客户端能够发出请求,服务端能够做出响应,这样才能形成一个完整的HTTP通信过程。假如只有客户端没有服务端,则发出请求后收不到响应,岂不是白白浪费时间?而如果没有任何请求,那么服务端无从响应,也不知道响应给谁。这两种情况都不太好,只有一个人喊一句:有船吗?另一个人回应:船来啦!这样才圆满。
1.1 报文
如果你接触过HTTP,那么对“报文”肯定有所耳闻。HTTP的报文有两种
- 请求报文
- 响应报文
请求报文
开头的GET
表示请求的类型 , 称 为 HTTP 的 方 法 ( Method ) 。 之 后的/v3/api-docs
表示请求的路径。HTTP/1.1
表示本次请求使用的HTTP协议的版本。接下来的Host和Accept都属于首部(Header)字段,属于可选字段。空行下面的…代表实体的主体(Entity Body)部分,同样是可选内容。
这段请求内容的意思是:以GET方式基于HTTP协议的1.1版本请求访问localhost:8080
服务器(这里是我们自己的计算机)上的/v3/api-docs
资源。
响应报文
开头的HTTP/1.1与请求报文的意义相同。之后的200 OK
表示响应的状态码(Status Code)和原因短语(Reason-Phrase),接下来的Date、Content-Type
等都属于首部字段。接着以空行分隔,最后的…与请求报文一样是资源实体的主体。
报文结构
根据上面请求报文和响应报文的例子,我们可以知道报文由3部分组成。
- 起始行(请求行、响应行)
报文的第一行,请求行(请求报文中的起始行)用来说明要做什么,响应行(响应报文中的起始行)用来说明结果如何。 - 首部
起始行后面有零到多个首部字段,首部字段由key:value的方式构成,类似于Java中的Map结构。首部以一个空行结束。 - 主体(部分请求方法没有主体)
空行之后是报文主体,请求主体包含了客户端发送给服务端的数据;响应主体则是服务端要返回给客户端的内容。起始行和首部都是文本格式,且其结构都是相对固定的。而主体则不一样,主体中可以包含任何格式的数据(如文本、图片、音频、视频、其他文件)。
报文结构如图所示。
首部和主体之间有一个空行。
1.2 状态码
状态码与原因短语用来描述请求的处理结果。HTTP状态码共有五大类,如表所示。
目前,1xx的状态码并不常见,原因是对于这类状态码,人们还存在很多争议,对其应用非常少。常见状态码包括200、304、403、404、500等。
1.3 安全性和幂等性
安全性与幂等性指的都是HTTP方法的特性。安全性指的是不会对服务端造成影响,也就是说,如果一个方法是安全的,那么无论如何请求,服务端都不会因为这个请求而发生变化,简而言之就是只读。幂等性指的是多次请求对服务器造成的影响与第一次请求完全一样。例如,调用一个PUT方法将ID为1的用户年龄值设置为18,那么无论调用多少次这个方法,对服务端的影响都是将ID 为1的用户年龄值设置为18。
下表展示了常用HTTP方法的安全性和幂等性
安全性与幂等性依赖于服务端实现,这种方式是一种契约,并不是说将一个删除操作的接口设置为GET请求(它依然具备安全性),而是说对应类型的请求在实现的时候要符合它们的安全性、幂等性约定。
1.4 协议版本
在前面介绍报文的时候,你可能已经发现了,不管是请求还是响应,里面都有一个值HTTP/1.1
。这个值主要用来说明当前请求/响应使用的是HTTP 的哪个版本。HTTP发展至今,经历了几个版本的更迭,一直在进化,在成长。前面示例中用的是目前最为流行的HTTP/1.1。除了这个版本,在这个版本之前还有HTTP/0.9、HTTP/1.0,之后还有HTTP/2.0。接下来我们来看看它们之间的异同。
http/0.9
这个版本只能算作一个原型版本,诞生于1991年。它非常简陋,并且存在严重的设计缺陷。它只支持GET请求,没有Header(也就是我们上面说的首部),其设计初衷就是为了从服务器中获取简单的HTML对象。好在后面很快就被HTTP/1.0取代了。
HTTP/1.0
HTTP/1.0算是真正意义上的正式版本。这个版本设计已经非常良好与完善了,后面也得到了广泛的应用。HTTP/1.0在之前版本的基础上增加了Header、状态码的支持,并且支持更多的HTTP方法,还加入了对多媒体格式和缓存的支持。
HTTP/1.1
HTTP/1.1是目前应用最广泛的版本,在HTTP/1.0的基础上进行了进一步的完善。该版本最大的变化是引入了持久连接,使得建立一次连接可以发送多次HTTP请求,提高了资源利用率。同时,增加的PUT、PATCH、DELETE方法对后来RESTful的发展也有一定的促进作用。另外,Header中还增加了Host字段,使得同一主机可以提供多个服务。
HTTP/2.0
HTTP/2.0目前还没有得到广泛的应用,但这只是时间问题而已。这个版本主要在性能方面进行了优化,将所有数据都改为二进制格式进行传输(之前基本上都是字符串),并且对首部内容进行了压缩传输。此外,还增加了双工模式,使得客户端可以在一个HTTP连接中同时发送多个请求,服务端也可以同时处理多个请求。HTTP/2.0还增加了一个新特性——服务器推送(ServerPush),即由服务器主动发起的操作,这一特性很适合静态资源(如CSS、JS等)的加载。
说起HTTP,有这样一个现象:前端工作人员认为HTTP应该是后端工作人员掌握的知识,后端工作人员认为HTTP应该是前端工作人员掌握的知识。对此,HTTP表示“我招谁惹谁了?”那么,HTTP究竟是谁应该掌握的呢?我认为,每个程序员都应该了解HTTP。
二 解密REST
经过上面对HTTP的学习,我们已经具备了学习REST的前置知识,并掌握了基本工具。接下来,本章的“主角”就要登场了。
2.1 REST定义
REST是Representational State Transfer的缩写,翻译为中文就是“表现层状态转换”,是Roy Thomas Fielding于2000年在他的博士论文中提出来的一种互联网软件架构风格。
以上是关于REST的解释,如果你通过以上的描述理解了REST是什么,那么你可以不用继续往下学习了。假如你没理解,那么我非常欢迎你继续看下去。
Roy Thomas Fielding是何许人也呢?他是HTTP协议(1.0版和1.1版)的主要设计者,Apache服务器软件的作者之一,Apache基金会的第一任主席。所以,当他提出REST的概念时,能够迅速引起业界的高度关注也就不足为奇了。
资源
“表现层状态转换”的说法比较抽象,实际上,“表现层”指的是“资源”的表现层(可能Roy Thomas Fielding觉得RREST看起来不如REST好看,所以省略了Resource)。那么我们首先需要弄明白,这里的“资源”指的是什么。实际上,“资源”的范围比较宽泛,比如一个文件(图片、文档、音乐
等)、一条数据(用户信息、订单等)都可以被看作资源(每个资源都有一个对应的URI)。我们在学习面向对象编程的时候,应当都听过一句“五字真言”——万物皆对象。这里可以将其拿过来套用一下,即万物皆资源。
表现层
Representational被翻译成表现层,其实我认为叫“表现形式”会更容易理解。简单来说,就是资源以什么样的形式来展现自己——例如,文本可以是JSON或XML格式的,图片可以是JPEG或其他格式的。所以,我们现在将REST翻译成“资源表现形式的状态转换”,接下来我们来理解一下这个状态转换。
状态转换
我们对REST的翻译进化到了“资源表现形式的状态转换”,比起“表现层状态转换”好像清晰了一些,但总觉得哪里不太对。这个“状态转换”还是不好理解。这里有两个问题:一个是表面上的,即状态转换是什么;另一个是隐含的,即状态转换是如何产生的。
先来解决第一个问题,“状态转换”这个词太学术了,举个例子就明白了。首先创建一个User类:
然后创建一个user对象:
接下来将user对象的年龄修改成19:
这个修改年龄的操作让user对象的状态发生了转换,也就是说,状态转换说的是资源发生了变化。第一个问题解决了,下面我们来看第二个问题——状态转换是如何产生的?要解决这个问题,需要用到Roy Thomas Fielding的另一个身份——HTTP协议的设计者。这个问题跟HTTP协议有着密切的关系。还记得HTTP中的GET、POST、PUT、DELETE这4个方法吗?“资源”的状态转换正是由HTTP的各种动作(方法)所引起的。
至此,REST的翻译就变成了“资源以某种表现形式在HTTP方法的作用下发生变化”。这样一来,意思就比较明显了,转换其实就是发生了变化,就是改变的意思。而资源的状态发生了改变,其实就是说资源被修改了,也就是REST数据操作的另一种叫法。其实,REST的核心不仅仅是对数据的操作,还包括如何操作,以什么样的规范操作。后面会通过具体的例子来进一步说明RESTfulAPI到底是什么样的。
2.2 RESTful
我们从概念层面对REST风格有了一定的了解,在学习如何设计RESTfulAPI之前,需要弄明白为什么需要REST风格的API。
很久以前,后端工作人员不仅需要Java功底扎实,还需要熟练掌握HTML、CSS、JS、JSTL、EL表达式等前端技术,此外,对EasyUI、jQuery、dwz等技术用得也是相当顺手。随着Angular、React、Vue等前端框架的崛起,以及移动互联网的迅猛发展,前/后端分离的开发模式逐渐占据了主流。这些跟RESTful有什么关系呢?前/后端分离,移动互联网的爆发,导致后端服务不仅要为Web端提供支持,还要为移动端(如Android、iOS等)提供支持,那么对API的设计就显得格外重要了。API需要多端共享、多端统一,否则就会给后期的维护和扩展带来重大的灾难。这个时候,RESTful凭借它结构清晰、易于理解、方便扩展的特性成了设计良好API的不二之选。
2.3 RestFul实战
前面介绍了很多关于REST的内容,那么RESTful风格的API究竟是什么样的呢?下面我们通过几个实例体会一下。
RESTful风格的API要满足以下要求:
- 用URI定位资源
- URI由名词组成
- 使用HTTP方法操作资源
上面的描述比较抽象,相信你通过下表会有更清晰的认识。
获取数据
接下来我们编写一个RESTful风格的接口。获取一个ID为1的用户信息,可以像下面这样使用RESTful风格来编写:
其中,http://localhost:8080/rest/user/1
用来定位ID为1的用户(资源);GET是HTTP的一个方法,用来表示获取、查询;请求/user/1也都是由名词组成的。
对应的代码很简单,我们在第4章已经接触过了,后台代码的实现大致如下
通过这个例子,我们对RESTful风格的接口有了更加形象的认识。上面演示了一个查询数据的操作,同时通过前面讲解的HTTP部分,我们知道对数据的基本操作有4种——增删改查(CRUD)。HTTP中也有4个方法——GET(查询)、POST(新增)、PUT(更新)、DELETE(删除)对应这4种操作。下面我们来补齐剩下的3个方法。
新增数据
使用HTTP的POST方法新增数据:
Content-Type就是我们前面提到的表现形式。
POST方法参数通常会被放到请求体(RequestBody)中,以Content-Type中的格式(JSON)提交到服务端:
使用@RequestBody注解接收前端传来的参数:
更新数据
使用HTTP的PUT方法更新数据:
PUT方法与POST方法很相似,但请求参数稍有不同,除了要更新的值,我们还要指定一个唯一字段(如id),用于告诉服务器需要更新哪条数据:
服务端的代码实现几乎与POST方法一样:
PATCH方法也用于更新数据,但两者语义略有不同。PUT方法用于整体更新,PATCH方法用于局部更新。
删除数据
使用HTTP的DELETE方法删除数据:
服务端的代码实现几乎与GET方法一样:
通过学习上面的内容,相信你对REST的设计风格已经有了一个比较清楚的认识,当有人再问你:“有Free Style吗?”你可以告诉他:“我有RESTStyle。”“纸上得来终觉浅”,想要更好地掌握REST Style,还需要多加实践。
三 URL与URI
不知道你有没有注意到,上面描述REST规范的时候用的是URI,而不是我们更为熟悉的URL。它们两个有什么区别和联系呢?想要弄清楚它们之间的关系,需要引入第三方——URN。这里我们不过多介绍,只是简单说明URI和URL的关系。
- URI:Uniform Resource Identifier,统一资源标识符
- URL:Uniform Resource Locator,统一资源定位符
- URN:Uniform Resource Name,统一资源名称
3.1 关系
我们先不管它们3个具体是什么,先来看看它们之间的关系,如图所示。
由图可知,URL和URN都是URI的子集。换句话说,就是所有的URL和URN都可以称为URI。
RFC3986 中 有 一 句 话 : A URI can be further classified as alocator,a name,or both.这句话更明确地说明了它们之间的关系。
3.2 区别
现在我们知道,所有的URL和URN都是URI,那么,URL和URN有什么区别呢?其实它们的区别就隐藏在名称的最后一个单词里。
- URL:Uniform Resource Locator
- URN:Uniform Resource Name
Locator表示定位符;Name表示名称。两者都是对资源的唯一描述(在指定范围内),但URN只是对资源的描述,而通过URL可以直接找到资源。下面通过一个例子来更形象地体会一下:
第1条是一位大佬之前写的一个专利的线上地址,属于URL。它是唯一的,并且可以通过它找到对应的专利信息。
第2条是专利编号,属于URN。虽然它也是唯一的,但是并不能通过它直接定位到该专利。