在主流公司的程序开发中,为了提高程序开发迭代的速度,基本都是前后端分离架构,而前端既包括网页、App、小程序等等,因此必须要有一个统一的规范用于约束前后端的通信,RESTful API则是目前比较成熟的API设计理论。
要想理解RESTful,就需要先明白REST。REST是 Roy T. Fielding 在其2000年的博士论文中提出的,是RE
presentational S
tate T
ransfer 词组的缩写,可以翻译为“表现层状态转移”,其实这个词组前面省略了个主语--“Resource”,加上主语后就是“资源表现层状态转移”。每个词都能看懂,连在一起不知道什么意思了有木有?
Resource(资源)
所谓资源,就是互联网上的一个实体。URI(Uniform Resource Identifier)的全称就是统一资源标识符,和我们这里的资源一个意思。一个资源可以是一段文字、一张图片、一段音频、一个服务。
表现层(Representation)
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。比如一篇文章,可以使用XML、JSON、HTML的形式呈现出来。
状态转移(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。互联网通信协议HTTP协议,是一个无状态协议,这意味着所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
上面我们介绍了REST的基本概念,那么服务REST规范的设计,我们就可以称为RESTful。接下来我们就来看一下RESTful API的设计规范。
协议是最基本的设计,表示前后端通信规范,现阶段应该使用HTTPs
协议。
API
的根入口点应尽可能保持足够简单,这里有两个常见的例子:
域名应该考虑拓展性,如果不确定API后续是否会拓展,应该将其放在子域名下,这样可以保持一定的灵活性。
路径又称为端点,表示API的具体地址。在路径的设计中,需遵守下列约定:
小写
名词
,并且必须是复数形式
命名必须全部小写和易读都无需解释,可以理解为规定,那么为什么命名必须是名词且需要复数形式呢?这是因为在RESTful中,主语是资源,资源肯定是名词,不能是动词。其次,一个资源往往对应数据库中一张表,表就是实体的集合,因此需要是复数形式。
下面是一些反例:
下面是一些正例:
常见的情况是,资源需要多级分类,因此很容易写出多级的 URL,比如获取某个作者的某一类文章。
GET /authors/12/articles/categories/2
这种 URL 不利于扩展,语义也不明确,往往要想一会,才能明白含义。更好的做法是,除了第一级,其他级别都用查询字符串表达。
GET /authors/12/articles?categories=2
下面是另一个例子,查询已发布的文章。你可能会设计成下面的 URL。
查询字符串的写法明显更好。
GET /articles?published=true
比如用户1的编号为2的文章,就是users/1/articles/2
如果两个资源是平等的,那么一般是作为路由中HTTP方法的参数,
比如你提到的“要表示x轴为2,y轴为3的点”,就一般是用xxx.com/dot?x=2&y=3
对于如何操作资源,有相应的HTTP动词对应,常见的动词有如下五个(括号里表示SQL对应的命令):
示例:
HTTP动词 | 路径 | 表述 |
---|---|---|
GET | /zoos | 获取所有动物园信息 |
POST | /zoos | 新建一个动物园 |
GET | /zoos/ID | 获取指定动物园的信息 |
PUT | /zoos/ID | 更新指定动物园的信息(前端提供该动物园的全部信息) |
PATCH | /zoos/ID | 更新某个指定动物园的部分信息(提供该动物园改动部分的信息,如开门时间) |
DELETE | /zoos/ID | 删除某个动物园 |
GET | /zoos/ID/animals | 获取某个动物园里面的所有动物信息 |
如果数据量很大,服务器不可能将全部数据都返回给前端,因此前端需要提供一些参数进行过滤,用于分页展示或者排序等,下面是一些常见的参数:
HATEOAS 是 Hypermedia As The Engine Of Application State 的缩写,从字面上理解是 “超媒体即应用状态引擎” 。其原则就是客户端与服务器的交互完全由API动态提供,客户端无需事先了解如何与服务器交互,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。
例如,若要处理订单与客户之间的关系,可以在订单的表示形式中包含链接,用于指定下单客户可以执行的操作(查看客户信息、查看订单信息、删除订单等操作)。rel表示这个API与当前网址的关系,href表示API的路径,title表示API的标题,type表示返回类型,action表示支持的操作类型。
{
"orderID":3,
"productID":2,
"quantity":4,
"orderValue":16.60,
"links":[
{
"rel":"customer",
"href":"https://adventure-works.com/customers/3",
"action":"GET",
"title":"get customer info",
"types":["text/xml","application/json"]
},
{
"rel":"self",
"href":"https://adventure-works.com/orders/3",
"action":"GET",
"title":"get order info",
"types":["text/xml","application/json"]
}]
}
Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
{
"current_user_url": "https://api.github.com/user",
"emojis_url": "https://api.github.com/emojis",
"events_url": "https://api.github.com/events",
......
}
API一直保持静态的可能性很小,随着业务需求变化,可能会添加新的资源,底层的数据结构可能也会有更改。在更新API提供新功能的同时,需要考虑对已使用该API用户的影响,因此需要保持向前兼容,这就引出了版本控制。主要的版本控制方法有如下几种:
每次修改 Web API 或更改资源的架构时,向每个资源的 URI 添加版本号。 以前存在的 URI 应像以前一样继续运行,并返回符合原始架构的资源。
api.example.com/v1/*
api.example.com/v2/*
该方法的版本控制机制非常简单,但是随着 API 多次迭代,服务器需要支持多个版本的路由,增大了维护的成本。 此方案也增加了 HATEOAS 实现的复杂性,因为所有链接都需要在其 URI 中包括版本号。
不是提供多个 URI,而是通过在追加查询字符串的方式来指定版本,例如 https://adventure-works.com/customers/3?version=2
。 如果 version 参数被较旧的客户端应用程序省略,则应默认为有意义的值(例如 1)。
此方法具有语义优势(即,同一资源始终从同一 URI 进行检索),但它依赖于代码处理请求以分析查询字符串并发送回相应的 HTTP 响应。 该方法也与 URI 版本控制机制一样,增加了实现 HATEOAS 的复杂性。
在请求的header中自定义版本控制选项。
GET https://adventure-works.com/customers/3 HTTP/1.1
Custom-Header: api-version=1
当客户端应用程序向 Web 服务器发送 HTTP GET 请求时,可以 Accept 标头规定它可以处理的内容的格式。 通常,Accept 标头的用途是客户端指定响应的正文应是 XML、JSON 或者其他可处理的的格式。 但是,我们也可以指定该标头为使客户端需要的资源版本。
GET https://adventure-works.com/customers/3 HTTP/1.1
Accept: application/vnd.adventure-works.v1+json
上例将 Accept 标头指定为 application/vnd.adventure-works.v1+json。 vnd.adventure-works.v1 元素向 Web 服务器指示它应返回资源的版本 v1,而 json 元素则指定响应正文的格式应为 JSON。
此方法可以说是最纯粹的版本控制机制并自然地适用于 HATEOAS,后者可以在资源链接中包含相关数据的 MIME 类型。
在现实世界中,API永远不会完全稳定。因此,如何管理这一变化非常重要。对于大多数API而言,商定好部分版本的控制策略,然后对API详细记录和逐步弃用是可接受的做法。
API响应,需要遵守HTTP设计规范,选择合适的状态码返回。你可能见过有的接口始终返回状态码200,然后通过返回体中的code字段进行区分请求是否成功,这种是不符合规范的,相当于状态码没有了任何作用,下面就是一个反例。
HTTP/1.1 200 ok
Content-Type: application/json
Server: example.com
{
"code": -1,
"msg": "该活动不存在",
}
其次,在出现错误时,需要返回错误信息,常见的返回方式就是放在返回体中。
HTTP/1.1 401 Unauthorized
Server: nginx/1.11.9
Content-Type: application/json
Transfer-Encoding: chunked
Cache-Control: no-cache, private
Date: Sun, 24 Jun 2018 10:02:59 GMT
Connection: keep-alive
{"error_code":40100,"message":"Unauthorized"}
HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,HTTP状态码共分为5种类型:
分类 | 描述 |
---|---|
1xx | 信息,服务器收到请求,需要请求者继续执行操作 |
2xx | 成功,操作被成功接收并处理 |
3xx | 重定向,需要进一步的操作以完成请求 |
4xx | 客户端错误,请求包含语法错误或无法完成请求 |
5xx | 服务器错误,服务器在处理请求的过程中发生了错误 |
API不需要1xx类型的状态码,因此我们主要看下其他几个类型常见的状态码:
状态码 | 英文名称 | 描述 |
---|---|---|
200 | OK | 请求成功,一般用于GET和POST请求 |
201 | Created | 请求成功并创建了新的资源,用于POST、PUT、PATCH请求。例如新增用户、修改用户信息等,同时在返回体中,我们既可以返回创建后实体的所有信息数据,也可以不返回相关信息。 |
202 | Accepted | 已接受请求,但未处理完成,会在未来再处理,通常用于异步操作 |
204 | No Content | 该状态码表示响应实体不包含任何数据,使用DELETE进行删除操作时,需返回该状态码 |
API 用不到301状态码(永久重定向)和302状态码(暂时重定向,307也是这个含义),因为它们可以由应用级别返回,浏览器会直接跳转,API 级别可以不考虑这两种情况。
API 用到的3xx状态码,主要是303 See Other,表示参考另一个 URL。它与302和307的含义一样,也是"暂时重定向",区别在于302和307用于GET请求,而303用于POST、PUT和DELETE请求。收到303以后,浏览器不会自动跳转,而会让用户自己决定下一步怎么办。
下面是一个例子。
HTTP/1.1 303 See Other
Location: /api/orders/12345
状态码 | 英文名称 | 描述 |
---|---|---|
400 | Bad Request | 客户端请求的语法错误,服务器无法理解 |
401 | Unauthorized | 表示用户没有权限(令牌、用户名、密码错误) |
403 | Forbidden | 没有权限访问该请求,服务器收到请求但拒绝提供服务 |
404 | Not Found | 服务器无法根据客户端的请求找到资源(如路径不存在) |
405 | Method Not Allowed | 客户端请求的方法服务端不支持,例如使用 POST 方法请求只支持 GET 方法的接口 |
406 | Not Acceptable | 用户GET请求的格式不可得(比如用户请求 JSON 格式,但是只有 XML 格式) |
408 | Request Time-out | 客户端请求超时 |
410 | Gone | 客户端GET请求的资源已经不存在。410 不同于 404,如果资源以前有现在被永久删除了可使用410 代码,网站设计人员可通过 301 代码指定资源的新位置 |
415 | Unsupported Media Type | 通常表示服务器不支持客户端请求首部 Content-Type 指定的数据格式。如在只接受 JSON 格式的 API 中放入 XML 类型的数据并向服务器发送 |
429 | Too Many Requests | 客户端的请求次数超过限额 |
5xx状态码表示服务端错误。一般来说,API 不会向用户透露服务器的详细信息,所以只要两个状态码就够了。
状态码 | 英文名称 | 描述 |
---|---|---|
500 | Internal Server Error | 客户端请求有效,服务器处理时发生了意外 |
503 | Service Unavailable | 服务器无法处理请求,一般用于网站维护状态 |
更多状态码说明参考:RFC 9110:HTTP 语义状态码部分介绍
www.ruanyifeng.com/blog/2011/0…
https://www.leftso.com/article/2408241915326638.html