restful风格概述

RESTful 架构风格概述

本文首发于Gevin的博客

原文链接:RESTful 架构风格概述

未经Gevin授权,禁止转载

在移动互联网的大潮下,随着docker等技术的兴起,『微服务』的概念也越来越被大家接受并应用于实践,日益增多的web service逐渐统一于RESTful 架构风格,如果开发者对RESTful 架构风格不甚了解,则开发出的所谓RESTful API总会貌合神离,不够规范。

本文是我对RESTful 架构风格的一些理解,和大家分享一下,如果有问题,欢迎讨论。
Outline

1. RESTful架构风格
    1.1 RESTful架构风格的特点
        1.1.1 资源
        1.1.2 统一接口
        1.1.3 URI
        1.1.4 无状态
    1.2 ROA、SOA、REST与RPC
    1.3 本真REST与hybrid风格
2. 认证机制
    2.1 Basic Auth
    2.2 Token Auth
    2.3 OAuth
3. 总结
  1. RESTful架构风格

RESTful架构风格最初由Roy T. Fielding(HTTP/1.1协议专家组负责人)在其2000年的博士学位论文中提出。HTTP就是该架构风格的一个典型应用。从其诞生之日开始,它就因其可扩展性和简单性受到越来越多的架构师和开发者们的青睐。一方面,随着云计算和移动计算的兴起,许多企业愿意在互联网上共享自己的数据、功能;另一方面,在企业中,RESTful API(也称RESTful Web服务)也逐渐超越SOAP成为实现SOA的重要手段之一。时至今日,RESTful架构风格已成为企业级服务的标配。

REST即Representational State Transfer的缩写,可译为”表现层状态转化”。REST最大的几个特点为:资源、统一接口、URI和无状态。
1.1 RESTful架构风格的特点
1.1.1 资源

所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。资源总要通过某种载体反应其内容,文本可以用txt格式表现,也可以用HTML格式、XML格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现;JSON是现在最常用的资源表示格式。

结合我的开发实践,我对资源和数据理解如下:

资源是以json(或其他Representation)为载体的、面向用户的一组数据集,资源对信息的表达倾向于概念模型中的数据:

资源总是以某种Representation为载体显示的,即序列化的信息
常用的Representation是json(推荐)或者xml(不推荐)等
Represntation 是REST架构的表现层

相对而言,数据(尤其是数据库)是一种更加抽象的、对计算机更高效和友好的数据表现形式,更多的存在于逻辑模型中

资源和数据关系如下:

resource vs data
1.1.2 统一接口

RESTful架构风格规定,数据的元操作,即CRUD(create, read, update和delete,即数据的增删查改)操作,分别对应于HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口,仅通过HTTP方法,就可以完成对数据的所有增删查改工作。

即:

GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。
PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)。
DELETE(DELETE):从服务器删除资源。

1.1.3 URI

可以用一个URI(统一资源定位符)指向资源,即每个URI都对应一个特定的资源。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或识别符。

一般的,每个资源至少有一个URI与之对应,最典型的URI即URL。
1.1.4 无状态

所谓无状态的,即所有的资源,都可以通过URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而改变。有状态和无状态的区别,举个简单的例子说明一下。如查询员工的工资,如果查询工资是需要登录系统,进入查询工资的页面,执行相关操作后,获取工资的多少,则这种情况是有状态的,因为查询工资的每一步操作都依赖于前一步操作,只要前置操作不成功,后续操作就无法执行;如果输入一个url即可得到指定员工的工资,则这种情况是无状态的,因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个url与之对应,可以通过HTTP中的GET方法得到资源,这是典型的RESTful风格。

state

stateless
1.2 ROA、SOA、REST与RPC

ROA即Resource Oriented Architecture,RESTful 架构风格的服务是围绕资源展开的,是典型的ROA架构(虽然“A”和“架构”存在重复,但说无妨),虽然ROA与SOA并不冲突,甚至把ROA看做SOA的一种也未尝不可,但由于RPC也是SOA,比较久远一点点论文、博客或图书也常把SOA与RPC混在一起讨论,因此,RESTful 架构风格的服务通常被称之为ROA架构,很少提及SOA架构,以便更加显式的与RPC区分。

RPC风格曾是Web Service的主流,最初是基于XML-RPC协议(一个远程过程调用(remote procedure call,RPC)的分布式计算协议),后来渐渐被SOAP协议(简单对象访问协议(Simple Object Access Protocol))取代;RPC风格的服务,不仅可以用HTTP,还可以用TCP或其他通信协议。但RPC风格的服务,受开发服务采用语言的束缚比较大,如.NET框架中,开发web service的传统方式是使用WCF,基于WCF开发的服务即RPC风格的服务,使用该服务的客户端通常要用C#来实现,如果使用python或其他语言,很难实现可以直接与服务通信客户端;进入移动互联网时代后,RPC风格的服务很难在移动终端使用,而RESTful风格的服务,由于可以直接以json或xml为载体承载数据,以HTTP方法为统一接口完成数据操作,客户端的开发不依赖于服务实现的技术,移动终端也可以轻松使用服务,这也加剧了REST取代RPC成为web service的主导。

RPC与RESTful的区别如下面两个图所示:

blog-post-REST-vs-RPC1

blog-post-REST-vs-RPC2
1.3 本真REST与hybrid风格

通常开发者做服务相关的客户端开发时,使用的所谓RESTful服务,基本可分为本真REST和hybrid风格两类。本真REST即我上文阐述的RESTful架构风格,具有上述的4个特点,是真正意义上的RESTful风格;而hybrid风格,只是借鉴了RESTful的一些优点,具有一部分RESTful的特点,但对外依然宣称是RESTful风格的服务。(窃以为,正是由于hybrid风格服务混淆了RESTful的概念,才在RESTful架构风格提出了本真REST的概念,以为了划分界限 :P)

hybrid风格的最主流的用法是,使用GET方法获取资源,用POST方法实现资源的创建、修改和删除。hybrid风格之所以存在,据我了解有两种来源:一种情况是因为,某些开发者并没有真正理解何为RESTful架构风格,导致开发的服务貌合神离;而主流的原因是由于历史包袱 —— 服务本来是RPC风格的,由于上文提到的RPC的劣势及RESTful的优势,开发者在RPC风格的服务上又包装了一层RESTful的外壳,通常这层外壳只为获取资源服务,因此会按RESTful风格实现GET方法,如果客户端提出一些简单的创建、修改或删除数据的需求,则通过HTTP协议中最常用的POST方法实现相应功能。

因此,开发RESTful 服务,如果没有历史包袱,不建议使用hybrid风格。
2. 认证机制

stateless-auth

由于RESTful风格的服务是无状态的,认证机制尤为重要。例如上文提到的员工工资,这应该是一个隐私资源,只有员工本人或其他少数有权限的人有资格看到,如果不通过权限认证机制对资源做一层限制,那么所有资源都以公开方式暴露出来,这是不合理的,也是很危险的。

认证机制解决的问题是,确定访问资源的用户是谁;权限机制解决的问题是,确定用户是否被许可使用、修改、删除或创建资源。权限机制通常与服务的业务逻辑绑定,因此权限机制需要在每个系统内部定制,而认证机制基本上是通用的,常用的认证机制包括 session auth(即通过用户名密码登录),basic auth,token auth和OAuth,服务开发中常用的认证机制为后三者。
2.1 Basic Auth

HTTP Basic authentication (BA) implementation is the simplest technique for enforcing access controls to web resources because it doesn't require cookies, session identifier and login pages. Rather, HTTP Basic authentication uses static, standard fields in the HTTP header which means that no handshakes have to be done in anticipation.

Visit Wikipedia To Read More

简言之,Basic Auth是配合RESTful API 使用的最简单的认证方式,只需提供用户名密码即可,但由于有把用户名密码暴露给第三方客户端的风险,在生产环境下被使用的越来越少。因此,在开发对外开放的RESTful API时,尽量避免采用Basic Auth
2.2 Token Auth

Token Auth并不常用,它与Basic Auth的区别是,不将用户名和密码发送给服务器做用户认证,而是向服务器发送一个事先在服务器端生成的token来做认证。因此Token Auth要求服务器端要具备一套完整的Token创建和管理机制,该机制的实现会增加大量且非必须的服务器端开发工作,也不见得这套机制足够安全和通用,因此Token Auth用的并不多。

本文不在展开介绍Token Auth,我个人对这套机制也了解有限,有兴趣了解这套机制的同学不妨从Stack Overflow上的这篇讨论入手。
2.3 OAuth

OAuth is an open standard for authorization. OAuth provides client applications a 'secure delegated access' to server resources on behalf of a resource owner. It specifies a process for resource owners to authorize third-party access to their server resources without sharing their credentials. Designed specifically to work with Hypertext Transfer Protocol (HTTP), OAuth essentially allows access tokens to be issued to third-party clients by an authorization server, with the approval of the resource owner. The client then uses the access token to access the protected resources hosted by the resource server. OAuth is commonly used as a way for Internet users to log into third party websites using their Microsoft, Google, Facebook or Twitter accounts without exposing their password.

OAuth is a service that is complementary to and distinct from OpenID. OAuth is also distinct from OATH, which is a reference architecture for authentication, not a standard for authorization. However, OAuth is directly related to OpenID Connect (OIDC) since OIDC is an authentication layer built on top of OAuth 2.0.

Visit Wikipedia To Read More

OAuth(开放授权)是一个开放的授权标准,允许用户让第三方应用访问该用户在某一web服务上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。

OAuth允许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每一个令牌授权一个特定的第三方系统(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth让用户可以授权第三方网站访问他们存储在另外服务提供者的某些特定信息,而非所有内容。

正是由于OAUTH的严谨性和安全性,现在OAUTH已成为RESTful架构风格中最常用的认证机制,和RESTful架构风格一起,成为企业级服务的标配。

目前OAuth已经从OAuth1.0发展到OAuth2.0,但这二者并非平滑过渡升级,OAuth2.0在保证安全性的前提下大大减少了客户端开发的复杂性,因此,Gevin建议在实战应用中采用OAuth2.0认证机制。

现在网上关于OAuth的资料非常丰富,也有大量开源的第三方库实现了OAuth机制,不熟悉OAuth的同学从OAuth官网入手即可。
3. 总结

本真REST + OAuth是RESTful 是微服务的标配
Basic Auth只在开发环境中使用
设计合理的资源
用正确的HTTP方法对数据发正确的请求

http://gold.xitu.io/post/57d168e9bf22ec005f98a3a5

基于一些不错的RESTful开发组件,可以快速的开发出不错的RESTful API,但如果不了解开发规范的、健壮的RESTful API的基本面,即便优秀的RESTful开发组件摆在面前,也无法很好的理解和使用。下文Gevin结合自己的实践经验,整理了从零开始开发RESTful API的核心要点,完善的RESTful开发组件基本都会包含全部或大部分要点,对于支持不够到位的要点,我们也可以自己写代码实现。
Outline

Request 和 Response
Serialization 和 Deserialization
Validation
Authentication 和 Permission
CORS
URL Rules
  1. Request 和 Response

RESTful API的开发和使用,无非是客户端向服务器发请求(request),以及服务器对客户端请求的响应(response)。本真RESTful架构风格具有统一接口的特点,即,使用不同的http方法表达不同的行为:

GET(SELECT):从服务器取出资源(一项或多项)
POST(CREATE):在服务器新建一个资源
PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)
PATCH(UPDATE):在服务器更新资源(客户端提供需要修改的资源数据)
DELETE(DELETE):从服务器删除资源

客户端会基于GET方法向服务器发送获取数据的请求,基于PUT或PATCH方法向服务器发送更新数据的请求等,服务端在设计API时,也要按照相应规范来处理对应的请求,这点现在应该已经成为所有RESTful API的开发者的共识了,而且各web框架的request类和response类都很强大,具有合理的默认设置和灵活的定制性,Gevin在这里仅准备强调一下响应这些request时,常用的Response要包含的数据和状态码(status code),不完善的内容,欢迎大家补充:

当GET, PUT和PATCH请求成功时,要返回对应的数据,及状态码200,即SUCCESS
当POST创建数据成功时,要返回创建的数据,及状态码201,即CREATED
当DELETE删除数据成功时,不返回数据,状态码要返回204,即NO CONTENT
当GET 不到数据时,状态码要返回404,即NOT FOUND
任何时候,如果请求有问题,如校验请求数据时发现错误,要返回状态码 400,即BAD REQUEST
当API 请求需要用户认证时,如果request中的认证信息不正确,要返回状态码 401,即NOT AUTHORIZED
当API 请求需要验证用户权限时,如果当前用户无相应权限,要返回状态码 403,即FORBIDDEN

最后,关于Request 和 Response,不要忽略了http header中的Content-Type。以json为例,如果API要求客户端发送request时要传入json数据,则服务器端仅做好json数据的获取和解析即可,但如果服务端支持多种类型数据的传入,如同时支持json和form-data,则要根据客户端发送请求时header中的Content-Type,对不同类型是数据分别实现获取和解析;如果API响应客户端请求后,需要返回json数据,需要在header中添加Content-Type=application/json。
2. Serialization 和 Deserialization

Serialization 和 Deserialization即序列化和反序列化。RESTful API以规范统一的格式作为数据的载体,常用的格式为json或xml,以json格式为例,当客户端向服务器发请求时,或者服务器相应客户端的请求,向客户端返回数据时,都是传输json格式的文本,而在服务器内部,数据处理时基本不用json格式的字符串,而是native类型的数据,最典型的如类的实例,即对象(object),json仅为服务器和客户端通信时,在网络上传输的数据的格式,服务器和客户端内部,均存在将json转为native类型数据和将native类型数据转为json的需求,其中,将native类型数据转为json即为序列化,将json转为native类型数据即为反序列化。虽然某些开发语言,如Python,其原生数据类型list和dict能轻易实现序列化和反序列化,但对于复杂的API,内部实现时总会以对象作为数据的载体,因此,确保序列化和反序列化方法的实现,是开发RESTful API最重要的一步准备工作

题外话,序列化和反序列化的便捷,造就了RESTful API跨平台的特点,使得REST取代RPC成为Web Service的主流

序列化和反序列化是RESTful API开发中的一项硬需求,所以几乎每一种常用的开发语言都会有一个或多个优秀的开源库,来实现序列化和反序列化,因此,我们在开发RESTful API时,没必要制造重复的轮子,选一个好用的库即可,如python中的marshmallow,如果基于Django开发,Django REST Framework中的serializer即可。
3. Validation

Validation即数据校验,是开发健壮RESTful API中另一个重要的一环。仍以json为例,当客户端向服务器发出post, put或patch请求时,通常会同时给服务器发送json格式的相关数据,服务器在做数据处理之前,先做数据校验,是最合理和安全的前后端交互。如果客户端发送的数据不正确或不合理,服务器端经过校验后直接向客户端返回400错误及相应的数据错误信息即可。常见的数据校验包括:

数据类型校验,如字段类型如果是int,那么给字段赋字符串的值则报错
数据格式校验,如邮箱或密码,其赋值必须满足相应的正则表达式,才是正确的输入数据
数据逻辑校验,如数据包含出生日期和年龄两个字段,如果这两个字段的数据不一致,则数据校验失败

以上三种类型的校验,数据逻辑校验最为复杂,通常涉及到多个字段的配合,或者要结合用户和权限做相应的校验。Validation虽然是RESTful API 编写中的一个可选项,但它对API的安全、服务器的开销和交互的友好性而言,都具有重要意义,因此,Gevin建议,开发一套完善的RESTful API时,Validation的实现必不可少。
4. Authentication 和 Permission

Authentication指用户认证,Permission指权限机制,这两点是使RESTful API 强大、灵活和安全的基本保障。

常用的认证机制是Basic Auth和OAuth,RESTful API 开发中,除非API非常简单,且没有潜在的安全性问题,否则,认证机制是必须实现的,并应用到API中去。Basic Auth非常简单,很多框架都集成了Basic Auth的实现,自己写一个也能很快搞定,OAuth目前已经成为企业级服务的标配,其相关的开源实现方案非常丰富(更多)。

我在《RESTful 架构风格概述》中,对认证机制做了更加详细的描述,有兴趣的同学不妨阅读相关章节。

权限机制是对API请求更近一步的限制,只有通过认证的用户符合权限要求,才能访问API。权限机制的具体实现通常依赖于系统的业务逻辑和应用场景,generally speaking,常用的权限机制主要包含全局型的和对象型的,全局型的权限机制,主要指通过为用户赋予权限,或者为用户赋予角色或划分到用户组,然后为角色或用户组赋予权限的方式来实现权限控制,对象型的权限机制,主要指权限控制的颗粒度在object上,用户对某个具体对象的访问、修改、删除或其行为,要单独在该对象上为用户赋予相关权限来实现权限控制。

全局型的权限机制容易理解,实现也简单,有很多开源库可做备选方案,不少完善的web开发框架,也会集成相关的权限逻辑,object permission 相对难复杂一点,但也有很多典型的应用场景,如多人博客系统中,作者对自己文章的编辑权限即为object permission,其对应的开源库也有很多。

注: 我写过一篇《Django权限机制的实现》,Django 开发者可做延伸阅读。

开发一套完整的RESTful API,权限机制必须纳入考虑范围,虽然权限机制的具体实现依赖于业务,权限机制本身,是有典型的模式存在的,需要开发者掌握基本的权限机制实现方案,以便随时应用到API中去。
5. CORS

CORS即Cross-origin resource sharing,在RESTful API开发中,主要是为js服务的,解决javascript 调用 RESTful API时的跨域问题。

由于固有的安全机制,js的跨域请求时是无法被服务器成功响应的。现在前后端分离日益成为web开发主流方式的大趋势下,后台逐渐趋向指提供API服务,为各客户端提供数据及相关操作,而网站的开发全部交给前端搞定,网站和API服务很少部署在同一台服务器上并使用相同的端口,js的跨域请求时普遍存在的,开发RESTful API时,通常都要考虑到CORS功能的实现,以便js能正常使用API。

目前各主流web开发语言都有很多优秀的实现CORS的开源库,我们在开发RESTful API时,要注意CORS功能的实现,直接拿现有的轮子来用即可。

更多关于CORS的介绍,有兴趣的同学可以查看阮一峰老师的跨域资源共享 CORS 详解
6. URL Rules

RESTful API 是写给开发者来消费的,其命名和结构需要有意义。因此,在设计和编写URL时,要符合一些规范。Url rules 可以单独写一篇博客来详细阐述,本文只列出一些关键点。
6.1 Version your API

规范的API应该包含版本信息,在RESTful API中,最简单的包含版本的方法是将版本信息放到url中,如:

/api/v1/posts/
/api/v1/drafts/

/api/v2/posts/
/api/v2/drafts/

另一种优雅的做法是,使用HTTP header中的accept来传递版本信息,这也是GitHub API 采取的策略。
6.2 Use nouns, not verbs

RESTful API 中的url是指向资源的,而不是描述行为的,因此设计API时,应使用名词而非动词来描述语义,否则会引起混淆和语义不清。即:

Bad APIs

/api/getArticle/1/
/api/updateArticle/1/
/api/deleteArticle/1/

上面四个url都是指向同一个资源的,虽然一个资源允许多个url指向它,但不同的url应该表达不同的语义,上面的API可以优化为:

Good APIs

/api/Article/1/

article 资源的获取、更新和删除分别通过 GET, PUT 和 DELETE方法请求API即可。试想,如果url以动词来描述,用PUT方法请求 /api/deleteArticle/1/ 会感觉多么不舒服。
6.3 GET and HEAD should always be safe

RFC2616已经明确指出,GET和HEAD方法必须始终是安全的。例如,有这样一个不规范的API:

The following api is used to delete articles

[GET]

/api/deleteArticle?id=1

试想,如果搜索引擎访问了上面url会如何?
6.4 Nested resources routing

如果要获取一个资源子集,采用 nested routing 是一个优雅的方式,如,列出所有文章中属于Gevin编写的文章:

List Gevin’s articles

/api/authors/gevin/articles/

获取资源子集的另一种方式是基于filter(见下面章节),这两种方式都符合规范,但语义不同:如果语义上将资源子集看作一个独立的资源集合,则使用 nested routing 感觉更恰当,如果资源子集的获取是出于过滤的目的,则使用filter更恰当。

至于编写RESTful API时到底应采用哪种方式,则仁者见仁,智者见智,语义上说的通即可。
6.5 Filter

对于资源集合,可以通过url参数对资源进行过滤,如:

List Gevin’s articles

/api/articles?author=gevin

分页就是一种最典型的资源过滤。
6.6 Pagination

对于资源集合,分页获取是一种比较合理的方式。如果基于开发框架(如Django REST Framework),直接使用开发框架中的分页机制即可,如果是自己实现分页机制,Gevin的策略是:

返回资源集合是,包含与分页有关的数据如下:

{
“page”: 1, # 当前是第几页
“pages”: 3, # 总共多少页
“per_page”: 10, # 每页多少数据
“has_next”: true, # 是否有下一页数据
“has_prev”: false, # 是否有前一页数据
“total”: 27 # 总共多少数据
}

当想API请求资源集合时,可选的分页参数为:
参数 含义
page 当前是第几页,默认为1
per_page 每页多少条记录,默认为系统默认值

另外,系统内还设置一个per_page_max字段,用于标记系统允许的每页最大记录数,当per_page值大于 per_page_max 值时,每页记录条数为 per_page_max。
6.7 Url design tricks

(1)Url是区分大小写的,这点经常被忽略,即:

/Posts
/posts

上面这两个url是不同的两个url,可以指向不同的资源

(2)Back forward Slash (/)

目前比较流行的API设计方案,通常建议url以/作为结尾,如果API GET请求中,url不以/结尾,则重定向到以/结尾的API上去(这点现在的web框架基本都支持),因为有没有 /,也是两个url,即:

/posts/
/posts

这也是两个不同的url,可以对应不同的行为和资源

(3)连接符 - 和 下划线 _

RESTful API 应具备良好的可读性,当url中某一个片段(segment)由多个单词组成时,建议使用 - 来隔断单词,而不是使用 _,即:

Good

/api/featured-post/

Bad

/api/featured_post/

这主要是因为,浏览器中超链接显示的默认效果是,文字并附带下划线,如果API以_隔断单词,二者会重叠,影响可读性。
总结

编写本文的初衷,是为了整理一套从零开始编写规范、安全的RESTful API的基本思路。本文介绍了开发RESTful API时,要考虑的基本内容,对于类似Flask这种天生支持RESTful风格的web框架,不依赖其他RESTful第三方库开发RESTful 服务时,可以从本文内容入手;不少强大的RESTful 库,虽然其相关接口基本涵盖了本文的全部或大部分内容,但本文的总结,相信对这些库的理解和使用也是有帮助的。

最后,关于如何开发RESTful API,欢迎大家与我交流~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值