RESTful API 设计规范

简介

在主流公司的程序开发中,为了提高程序开发迭代的速度,基本都是前后端分离架构,而前端包括网页、App、小程序等等,因此必须要有一个统一的规范用于约束前后端的通信,RESTful API 则是目前比较成熟的 API 设计理论。

要想理解 RESTful,就需要先明白 REST。REST 是 Roy Thomas Fielding 在其2000年的博士论文中提出的。Fielding 是一个非常重要的人,他是 HTTP 协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。

REST 是REpresentational State Transfer 词组的缩写,可以翻译为"表现层状态转移",其实这个词组前面省略了个主语–“Resource”,加上主语后就是"资源表现层状态转移"。

每个词都能看懂,连在一起就不知道什么意思了?

  1. Resource(资源)

    所谓资源,就是互联网上的一个实体。URI(Uniform Resource Identifier)的全称就是统一资源标识符,和我们这里的资源一个意思。一个资源可以是一段文字、一张图片、一段音频、一个服务。

  2. 表现层(Representation)

    “资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层”(Representation)。比如一篇文章,可以使用 XML、JSON、HTML 的形式呈现出来;一张图片可以用 JPG 格式表现,也可以用 PNG 格式表现。

  3. 状态转移(State Transfer)

    访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化,比如文件被修改,访问数量增加等。互联网通信协议 HTTP 协议,是一个无状态协议,这意味着所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。

上面我们介绍了 REST 的基本概念,那么符合 REST 规范的设计,我们就可以称为 RESTful。

RESTful 架构,是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制,所以正得到越来越多网站的采用。

接下来我们就来看一下 RESTful API 的设计规范。

协议

协议是最基本的设计,表示前后端的通信规范,现阶段应该使用 HTTP/HTTPs 协议。

API 与用户的通信协议,尽量使用 HTTPs 协议。HTTPs 协议的所有信息都是加密传播,第三方无法窃听,具有校验机制,一旦被篡改,通信双方会立刻发现,配备身份证书,防止身份被冒充。

域名

API 的根入口点应尽可能保持足够简单,这里有两个常见的例子:

  • http://api.example.com/* (专用域名下)
  • http://example.com/api/* (主域名下)

域名应该考虑拓展性,尽量将 API 部署在专用域名之下;如果确定 API 很简单,后续不会拓展,可以考虑放在主域名下。

路径

路径又称为端点(Endpoints),表示 API 的具体网址。在路径的设计中,需遵守下列约定:

  • 命名必须全部小写
  • URI 必须是名词,并且必须是复数形式
  • 如果要使用连字符,使用 “-” 而不是 “_”,因为 “_” 字符可能会在某些浏览器或屏幕中被部分遮挡或完全隐藏
  • 易读

命名必须全部小写和易读都无需解释,可以理解为规定,那么为什么命名必须是名词且需要复数形式呢?这是因为在 RESTful 中,主语是资源,资源肯定是名词,不能是动词。其次,一个资源往往对应数据库中一张表,表就是实体的集合,因此需要是复数形式。

下面是一些反例:

  • http://api.example.com/getUser
  • http://api.example.com/addUser

下面是一些正例:

  • http://api.example.com/zoos

  • http://api.example.com/animals

避免多级URI

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

GET /authors/12/categories/2

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

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

GET /authors/12?categories=2

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

GET /articles/published

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

GET /articles?published=true

URI结尾不应包含斜杠"/"

这是作为 URI 路径处理中最重要的规则之一,正斜杠 “/” 不会增加语义值,且可能导致混淆。REST API 不允许一个尾部的斜杠,不应该将它包含在提供给客户端的链接的结尾处。

许多 Web 组件和框架将平等对待以下两个URL:

  • http://api.example.com/zoos/

  • http://api.example.com/zoos

但是,实际上 URI 中的每个字符都会计入资源的唯一身份的识别中。

两个不同的 URI 映射到两个不同的资源。如果 URI 不同,那么资源也应该不同,反之亦然。因此,REST API 必须生成和传递精确的 URI,不能容忍任何的客户端尝试不精确的资源定位。

有些 API 碰到这种情况,可能设计为让客户端重定向到相应没有尾斜杠的 URI(也有可能会返回301 - 用来资源重定向)。

HTTP动词

对于如何操作资源,有相应的 HTTP 动词对应,常见的动词有如下五个:

  • GET:从服务器取出资源(一项或多项)
  • POST:在服务器新建一个资源
  • PUT:在服务器更新资源(客户端提供改变后的完整资源)
  • PATCH:在服务器更新资源(客户端提供改变的部分资源)
  • DELETE:从服务器删除资源

示例:

HTTP动词路径表述
GET/zoos获取所有动物园信息
POST/zoos新建一个新的动物园
GET/zoos/ID获取指定动物园的信息
PUT/zoos/ID更新指定动物园的信息(前端提供该动物园的全部信息)
PATCH/zoos/ID更新指定动物园的某部分信息(前端提供该动物园改动部分的信息)
DELETE/zoos/ID删除某个动物园
GET/zoos/ID/animals获取某个动物园里面的所有动物信息

过滤信息

如果数据量很大,服务器不可能将全部数据都返回给前端,因此前端需要提供一些参数进行过滤,用于分页展示或者排序等,下面是一些常见的参数:

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置
  • ?page=2&per_page=100:指定第几页,以及每页的记录数
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序
  • ?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许 API 路径和 URI 参数偶尔有重复。

HATEOAS

HATEOAS 是 Hypermedia As The Engine Of Application State 的缩写,从字面上理解是"超媒体作为应用状态的引擎" 。其原则就是客户端与服务器的交互完全由 API 动态提供,客户端无需事先了解如何与服务器交互,即返回结果中提供链接,指向其他 API 方法,使用户不查文档,也知道下一步应该做什么。

比如,当用户向 api.example.com 的根目录发出请求,会得到这样一个文档。

{
  "link": 
  {
    "rel":   "collection https://www.example.com/zoos",
    "href":  "https://api.example.com/zoos",
    "title": "List of zoos",
    "type":  "application/json"
  }
}

上面代码表示,文档中有一个 link 属性,用户读取这个属性就知道下一步该调用什么 API 了。

  • rel 表示这个 API 与当前网址的关系(collection 关系,并给出该 collection 的网址)

  • href 表示 API 的路径

  • title 表示 API 的标题

  • type 表示返回类型

Github 的 API 就是这种设计,访问 api.github.com 会得到一个所有可用 API 的网址列表。

{
  "current_user_url": "https://api.github.com/user",
  "authorizations_url": "https://api.github.com/authorizations",
  ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问 api.github.com/user,然后就得到了下面结果。

{
  "message": "Requires authentication",
  "documentation_url": "https://docs.github.com/rest/reference/users#get-the-authenticated-user"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

版本控制

API 一直保持静态的可能性很小,随着业务需求变化,可能会添加新的资源,底层的数据结构可能也会有更改。在更新 API 提供新功能的同时,需要考虑对已使用该 API 用户的影响,因此需要保持向前兼容,这就引出了版本控制。主要的版本控制方法有如下几种:

URI版本控制

每次修改 Web API 或更改资源的架构时,向每个资源的 URI 添加版本号。 以前存在的 URI 应像以前一样继续运行,并返回符合原始架构的资源。

  • http://api.example.com/v1/*
  • http://api.example.com/v2/*

该方法的版本控制机制非常简单,但是随着 API 多次迭代,服务器需要支持多个版本的路由,增大了维护的成本。 此方案也增加了 HATEOAS 实现的复杂性,因为所有链接都需要在其 URI 中包括版本号。

查询字符串版本控制

不是提供多个 URI,而是通过追加查询字符串的方式来指定版本,例如

  • http://api.example.com/zoos/1?version=2

如果 version 参数被较旧的客户端应用程序省略,则应默认为有意义的值(例如 1)。

此方法具有语义优势(即,同一资源始终从同一 URI 进行检索),但它依赖于代码处理请求以分析查询字符串并发送回相应的 HTTP 响应。 该方法也与 URI 版本控制机制一样,增加了实现 HATEOAS 的复杂性。

自定义请求标头进行版本控制

在请求的 header 中自定义版本控制选项。

GET http://api.example.com/zoos/1 HTTP/1.1
Custom-Header: api-version=2

Accept标头进行版本控制

当客户端应用程序向 Web 服务器发送 HTTP GET 请求时,使用 Accept 标头规定它可以处理的内容的格式。 通常,Accept 标头的用途是客户端指定响应的正文应是 XML、JSON 或者其他可处理的格式。 但是,我们也可以指定该标头为客户端需要的资源版本。

GET http://api.example.com/zoos/1 HTTP/1.1
Accept: application/vnd.example.com.v2+json

上例将 Accept 标头指定为 application/vnd.example.com.v2+json。vnd.example.com.v2 元素向 Web 服务器指示它应返回资源的版本是 v2,而 json 元素则指定响应正文的格式应为 JSON。

其中 vnd 表示 Standards Tree 标准树类型,有三个不同的树: xprsvnd

你使用的标准树需要取决于你开发的项目

  • 未注册的树(x)主要表示本地和私有环境
  • 私有树(prs)主要表示没有商业发布的项目
  • 供应商树(vnd)主要表示公开发布的项目

后面几个参数依次为应用名称(一般为应用域名)、版本号、期望的返回格式。

此方法可以说是最纯粹的版本控制机制并自然地适用于 HATEOAS。

在现实世界中,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": 401,
  "message": "Unauthorized"
}

状态码

HTTP 状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,HTTP 状态码共分为5种类型:

分类描述
1xx信息,服务器收到请求,需要请求者继续执行操作
2xx成功,操作被成功接收并处理
3xx重定向,需要进一步的操作以完成请求
4xx客户端错误,请求包含语法错误或无法完成请求
5xx服务器错误,服务器在处理请求的过程中发生了错误

1xx 状态码

API 不需要 1xx 类型的状态码,因此我们主要看下其他几个类型常见的状态码。

2xx 状态码

状态码英文名称描述
200OK请求成功,一般用于 GET 和 POST 请求
201Created请求成功并创建了新的资源,用于 POST、PUT、PATCH 请求。例如新增用户、修改用户信息等,同时在返回体中,我们既可以返回创建后实体的所有信息数据,也可以不返回相关信息
202Accepted已接受请求,但未处理完成,会在未来再处理,通常用于异步操作
204No Content该状态码表示响应实体不包含任何数据,使用 DELETE 进行删除操作时,需返回该状态码

3xx 状态码

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

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

下面是一个例子。

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

4xx 状态码

状态码英文名称描述
400Bad Request客户端请求的语法错误,服务器无法理解
401Unauthorized表示用户没有权限(令牌、用户名、密码错误)
403Forbidden没有权限访问该请求,服务器收到请求但拒绝提供服务
404Not Found服务器无法根据客户端的请求找到资源(如路径不存在)
405Method Not Allowed客户端请求的方法服务端不支持,例如使用 POST 方法请求只支持 GET 方法的接口
406Not Acceptable用户 GET 请求的格式不可得(比如用户请求 JSON 格式,但是只有 XML 格式)
408Request Time-out客户端请求超时
410Gone客户端 GET 请求的资源已经不存在。410 不同于 404,如果资源以前有现在被永久删除了可使用 410 代码,网站设计人员可通过 301 代码指定资源的新位置
415Unsupported Media Type通常表示服务器不支持客户端请求首部 Content-Type 指定的数据格式。如在只接受 JSON 格式的 API 中放入 XML 类型的数据并向服务器发送
429Too Many Requests客户端的请求次数超过限额

5xx 状态码

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

状态码英文名称描述
500Internal Server Error客户端请求有效,服务器处理时发生了意外
503Service Unavailable服务器无法处理请求,一般用于网站维护状态

认证和授权

应该使用 OAuth 2.0 的方式为 API 调用者提供登录认证。必须先通过登录接口获取 Access Token 后再通过该 token 调用需要身份认证的 API。

Oauth 的端点设计示列:

  • Twitter /oauth2/token
  • Fackbook /oauth/access_token
  • Google /o/oauth2/token
  • Github /login/oauth/access_token

服务端在返回 access token 的同时必须在响应中包含一个名为 expires_in 的数据,它表示当前获得的 token 会在多少秒后失效。

{
    "access_token": "token....",
    "token_type": "Bearer",
    "expires_in": 3600
}

客户端在请求需要认证的 API 时,必须在请求头 Authorization 中带上 access_token

Authorization: Bearer token...

当超过指定的秒数后,access token 就会过期,再次用过期/或无效的 token 访问时,服务端应该返回 invalid_token 的错误或 401 错误码。

HTTP/1.1 401 Unauthorized
Content-Type: application/json
Cache-Control: no-store
Pragma: no-cache

{
  "error": "invalid_token"
}

其他

  • 服务器返回的数据格式,应该尽量使用 JSON,避免使用 XML

参考

https://juejin.cn/post/7025222833798119454

https://www.ruanyifeng.com/blog/2011/09/restful.html

https://www.ruanyifeng.com/blog/2014/05/restful_api.html

https://segmentfault.com/a/1190000015384373

RESTful API设计规范是一种用于构建可扩展、灵活且易于理解的API架构风格。以下是一些常见的RESTful API设计规范: 1. 使用合适的HTTP方法:根据操作类型选择合适的HTTP方法,如GET用于获取资源,POST用于创建资源,PUT用于更新资源,DELETE用于删除资源。 2. 使用合适的URI:URI应该被设计成有意义且易于理解的结构。使用名词来表示资源,避免使用动词或操作。 3. 使用合适的HTTP状态码:根据操作的结果返回合适的HTTP状态码。例如,200表示成功,201表示资源创建成功,404表示资源不存在等。 4. 使用版本控制:在URI中包含版本号来管理API的变化,以保持向后兼容性。 5. 使用合适的HTTP头部信息:使用适当的HTTP头部信息来提供额外的元数据,如Content-Type和Accept用于指定请求和响应的数据格式。 6. 使用合适的错误处理机制:对于错误情况,返回合适的错误码和错误信息,并提供清晰的错误处理机制。 7. 使用过滤、分页和排序:对于大量数据的查询,提供过滤、分页和排序的功能,以提高性能和用户体验。 8. 使用合适的安全机制:对于敏感数据或操作,使用适当的身份验证和授权机制,如OAuth2.0。 9. 提供合适的文档和示例:为API提供清晰、详细的文档和示例,以便开发者能够快速理解和使用API。 10. 遵循HATEOAS原则:为API提供超媒体驱动的链接,使客户端能够通过链接发现和导航API的资源。 这些是常见的RESTful API设计规范,根据具体项目需求和团队约定,可能会有所调整和扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cfl927096306

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值