欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

荐 RESTful API 设计规范

程序员文章站 2022-04-01 11:31:47
目录文章目录目录RESTRESTful APIRESTful API 设规范REST早古时期,软件和网络是两个不同的领域,前者围绕着单机环境展开,而后者则研究系统之间的通信。随着互联网的兴起,使得这两个领域开始融合,首当其冲的就是基于 HTTP 协议的 Web 服务,越来越多的人开始意识到,“网站” 即是 “软件”。其中的先驱者就是 Tim Berners-Lee(万维网的发明者,万维网联盟负责人)和 Roy Thomas Fielding(1996 HTTP/1.0、1999 HTTP/1.1 的...

目录

REST

早古时期,软件和网络是两个不同的领域,前者围绕着单机环境展开,而后者则研究系统之间的通信。随着互联网的兴起,使得这两个领域开始融合,首当其冲的就是基于 HTTP 协议的 Web 服务,越来越多的人开始意识到,“网站” 即是 “软件”。

其中的先驱者就是 Tim Berners-Lee(万维网的发明者,万维网联盟负责人)和 Roy Thomas Fielding(1996 HTTP/1.0、1999 HTTP/1.1 的主要设计者之一,Apache 基金会的第一任主席,Apache Web Server 和 HTTP 协议是共生共荣的关系)等人。

1989年,Tim Berners-Lee 在论文中提出可以在互联网上构建超链接文档,并提出了三点基本要素:

  1. URI(Uniform Resource Identifier):统一资源标识符,是资源(Resource)在互联网中的唯一标识。
  2. HTML(Hyper Text Markup Language):超文本标记语言,超文本文档是由 HTML 标签组成的描述性文本,HTML 标签将文字,图形、动画、声音、表格、链接等内容格式进行了统一。
  3. HTTP(Hyper Text Transfer Protocol):超文本传输协议,传输超文本文档的传输协议,传输的数据主体称为 Message(消息)。

首先我们需要对 Resource 的概念有一个清晰的理解。所谓 Resource,就是互联网上的一个实体,或者叫具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体实物的抽象。在互联网中使用 URI 来唯一标记一个 Resource,包含了 URL 和 URN。

  • URL(Uniform Resource Loader):统一资源定位符,侧重于 “定位” 二字。
  • URN(Uniform Resource Name):统一资源名称,侧重于 “命名” 二字。

举个例子:寻找一个具体的人(URI)。使用地址来寻找就是 URL:XX省XX市XX区XX单元XX室的主人;使用身份证号和名字来寻找就是 URN:身份证号 + 名字(但无法确认人的地址)。两者各有场景,但需要注意的是 URL、URN 都是 URI 的子集,但日常生活中最常见的是 URL,所以大家口头上也习惯使用 URL 来说明一个 Resource。但在实际编码中,开发者仍要注意 URI 和 URL 的本质区别,注意规范选词。

荐
                                                        RESTful API 设计规范

REST(Representational State Transfer,表现层状态转移)最初被 Roy Thomas Fielding 在 2000 年的博士论文《Architectural Styles and the Design of Network-based Software Architectures(架构风格和基于网络的软件架构设计)》中提出。顾名思义,Roy Thomas Fielding 在这篇论文中主要讨论的是:如何在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。

可见,REST、HTTP 协议、Web 网站,三者之间天生联系紧密。基于此,我们要清晰理解 Representational State Transfer 含义的前提是要对 HTTP 协议有一定的认识。

Representational State Transfer 的含义:

  • Representational(表现层):即 Resources 的表现层。上述我们知道 Resource 是一种抽象,它具有多种表现形式,而最终把 Resource 具体呈现出来的形式,就叫做它的 “表现层”。例如:一个文本资源可以使用 txt 格式表现,也可以使用 HTML、XML、JSON 等格式表现。在 HTTP/1.0 中,Roy Thomas Fielding 为 HTTP Header 设计了 Accept/Content-Type 字段来描述这个 Resource 的 Representational。

所以,需要注意的是:URI 仅用于表示一个 Resource,不应该在 URI 中描述表现层的内容,一个优雅的 RESTful API 应该使用 Accept/Content-Type 字段来描述 Resource 的表现层。

Accept: Application/json
Content-Type: Application/json
  • State Transfer(状态转移):Resource 作为 C/S 交互的实体,必然存在着状态的变化,然而 HTTP 是一种无状态的协议(不传输资源状态的描述)。这意味着:Resource 的状态都保存在服务端,客户端想要操作某个 Resource(改变其状态)就必须通过某种手段让 Resource 在服务端发生状态的 “转移”,而且这种 “转移” 必然是建立在 Resource 的 “表现层” 之上的,例如:创建一个图片资源、删除一个图片资源;启动一个服务,关闭一个服务。故称之为 “表现层状态转移”。

而客户端使用的手段就是 Roy Thomas Fielding 在 HTTP 请求行中设计的 Request Methods(在 HTTP/0.9 中首次引入 GET 方法,在 HTTP/1.0 中首次引入了 POST、Head 方法,在 HTTP/1.1 中引入了 PUT 方法)。

# 客户端
GET /resource
Accept: text/html

# 服务端
Content-Type: text/html;charset=utf-8
Response code: 200

至此,我们回头再看,REST 讨论的其实是一个:如何将软件和网络两个领域进行交叉融合,继而得到一个功能强、性能好、适宜通信的互联网软件架构的问题。

虽然 REST 架构在今天(2020)的软件设计中随处可见,但了解其诞生的历史背景和发展历程,还是会对这些先驱者们产生由衷的敬意。

RESTful API

*-ful 在西文语境中常用于表示一种风格,RESTful API 就是符合 REST 架构设计思想的软件 API 风格。

笔者最早认识到 RESTful API,源于时任 AWS CTO 的一封内部邮件,那时是 2006 年,正值 AWS 孵化的初期,印象非常深刻的有两点:

  1. 强调 AWS 必须是一种 Resilient software architecture(具有韧性/弹性的软件架构)。
  2. 不使用 RESTful API 的员工将被辞退。

直到 2013 年笔者接触 OpenStack(OpenStack 初期常被认为是 AWS 的开源对标版本)之后才更深刻的体会到了其内涵和精髓。一言以蔽之就是:大型分布式软件的各个组件之间必须具备解耦和扩展的能力,网络(Network-base)是组件之间通信的唯一依赖,且对资源的操作具有唯一的确定性

退一步的,我们可以选取一个角度来比较一下 RESTful 与另外两种常见的分布式架构风格的区别:

  • RESTful 抽象的是资源,资源的抽象不需要依赖开发平台或编程语言,客户端和服务端完全松耦合。
  • DO(Distributed Objects,分布式对象)抽象的是对象,而不同的编程语言对对象的定义有很大差别,所以 DO 架构通常会与某种编程语言(.NET)绑定,若跨语言交互,实现则会非常复杂。架构实例有 RMI、EJB/DCOM、.NET Remoting 等。
  • RPC(Remote Procedure Call,远程过程调用)抽象的是过程,这要求客户端和服务端具有很强的耦合度,否者双方无法理解对方的意图。所以 RESTful 只需要以名词为核心的,而 RPC 则需要以动词为核心。架构实例有 SOAP、XML-RPC、Flash AMF 等。

AWS 奉行的这一铁律,使其得以在几年间飞速扩展至上百个核心组件,成为了极具韧性和良好生态的公有云架构。现在的软件公司基本都会采用 RESTful API 风格,让产品可以通过 API 的方式融入到行业生态中,形成 APIs 经济效益。

RESTful API 设计规范

核心原则只有一句话:总是围绕着 Resources 进行建模,HTTP URI 标识资源,HTTP Request Methods 操作资源

URI

  • 资源命名使用名词 + “_” 组合。
  • 资源命名使用复数。
  • 资源命名尽量与关系型数据库表结构命名一致。
  • URI 突出版本号。
  • URI 不具有表现层描述。
  • URL 使用 “/” 划分层级。
  • 避免使用多级 URI。

Request

使用 HTTP Request Methods 时,要注意方法的 “安全性” 和 “幂等性”:

  • 状态性:指调用 HTTP Request Method,是否会导致资源状态变化。
  • 幂等性:指多次调用 HTTP Request Method,只要输入不变,那么执行的结果也是不变的。
Method 功能 描述 状态转移 幂等
GET SELECT 获取一个(提供资源 ID)或多个(提供 Filter 条件)或全部资源,获取多个时使用 Query Parameters 进行过滤查询,使用 Pagination Parameters(e.g. limit、offset、page、sortby)进行分页查询。
POST CREATE 创建一个资源(杜绝一次创建多个资源)。 X X
PUT UPDATE 更新一个资源(提供完整的资源数据)。 X
PATCH UPDATE 更新一个资源(提供部分的资源数据)。 X
DELETE DELETE 删除一个资源。 X
HEAD 获取资源的元数据而非资源本身。此方法经常被用来测试超文本链接的有效性,可访问性,和最近的改变,例如:获取虚拟机镜像的属性信息。
OPTIONS 获取信息,关于资源的哪些属性是由客户端决定的。在跨域或使用代理请求时,通常会用到,OPTION 请求在于判定资源的选项或需求,或者服务器的能力。

Response

Method 功能 描述
GET SELECT 返回一个或多个资源的完整数据和 Status Code,返回一个时使用 {},返回多个时使用 [];若错误需要返回错误原因和正确提示。
POST CREATE 返回一个资源的完整数据和 Status Code;若错误需要返回错误原因和正确提示。
PUT UPDATE 返回一个资源的完整数据和 Status Code;若错误需要返回错误原因和正确提示。
PATCH UPDATE 返回一个资源的完整数据和 Status Code;若错误需要返回错误原因和正确提示。
DELETE DELETE 仅返回 Status Code;若错误需要返回错误原因和正确提示。

HTTP Response Status Codes(状态码)就是一个三位数,分成五个类别:

  • 1xx:相关信息(不常用)
    荐
                                                        RESTful API 设计规范

  • 2xx:操作成功
    荐
                                                        RESTful API 设计规范

  • 3xx:重定向(不常用)
    荐
                                                        RESTful API 设计规范

  • 4xx:客户端错误
    荐
                                                        RESTful API 设计规范

  • 5xx:服务器错误
    荐
                                                        RESTful API 设计规范

登录认证问题

登录认证是 RESTful API 设计中的一个特殊课题,登陆验证源于用户使用 Web 应用时记录用户身份状态的需求,其特点是:

  • 客户端和服务端都记录了用户的账户信息,客户端请求时携带账户信息到服务端进行身份认证。
  • 认证成功后,一次登录持久在线,或者设定登陆状态的过期时间。

荐
                                                        RESTful API 设计规范

在 RESTful 设计中通常使用 Cookies 或 Token 的方式来实现登陆验证的需求:

Cookie 方式:因为 HTTP 协议是无状态的,所以一般使用 Cookie 来解决会话状态的保存,以弥补无法进行会话跟踪的不足。

  • 在登陆时,在服务端进行身份认证,通过则创建 Session,记录用户登陆状态及相关信息,并将 Session ID 返回;
  • 客户端接收响应后,将 Session ID 存放在 Cookie 中(浏览器可自动记录并存储);
  • 客户端的后续请求,直接在 Request Header 中携带对应 Cookie 中存放的 Session ID;
  • 服务端接收请求后,验证 Session ID 是否合法,若合法则进行相关处理并给出响应。

Token 方式

  • 在登陆时,在服务端进行身份认证,通过则返回 Token,Token 具有过期时间。
  • 客户端的后续请求,都携带者 Token 进行请求。返回client后,client需要通过脚本控制存放token信息;

显然,Token 是更加推荐的方式,因为 Cookie 方式会在 HTTP Header 中保持一个状态(Session ID)。这个状态会要求该请求只能被存储了对应的 Session 进行处理,这一点违背了 REST 架构的原则。

动作类型的资源定义

现实情况中,总有一些场景(资源)是 HTTP Request Methods 所抽象不了的。上述的登陆验证是一个典型的例子。针对登陆验证场景,可以把用户在远程服务器的会话信息抽象为一个资源,这样的话,登陆动作其实就是在远程服务器增加了一个会话资源,反正,登出就是删除一个会话资源,所以 RESTful API 可以这样设计:

[POST] /login
[DELETE] /logout

再比如,网上汇款场景,将汇款的动作定义为一种服务:

[POST]  /smsService
{"mobile":"13813888888","text":"hello world"}

再比如,OpenStack 的虚拟机操作 start、stop、reboot、migration 等,将这些操作定义为一个 os-action 资源,然后通过不同在 Request Body 中使用不同的内容来进行区分:

[POST]  /servers/{uuid}
{"os-action": "start"}
{"os-action": "stop"}

简而言之,如果某些动作是 HTTP Method 动词所表示不了的,就应该把这个动作做成一种资源。

HATEOAS

Roy Thomas Fielding 在论文中还提到了 HATEOAS(Hypermedia as the Engine of Application State,超媒体作为应用状态的引擎)的概念。

所谓 “应用状态”,即:客户端的状态,可以理解为会话状态。服务端以 HyperMedia(超媒体的)形式将资源展示在客户端,当客户端访问其中的超媒体的链接(URI)时,就可以获取该链接关联的资源或者可以对资源执行特定的操作。获取的资源或者经特定操作响应后的资源在经过同样 Resource Request Handler 确定表现层后,继续以超媒体的形式在表现在客户端中。而这种资源内容或形式的改变都会导致客户端会话状态的改变,所以媒体就成为了驱动客户端会话状态转换的引擎(应用状态的改变基于超媒体的改变)。

简而言之,HATEOAS 就是不断的在 Response Body 加入 HyperMedia API 链接,以供客户端进行调用。

通常的,API 的调用者完全掌握了 URI 是怎么设计的。一个解决方法就是在 Response Body 中给出相关链接,便于客户端进行下一步操作的判断。使得用户需要查阅繁琐的文档,也知道下一步应该怎么做。

Github 的 API 就实现了 HATEOAS,访问 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://developer.github.com/v3"
}

服务器的返回中提示了相关文档的网址。

HATEOAS 格式并没有统一的标准,上面例子中,GitHub 就将它们与其他属性放在了一起。其实,更好的做法应该是将相关链接与其他属性分开在不同的区间中:

HTTP/1.1 200 OKContent-Type: application/json

{
  "status": "In progress",
   "links": {[
    { "rel":"cancel", "method": "delete", "href":"/api/status/12345" } ,
    { "rel":"edit", "method": "put", "href":"/api/status/12345" }
  ]}
}

OpenStack 也大量的使用到了这种设计:

HTTP/1.1 200 OK
Content-Type: application/json

{"servers": [{
    "status": "ACTIVE",
    "links": [{
        "href": "http://192.168.10.111:8774/v2.1/e5ab2182bb984f3bb4773d4a83672549/servers/95f684d4-0802-484e-b852-7ded35a8eeb5",
        "rel": "self"
    }, {
        "href": "http://192.168.10.111:8774/e5ab2182bb984f3bb4773d4a83672549/servers/95f684d4-0802-484e-b852-7ded35a8eeb5",
        "rel": "bookmark"
    }],
    "image": {
        "id": "be4e8e37-226f-4784-b19d-a439400edca0",
        "links": [{
            "href": "http://192.168.10.201:8774/e5ab2182bb984f3bb4773d4a83672549/images/be4e8e37-226f-4784-b19d-a439400edca0",
            "rel": "bookmark"
        }]
    },
    "flavor": {
        "id": "ed218eec-1e00-4ea9-93e7-f6e4e7c0ba93",
        "links": [{
            "href": "http://192.168.10.201:8774/e5ab2182bb984f3bb4773d4a83672549/flavors/ed218eec-1e00-4ea9-93e7-f6e4e7c0ba93",
            "rel": "bookmark"
        }]
    },
    "id": "95f684d4-0802-484e-b852-7ded35a8eeb5",
    ......
}]}

本文地址:https://blog.csdn.net/Jmilk/article/details/107591440

相关标签: 分布式系统