Restful API 近年来应用越来越广泛,各大互联网公司纷纷推出了自己的 Restful API 服务。
本文将从实际应用出发,从 REST 到 Restful 再到 Restful API ,逐一进行介绍和分析。
REST 风格
REST 风格最早由 Roy Thomas Fielding 博士提出, REST 是一种系统架构设计风格,主要面向基于网络的软件架构设计。这一架构风格,包含了以下一些基本要求:
客户-服务器
在 REST 风格中,最基本的要求就是对于一个程序来说,应当分离用户接口和数据存储,改善用户接口跨平台迁移的可移植性,同时简化服务器组件,改善系统的可伸缩性。
对于这一点,目前我们所接触到的大多数应用都已经使用了这一模式,无论是浏览器,还是自主开发的客户端程序,其根本的实现方式都是采用了 客户-服务器 模式。客户端负责与用户之间的交互处理,而服务器端则实现数据存储以及相关的业务逻辑。
同时,对于服务器端,完整的系统大部分情况下都会包含多个不同的模块,这些模块之间的调用也应当遵循 客户-服务器 的模式,模块之间通过接口进行互相访问。
无状态
服务端在设计接口时,应当设计为无状态接口。也就是说,服务器端不保存任何与客户端相关的状态上下文信息,客户端在每次调用服务端接口时,需要提供足够的信息,以供服务端完成操作。
在无状态的设计中,服务端减少了保存客户端相关上下文数据,因此,一方面服务端能够更加容易的实现动态扩展,而不至于影响客户端使用;另一方面则减少了服务端从故障中恢复的任务量。
但无状态也会带来额外的问题。客户端将需要保存完整的用户状态信息,在每次与服务端交互时可能需要增加与用户状态相关的上下文信息,这样将导致请求数据的重复和增大。
缓存
根据接口的实际情况,应当在接口设计中增加缓存策略,服务端可以决定是否可以缓存当前返回的数据。通过此种方式,可以在一定程度上减少实际到达服务端请求,从而提高网络访问性能。
但缓存需要谨慎使用,缓存哪些数据,缓存过期时间都是需要根据实际情况进行设计。适当的缓存可以有效的提高系统效率,但是如果设计不当,将有可能导致大量的过期数据,进而影响系统运行。
一般而言,数据字典类数据、修改频率非常低的数据、实时性要求很低的数据等,这些数据可以设计一定的缓存策略,以提高系统运行效率。
系统分层
在设计系统,尤其是大型系统,通常需要将系统按照不同的功能进行横向和纵向的分层。例如横向分层一般可分为交互层、服务层、数据层等,而纵向分层则通常会按照不同的业务功能对系统进行切分。经过分层后,系统将划分为不同的模块进行独立开发部署运行。系统分层后,不同的模块可以独立进化,实现功能解耦,提高整个系统的可扩展性。
统一接口
统一接口,即是不同系统模块之间的调用接口统一规范,使用统一的调用协议,统一的数据格式等。统一接口带来的是系统交互的规范化,接口调用与业务解耦,各模块独立进化。
以上即是对于 REST 风格的解释说明。REST 本身是一种系统架构设计风格,因此其更加偏向理论性。以下的内容将专注于实际场景中运用 REST 风格。
Restful 实现
REST 风格是面向网络的软件架构设计风格,其针对的是基于网络的软件架构。当软件架构设计符合 REST 风格时,则可以描述为该设计是 Restful 的。而在实际场景中,REST 论文中描述了应用 REST 风格基本方式,即 REST (表述性状态转移)。
面向资源
网络上的一个实体,一个具体信息,都可以描述为一个 资源 ,资源可以是文本、图片、音频、服务等具体存在。在网络中,每种资源都对应与一个 URI (统一资源标识符)地址,通过 URI 就可以访问到该资源。而我们通常的上网,即是对资源的各种操作。
在 Restful 架构中,所有的接口应当采用面向资源的接口设计,即对于接口的访问地址指向其 URI 地址。
表述性
资源在网络上呈现出来的可能是多种形式,例如 HTML 、 XML 、 JSON 、图片等等。而客户端与服务器之间则传输的是资源的这种具体表现形式。客户端与服务端的互动,本质上就是通过这些表现形式,实现对资源的操作。
按照面向资源接口设计的要求,通常所见到的 URI 地址中,*.html / *.xml / *.json 等扩展名,其实都指向了当前资源的具体表现形式,而 URI 严格意义上仅指向了资源实体,并不包含具体表现形式。
状态转移
为了使操作资源,也即使资源发生状态转移,按照 REST 的要求,客户端若想要操作服务端资源,需要通过 HTTP 协议进行操作。而在 HTTP 协议中,规定了若干用于具体操作的动词,指向了不同的操作类型。
一般而言,对于资源的操作可以表示 CRUD 四类最基本的操作,即 增删改查 。而 HTTP 协议中的通常用以下动词表示这四类具体的操作:
-
GET :查询资源操作。
-
POST :新建资源操作,也可以用于更新资源。
-
PUT :更新资源操作。
-
DELETE :删除资源操作。
在实际应用中,客户端与服务端之间的交互,即是建立在 HTTP 协议之上,通过面向资源的接口地址,使用 HTTP 协议动词作为操作描述,进而实现客户端与服务端的交互过程。
Restful API 设计
在 REST 提出多年来,当前对于 REST 风格的应用最多的即是 Restful API 。
Restful API 一般分为对外和对内。对外的 Restful API 为面向公网的公共服务接口,此类接口一般可以通过公网直接访问,或是经过一定的安全认证后通过公网访问。而对内的 Restful API 则主要是一整套系统内部各个子系统或模块之间交互的标准接口,相对于对外的 Restful API 接口,内部 API 接口更加标准化。
按照 REST 的要求,Restful API 的设计可以总结出以下的一些具体要求。
HTTPS + 域名
Restful API 的无状态性,要求客户端需要在调用接口时传入足够的信息以供服务端用于操作指定的资源,这就有可能使得接口数据在网络传输过程中遭到拦截导致更多的数据泄漏。因此在提供 Restful API ,特别是对外的 API 时,应当尽可能的使用 HTTPS 协议,以确保传输过程的安全。
另一方面,在 API 地址中使用域名,可以进一步解耦服务端与客户端,服务端可以更加容易的迁移和扩展,而不会影响服务端的使用。
例如:
https://open.domain.com/
实际应用过程中,使用 HTTPS 协议,更多应用与对外的 Restful API 接口,而对内网的 Restful API 来说,可以在信任内网安全的前提下,使用 HTTP 协议,以降低复杂度,提高效率。
URL 指向资源,HTTP 动词指向操作
按照 REST 的要求,Restful API 的 URL 地址应指向具体的一个资源,例如用户 user 。URL 中应当只包含资源名词,不应该包含指向操作的动词,例如新建、查询、修改、删除等。具体操作通过 HTTP 动词( GET / POST / PUT / DELETE )指定。
例如,传统的访问地址,获取用户信息:
https://open.domain.com/app/getUser
此时的 URL 地址指向了 获取用户 这一具体的操作过程,这也就是传统的 RPC 形式。而按照 Restful API 的设计,该实例将设计为如下形式:
https://open.domain.com/app/user
// HTTP GET 请求
此时的 URL 地址指向了 user 这一资源实体,而通过 HTTP 协议中的 GET 动词指定了该请求为 获取用户 请求。
指定 API 版本号
在设计 Restful API 时,特别是对外的 API ,通常需要考虑 API 多版本的问题,因为 API 会进行升级,而客户端则处于不可控状态,可能无法及时对 API 调用过程进行配合升级。因此,服务端需要提供对不同版本 API 的支持,同时,客户端在调用 API 时也需要指定特定的版本号,以确保调用过程正常进行。
版本号的指定,可以在 URL 中,也可以在 HTTP 头信息中。
例如,在 URL 中指定版本号:
https://open.domain.com/v1/app/user
这种指定版本号的方式相对简单直观,但将导致指向统一资源的 URL 产生多个,增加了管理 URL 的成本和复杂度。
例如,在 HTTP 头信息中增加版本号:
https://open.domain.com/app/user
HTTP 头:
API-Vsersion: 1
以上实例中,在 HTTP 请求的头信息中增加了自定义字段,用于表示 Restful API 版本。这种方式不会产生多个 URL ,但其问题是不够直观和简洁,客户端和服务端增加了区分版本号的难度,在在一些特定情况下,例如浏览器端的 HTTP 请求,难以对 HTTP 请求的头信息进行更改。
指定参数
在 Restful API 请求中,可能需要根据不同的情况进行过滤,需要增加操作参数。一般来说,针对 GET 和 DELTE 请求需要增加操作参数的情况较多,而 POST 和 PUT 更多的是通过 HTTP 报文体提供操作数据信息。
指定参数可以通过两种方式:URL 地址参数,? 参数。
URL 地址参数:即在 URL 之后,继续按照 URL 格式拼接参数,服务端接到请求后,通过识别 URL 中字符串的位置,获取不同的参数。
例如:
https://open.domain.com/app/user/123456/Admin
在以上实例中, 123456 代表用户 ID 参数值, Admin 则代表用户类型参数值,后台解析该 URL 后即可根据字符串位置获取到特定的参数。
? 参数:即通过 QueryString 查询字符串的形式,拼接到 URL 之后,查询字符串的格式如下:
?key1=value1&key2=value2&...
例如:
https://open.domain.com/app/user?id=123456&type=Admin
以上实例中,服务端通过解析 ? 之后的字符串获取特定的参数。
使用 JSON 作为返回数据格式
相对于 XML 格式来说,JSON 格式更加简洁易用,占用数据量更小,在以网络为基础,使用 HTTP 协议的 Restful API 中,使用 JSON 作为返回数据格式更加合理。
例如:
// JSON 格式
{"name":"user","type":"Admin"}
// XML 格式
<user><name>user</name><type>Admin</type></user>
使用安全认证机制
在使用 Restful API ,特别是对公网开放的 Restful API,通常需要通过一定的安全认证机制来进行实现访问控制。目前主流的方案是通过 OAuth2.0 实现安全认证。
Restful API 分析
Restful API 的兴起,证明了其具备相比于传统 RPC 接口的优势。当同样的,Restful API 在实际应用过程中,也存在这自己的劣势和问题。
Restful API 优势
Restful API 充分利用了 HTTP 协议的设计,使用面向资源的接口设计,相对于传统 RPC 降低了接口设计的复杂度。
例如,使用传统 RPC 形式设计针对用户对象的 CRUD 操作:
// 新建用户
https://open.domain.com/app/addUser
// 查询用户
https://open.domain.com/app/retrieveUser
// 更新用户
https://open.domain.com/app/updateUser
// 删除用户
https://open.domain.com/app/deleteUser
在以上实例中,需要通过四个 URL 来实现 CRUD 操作。而通过 Restful API 设计,可为如下实例:
// GET: 查询用户;POST: 新建用户;PUT: 更新用户;DELETE: 删除用户
https://open.domain.com/app/user
在以上实例中,通过 HTTP 动词指定了不同的 CRUD 操作,将接口 URL 简化为了同一个地址,仅需要改变 HTTP 动词即可实现不同的操作。
另一方面,相对于 SOAP/XML 形式的 RPC 服务,Restful API 采用 HTTP/JSON 的形式传递数据,降低了传输数据量,同时提高了数据解析的效率,单位时间内的负载能力会高于 SOAP WebService 服务。
例如,对于 SOAP WebService 来说,基本的请求响应格式如下:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
</soapenv:Header>
<soapenv:Body>
</soapenv:Body>
</soapenv:Envelope>
而对于 Restful API 来说,其请求格式符合 HTTP 协议的要求,返回格式则符合标准的 JSON 格式即可。
Restful API 劣势
Restful API 面向资源设计接口,而对于一些复杂操作来说,接口设计难度将远大于 RPC 形式。
例如,用户登录验证,使用 RPC 形式设计接口如下:
https://open.domain.com/app/checkUser
在以上实例中,对于 Restful API 而言,很难将该业务归类为 HTTP 动词中的某一种。又比如“下单”这一功能,它涉及到了订单、用户、支付账户等多个资源实体的多种操作,因此同样也难以设计为 Restful API 。
Restful API 具备高效简洁的特点,但这也因此造成 Restful API 没有类似于 SOAP 协议的规范性协议,Restful API 中的数据格式、标准、安全性等都需要由开发者决定,这也就造成了无法建立统一的 Restful API 标准,作为客户端可能需要适配多种格式的 Restful API 。
Restful API 可能遇到的问题
当 Restful API 存在多版本时,服务端需要维护多版本的接口,这会导致更多的维护成本,而且随着 API 的不断升级,多版本的成本将会越来越高,如何通知客户端进行接口版本升级同样也是可能遇到的问题。
当使用 URL 地址参数形式时,对于可选参数,服务端难以实现正确的读取。在 API 存在可选参数的情况下,URL 地址中的参数位置是不固定的,因此服务端很难判断参数所处的正确位置。
针对特定的业务功能,难以完全按照 Restful 要求进行设计。例如用户验证、订单提交等涉及到多个资源实体和多种操作的业务流程,其一个接口中需要完成多项相关联的操作,若使用 RPC 形式设计接口,则设计为一个统一的接口即可,而使用 Restful API 则可能出现多个接口且接口间存在调用依赖,这将会增加客户端和服务端的处理难度。
Restful 本身并没有安全性方面的标准,需要根据不同的使用场景设计 API 的安全控制方案。目前常用的安全方案即是通过 OAuth2.0 进行安全认证,但不同的 API 在安全认证机制方面也会存在一定的差别,如果设计统一完善的安全机制,也是需要考虑的问题。
Restful API 使用场景
根据 Restful API 的特定,其应用场景可以参考以下的场景:
-
资源集中型服务,例如针对用户的信息查询,针对订单的信息查询的等,这类型服务以资源实体为中心,操作大多为简单的 CRUD 操作,业务逻辑简单。
-
访问量大,且对访问时效要求比较高的服务。Restful API 相对于 SOAP WebService 来说,数据量更小,解析更快,在网络环境下能够提高访问的速度和承载能力。
-
面向公网的,且安全性要求较低的开放型 API 服务。这类服务通常由开发者向公网的所有使用者开放,Restful API 的形式能够简化服务调用过程,提高访问效率。
对于复杂业务操作,例如保全申请提交,理赔申请提交等,使用 Restful API 形式难以进行设计,因此此类的业务可以使用传统 RPC 的接口形式进行设计,SOAP WebService 或者 HTTP/JSON 形式的 RPC 都是可行的选择,使用 RPC 形式反而会更加简单。
选择哪种方式的接口设计,需要根据实际的应用情况进行调整,没有最好的,只有最合适的。
传统 RPC 服务改造
在已经成型的系统当中,多数调用采用了 RPC 形式进行设计,并使用 SOAP WebService 作为具体的实现方式,而且开发者也已经习惯了使用了 RPC 的形式设计服务接口。在这种情况下,若想要向 Restful API 形式改造,将付出大量的改造成本。已有系统中存在错综复杂的调用逻辑,而接口也多数都是面向过程的,这种情况下改造接口,需要在接口设计上进行大幅改动才能够实现 Restful API。同时,开发者也需要在设计开发接口时转变思想,以面向资源的方式进行接口设计,这也是需要面临的问题。
而对于新建系统来说,不存在历史接口,则可以从头按照 REST 风格进行设计,接口可设计为 Restful API,其成本要小于对已有系统的改造。
参考文章:
-
《架构风格与基于网络的软件架构设计》 Roy Thomas Fielding 博士论文
-
《理解RESTful架构》 http://www.ruanyifeng.com/blog/2011/09/restful.html
-
《RESTful API 设计指南》 http://www.ruanyifeng.com/blog/2014/05/restful_api.html
-
《SOAP Webservice和RESTful Webservice》 http://blog.sina.com.cn/s/blog_493a845501012566.html