【API】什么是 RESTful ? 看着一篇就够了

RESTful(REST 风格)是一种当前比较流行的互联网软件架构模式,它充分并正确地利用 HTTP 协议的特性,为我们规定了一套统一的资源获取方式,以实现不同终端之间(客户端与服务端)的数据访问与交互。

什么是 REST

说到 REST,我们可能会想到英文单词 rest(意为:休息、放松等),但这里的 REST 实际上是 Resource Representational State Transfer 的缩写,翻译成中文就是“表现层资源表述状态转。

REST 是 Roy Thomas Fielding 博士在 2000 年写的一篇关于软件架构风格的论文中提出的,此文一出,可谓是震惊四方!国内外越来越多的互联网公司都纷纷采用 REST 风格来进行 Web 服务的开发,我们习惯上将使用 REST 风格开发出来的 Web 服务称为 RESTful Web Services,简称 REST 服务。
在这里插入图片描述
Fielding 是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。
他这样介绍论文的写作目的:
“本文研究计算机科学两大前沿----软件和网络----的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。”
(This dissertation explores a junction on the frontiers of two research disciplines in computer science: software and networking. Software research has long been concerned with the categorization of software designs and the development of design methodologies, but has rarely been able to objectively evaluate the impact of various design choices on system behavior. Networking research, in contrast, is focused on the details of generic communication behavior between systems and improving the performance of particular communication techniques, often ignoring the fact that changing the interaction style of an application can have more impact on performance than the communication protocols used for that interaction. My work is motivated by the desire to understand and evaluate the architectural design of network-based application software through principled use of architectural constraints, thereby obtaining the functional, performance, and social properties desired of an architecture. )

我们可以从以下 3 个角度来理解 REST。

1. Resource(资源)

当我们把 Web 工程部署到服务器(例如 Tomcat)中之后,那么这个工程中的所有内容在都可以被称为这个服务器中的资源。它可以是一个类、一个 HTML 文件、一个 CSS 文件、一个 JS 文件、数据库中的一张表、一段文本、一张图片、一段音频等等,它们都可以被称为资源。而服务器则可以看作是由许许多多离散的资源组成的。

这些资源都有一个共同的特征,那就是它们都可以通过一个 URI(统一资源标识符) 进行标识,任何对于该资源的操作都不能改变其 URI。想要获取这个资源,只要访问它的 URI 即可。

2. Representation(资源的表述)

资源的表述指的是资源在某个特定时刻的状态的描述,即资源的具体表现形式,它可以有多种格式,例如 HTML、XML、JSON、纯文本、图片、视频、音频等等。通常情况下,服务端与客户端资源的表述所有使用的格式往往是不同的,例如在服务端资源可能是数据库中的一段纯文本、一个 XML 文件、或者是数据库中的一张表,而客户端则可能是表现为 HTML 页面、JSON、甚至是音频和视频。

3.State Transfer(状态转移)

所谓的资源状态转移,简单点说就是,客户端与服务端进行交互时,资源从一种表现形式转换到另一种表现形式的过程。但是 HTTP 协议是一种无状态协议,它是无法保存任何状态的,因此如果客户端想要获取服务器上的某个资源,就必须通过某种手段让资源在服务器端发生“状态转化”,而这种状态转化又是建立在应用的表现层(UI)上的。这就是“表现层资源状态转移”的含义。

REST 实际上描述的是服务器与客户端的一种交互形式,REST 本身并不是一个实用的概念,真正实用的是如何设计 RESTFul(REST 风格)的接口,即我们到底通过什么样的手段让资源在服务器端发生状态转移。

RESTFul

在传统的项目开发中,我们通常都会将操作资源的动词写进 URL 中,而这些动词通常都是我们自行定义的,并没有一个统一的规范。莎士比亚说:一千个人眼中就有一个千个哈姆雷特,这句话应用在这里,再合适不过了。哪怕是对同一资源的相同操作,不同的人所定义的 URL 也是各不相同的。

例如,同样都是通过用户 ID 获取用户信息的请求,其 URL 可能是以下多种形式。

  • http://localhost:8080/biancheng/getUserById?id=1
  • http://localhost:8080/biancheng/user/getById?id=1
  • http://localhost:8080/biancheng/getUserInfo?id=1
  • http://localhost:8080/biancheng/a/b?id=1

RESTFul 提倡我们使用统一的风格来设计 URL,其规则如下。

  1. URL 只用来标识和定位资源,不得包含任何与操作相关的动词。例如访问与用户(user)相关的资源时,其 URL 可以定义成以下形式。
http://localhost:8080/biancheng/user
  1. 当请求中需要携带参数时,RESTFul 允许我们将参数通过斜杠(/)拼接到 URL 中,将其作为 URL 的一部分发送到服务器中,而不再像以前一样使用问号(?)拼接键值对的方式来携带参数,示例如下。
http://localhost:8080/biancheng/user/1

注:我们在 URL 的末尾通过 “/1”的形式传递了一个取值为 1 的参数。

  1. HTTP 协议中有四个表示操作方式的动词:GET、POST、PUT 和 DELETE,它们分别对应了四种与资源相关的基本操作: GET 用来获取资源, POST 用来新建资源, PUT 用来更新资源, DELETE 用来删除资源。客户端通过这四个动词,即可实现对服务器端资源状态转移的描述。
资源操作 传统方式 URL RESTFul URL HTTP 请求方式
获取资源(SELECT) http://localhost:8080/biancheng/getUserById?id=1 http://localhost:8080/biancheng/user/1 GET
保存或新增资源(INSERT) http://localhost:8080/biancheng/saveUser http://localhost:8080/biancheng/user POST
修改或更新资源(UPDATE) http://localhost:8080/biancheng/updateUser http://localhost:8080/biancheng/user PUT
删除资源(DELETE) http://localhost:8080/biancheng/deleteUser?id=1 http://localhost:8080/biancheng/user/1 DELETE

RESTful 最佳实践与设计细节

它的大原则容易把握,但是细节不容易做对。本文总结 RESTful 的设计细节,介绍如何设计出易于理解和使用的 API。

一、请求设计

1.1 动词 + 宾语

RESTful 的核心思想就是,客户端发出的数据操作指令都是”动词 + 宾语”的结构。比如,GET /articles 这个命令,GET是动词,/articles是宾语。

动词通常就是五种 HTTP 方法,对应 CRUD 操作。

  • GET:读取(Read)
  • POST:新建(Create)
  • PUT:更新(Update)
  • PATCH:更新(Update),通常是部分更新
  • DELETE:删除(Delete)

根据 HTTP 规范,动词一律大写。

1.2 动词的覆盖

有些客户端只能使用GETPOST这两种方法。服务器必须接受POST模拟其他三个方法(PUTPATCHDELETE)。

这时,客户端发出的 HTTP 请求,要加上X-HTTP-Method-Override属性,告诉服务器应该使用哪一个动词,覆盖POST方法。

POST /api/Person/4 HTTP/1.1  
X-HTTP-Method-Override: PUT

上面代码中,X-HTTP-Method-Override指定本次请求的方法是PUT,而不是POST

1.3 宾语必须是名词

宾语就是 API 的 URL,是 HTTP 动词作用的对象。它应该是名词,不能是动词。比如,/articles这个 URL 就是正确的,而下面的 URL 不是名词,所以都是错误的。

  • /getAllCars
  • /createNewCar
  • /deleteAllRedCars

1.4 复数 URL

既然 URL 是名词,那么应该使用复数,还是单数?

这没有统一的规定,但是常见的操作是读取一个集合,比如GET /articles(读取所有文章),这里明显应该是复数。

为了统一起见,建议都使用复数 URL,比如GET /articles/2要好于GET /article/2

1.5 避免多级 URL

常见的情况是,资源需要多级分类,因此很容易写出多级的 URL,比如获取某个作者的某一类文章。

GET /authors/12/categories/2

这种 URL 不利于扩展,语义也不明确,往往要想一会,才能明白含义。

更好的做法是,除了第一级,其他级别都用查询字符串表达。

GET /authors/12?categories=2

下面是另一个例子,查询已发布的文章。你可能会设计成下面的 URL。

GET /articles/published

查询字符串的写法明显更好。

GET /articles?published=true

二、响应状态码

2.1 状态码必须精确

客户端的每一次请求,服务器都必须给出回应。回应包括 HTTP 状态码和数据两部分。

HTTP 状态码就是一个三位数,分成五个类别。

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

这五大类总共包含100多种状态码,覆盖了绝大部分可能遇到的情况。每一种状态码都有标准的(或者约定的)解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。

API 不需要1xx状态码,下面介绍其他四类状态码的精确含义。

2.2 2xx 状态码

200状态码表示操作成功,但是不同的方法可以返回更精确的状态码。

  • GET: 200 OK
  • POST: 201 Created
  • PUT: 200 OK
  • PATCH: 200 OK
  • DELETE: 204 No Content

上面代码中,POST返回201状态码,表示生成了新的资源;DELETE返回204状态码,表示资源已经不存在。

此外,202 Accepted状态码表示服务器已经收到请求,但还未进行处理,会在未来再处理,通常用于异步操作。下面是一个例子。

HTTP/1.1 202 Accepted

{
  "task": {
    "href": "/api/company/job-management/jobs/2130040",
    "id": "2130040"
  }
}

2.3 3xx 状态码

API 用不到301状态码(永久重定向)和302状态码(暂时重定向,307也是这个含义),因为它们可以由应用级别返回,浏览器会直接跳转,API 级别可以不考虑这两种情况。

API 用到的3xx状态码,主要是303 See Other,表示参考另一个 URL。它与302307的含义一样,也是”暂时重定向”,区别在于302307用于GET请求,而303用于POSTPUTDELETE请求。收到303以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办。下面是一个例子。

HTTP/1.1 303 See Other
Location: /api/orders/12345

2.4 4xx 状态码

4xx状态码表示客户端错误,主要有下面几种。

400 Bad Request:服务器不理解客户端的请求,未做任何处理。

401 Unauthorized:用户未提供身份验证凭据,或者没有通过身份验证。

403 Forbidden:用户通过了身份验证,但是不具有访问资源所需的权限。

404 Not Found:所请求的资源不存在,或不可用。

405 Method Not Allowed:用户已经通过身份验证,但是所用的 HTTP 方法不在他的权限之内。

410 Gone:所请求的资源已从这个地址转移,不再可用。

415 Unsupported Media Type:客户端要求的返回格式不支持。比如,API 只能返回 JSON 格式,但是客户端要求返回 XML 格式。

422 Unprocessable Entity :客户端上传的附件无法处理,导致请求失败。

429 Too Many Requests:客户端的请求次数超过限额。

2.5 5xx 状态码

5xx状态码表示服务端错误。一般来说,API 不会向用户透露服务器的详细信息,所以只要两个状态码就够了。

500 Internal Server Error:客户端请求有效,服务器处理时发生了意外。

503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态。

三、响应体

3.1 不要返回纯本文

API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。所以,服务器回应的 HTTP 头的Content-Type属性要设为application/json

客户端请求时,也要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的ACCEPT属性也要设成application/json。下面是一个例子。

GET /orders/2 HTTP/1.1 
Accept: application/json

3.2 发生错误时,不要返回 200 状态码

有一种不恰当的做法是,即使发生错误,也返回200状态码,把错误信息放在数据体里面,就像下面这样。

HTTP/1.1 200 OK
Content-Type: application/json{
  "status": "failure",
  "data": {
    "error": "Expected at least two items in list."
  }
}

上面代码中,解析数据体以后,才能得知操作失败。

这张做法实际上取消了状态码,这是完全不可取的。正确的做法是,状态码反映发生的错误,具体的错误信息放在数据体里面返回。下面是一个例子。

HTTP/1.1 400 Bad Request
Content-Type: application/json{
  "error": "Invalid payoad.",
  "detail": {
     "surname": "This field is required."
  }
}

3.3 提供链接

API 的使用者未必知道,URL 是怎么设计的。一个解决方法就是,在回应中,给出相关链接,便于下一步操作。这样的话,用户只要记住一个 URL,就可以发现其他的 URL。这种方法叫做 HATEOAS。

举例来说,GitHub 的 API 都在 api.github.com 这个域名。访问它,就可以得到其他 URL。

{
  ...
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  ...
}

上面的回应中,挑一个 URL 访问,又可以得到别的 URL。对于用户来说,不需要记住 URL 设计,只要从 api.github.com 一步步查找就可以了。

HATEOAS 的格式没有统一规定,上面例子中,GitHub 将它们与其他属性放在一起。更好的做法应该是,将相关链接与其他属性分开。

HTTP/1.1 200 OK
Content-Type: application/json{
  "status": "In progress",
   "links": {[
    { "rel":"cancel", "method": "delete", "href":"/api/status/12345" } ,
    { "rel":"edit", "method": "put", "href":"/api/status/12345" }
  ]}
}

参考:
http://c.biancheng.net/spring_mvc/9675.html
https://www.ruanyifeng.com/blog/2011/09/restful.html

猜你喜欢

转载自blog.csdn.net/ihero/article/details/132109673