RESTful API 初探


1. 什么是Restful?

Restful是一种架构设计风格,提供了设计原则和约束条件,而不是架构,而满足这些约束条件和原则的应用程序或设计就是 Restful架构或服务。

主要的设计原则:

  •  资源与URI
  •  统一资源接口(HTTP方法如GET,PUT和POST)
  •  资源的表述
  •  资源的链接
  •  状态的转移

总之,RESTful的核心就是后端将资源发布为URI,前端通过URI访问资源,并通过HTTP动词表示要对资源进行的操作。

 

 

2. Richardson的REST成熟度模型

一个WEB服务有多么的“RESTful”,最有名的就是《RESTful Web Services》的合著者Leonard Richardson提出的REST成熟度模型,简称 Richardson成熟度模型!

第0级:使用HTTP作为传输方式;一个URI,一个HTTP方法
SOAP、XML-RPM都属于这一级别,仅是来回传送"Plain Old XML"(POX)。即使没有显式调用RPC接口(SOAP、XML-RPM),通常会调用服务器端的一个处理过程。一个接口会有一个端点,文档的内容会被解析用还判断所要调用的处理过程及其参数。这种做法相当于把 HTTP 这个应用层协议降级为传输层协议用。HTTP 头和有效载荷是完全隔离的,HTTP 头只用于保证传输,不涉及业务逻辑;有效载荷包含全部业务逻辑,因此 API 可以无视 HTTP 头中的任何信息。

 

第1级:引入了资源的概念,每个资源有对应的标识符和表达;多个URI,一个HTTP方法
这些资源仍是被“GETful”接口操作而不是HTTP动词,服务基本上只提供操作这些资源。例如:

1 GET http://example.com/app/createUser
2 GET http://example.com/app/getUser?id=123
3 GET http://example.com/app/changeUser?id=123&field=value
4 GET http://example.com/app/deleteUser?id=123

 

第2级:根据语义使用HTTP动词,适当处理HTTP响应状态码;多个URI,多个HTTP方法
在这一级别,资源名称为基URI的一部分,而不是查询参数。

1 GET(SELECT):从服务器取出资源(一项或多项)。
2 POST(CREATE):在服务器新建一个资源。
3 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
4 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。
5 DELETE(DELETE):从服务器删除资源。
6 HEAD:获取资源的元数据。
7 OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。

 

第3级:使用超媒体作为应用状态引擎(HATEOAS);多个URI,多个HTTP方法
特别注意,这里是超媒体(hypermedia),超媒体概念是包括超文本的。
我们已经知道什么是多媒体(multimedia),以及什么是超文本(hypertext)。其中超文本特有的优势是拥有超链接(hyperlink)。如果我们把超链接引入到多媒体当中去,那就得到了超媒体,因此关键角色还是超链接。使用超媒体作为应用引擎状态,意思是应用引擎的状态变更由客户端访问不同的超媒体资源驱动。

3、URL设计

3.1 动词 + 宾语

RESTful 的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构。比如,GET/articles这个命令,GET是动词,/articles是宾语。
动词通常就是五种 HTTP 方法,对应 CRUD 操作。
根据 HTTP 规范,动词一律大写。
 1 GET:   读取(Read)
 2 POST:  新建(Create)
 3 PUT:   更新(Update)
 4 PATCH: 更新(Update),通常是部分更新
 5 DELETE:删除(Delete)

3.2 动词的覆盖

有些客户端只能使用GETPOST这两种方法。服务器必须接受POST模拟其他三个方法(PUTPATCHDELETE)。

这时,客户端发出的 HTTP 请求,要加上X-HTTP-Method-Override属性,告诉服务器应该使用哪一个动词,覆盖POST方法。

1  POST /api/Person/4 HTTP/1.1  
2  X-HTTP-Method-Override: PUT

上面代码中,X-HTTP-Method-Override指定本次请求的方法是PUT,而不是POST

3.3 宾语必须是名词

宾语就是 API 的 URL,是 HTTP 动词作用的对象。它应该是名词,不能是动词。比如,/articles这个 URL 就是正确的,而下面的 URL 不是名词,所以都是错误的。 

1 /getAllCars
2 /createNewCar
3 /deleteAllRedCars

3.4 复数 URL

既然 URL 是名词,那么应该使用复数,还是单数?

这没有统一的规定,但是常见的操作是读取一个集合,比如GET /articles(读取所有文章),这里明显应该是复数。

为了统一起见,建议都使用复数 URL,比如GET /articles/2要好于GET /article/2

3.5 避免多级 URL

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

GET /authors/12/categories/2 

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

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

GET /authors/12?categories=2

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

GET /articles/published

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

GET /articles?published=true

4、状态码

4.1 状态码必须精确

客户端的每一次请求,服务器都必须给出回应。回应包括 HTTP 状态码和数据两部分。

HTTP 状态码就是一个三位数,分成五个类别。 

1xx:相关信息
2xx:操作成功
3xx:重定向
4xx:客户端错误
5xx:服务器错误

这五大类总共包含100多种状态码,覆盖了绝大部分可能遇到的情况。每一种状态码都有标准的(或者约定的)解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。

API 不需要1xx状态码,下面介绍其他四类状态码的精确含义。

4.2 2xx 状态码

200状态码表示操作成功,但是不同的方法可以返回更精确的状态码。

  • GET: 200 OK
  • POST: 201 Created
  • PUT: 200 OK
  • PATCH: 200 OK
  • DELETE: 204 No Content

上面代码中,POST返回201状态码,表示生成了新的资源;DELETE返回204状态码,表示资源已经不存在。

此外,202 Accepted状态码表示服务器已经收到请求,但还未进行处理,会在未来再处理,通常用于异步操作。下面是一个例子。


HTTP/1.1 202 Accepted
{
  "task": {
    "href": "/api/company/job-management/jobs/2130040",
    "id": "2130040"
  }
}

4.3 3xx 状态码

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

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


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

4.4 4xx 状态码

4xx状态码表示客户端错误,主要有下面几种。

400 Bad Request:服务器不理解客户端的请求,未做任何处理。

401 Unauthorized:用户未提供身份验证凭据,或者没有通过身份验证。

403 Forbidden:用户通过了身份验证,但是不具有访问资源所需的权限。

404 Not Found:所请求的资源不存在,或不可用。

405 Method Not Allowed:用户已经通过身份验证,但是所用的 HTTP 方法不在他的权限之内。

410 Gone:所请求的资源已从这个地址转移,不再可用。

415 Unsupported Media Type:客户端要求的返回格式不支持。比如,API 只能返回 JSON 格式,但是客户端要求返回 XML 格式。

422 Unprocessable Entity :客户端上传的附件无法处理,导致请求失败。

429 Too Many Requests:客户端的请求次数超过限额。

4.5 5xx 状态码

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

500 Internal Server Error:客户端请求有效,服务器处理时发生了意外。

503 Service Unavailable:服务器无法处理请求,一般用于网站维护状态。

5、服务器回应

5.1 不要返回纯本文

API 返回的数据格式,不应该是纯文本,而应该是一个 JSON 对象,因为这样才能返回标准的结构化数据。所以,服务器回应的 HTTP 头的Content-Type属性要设为application/json

客户端请求时,也要明确告诉服务器,可以接受 JSON 格式,即请求的 HTTP 头的ACCEPT属性也要设成application/json。下面是一个例子。


GET /orders/2 HTTP/1.1 
Accept: application/json

5.2 发生错误时,不要返回 200 状态码

有一种不恰当的做法是,即使发生错误,也返回200状态码,把错误信息放在数据体里面,就像下面这样。


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

{
  "status": "failure",
  "data": {
    "error": "Expected at least two items in list."
  }
}

上面代码中,解析数据体以后,才能得知操作失败。

这张做法实际上取消了状态码,这是完全不可取的。正确的做法是,状态码反映发生的错误,具体的错误信息放在数据体里面返回。下面是一个例子。


HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": "Invalid payoad.",
  "detail": {
     "surname": "This field is required."
  }
}

5.3 提供链接

API 的使用者未必知道,URL 是怎么设计的。一个解决方法就是,在回应中,给出相关链接,便于下一步操作。这样的话,用户只要记住一个 URL,就可以发现其他的 URL。这种方法叫做 HATEOAS。

举例来说,GitHub 的 API 都在 api.github.com 这个域名。访问它,就可以得到其他 URL。


{
  ...
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  ...
}

上面的回应中,挑一个 URL 访问,又可以得到别的 URL。对于用户来说,不需要记住 URL 设计,只要从 api.github.com 一步步查找就可以了。

HATEOAS 的格式没有统一规定,上面例子中,GitHub 将它们与其他属性放在一起。更好的做法应该是,将相关链接与其他属性分开。


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

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

6、扩展

1、什么是SOA?

SOA(Service-Oriented Architecture),中文全称:面向服务的架构。

通俗点来讲,SOA提倡将不同应用程序的业务功能封装成“服务”并宿主起来,通常以接口和契约的形式暴露并提供给外界应用访问(通过交换消息),达到不同系统可重用的目的。

SOA是一个组件模型,它能将不同的服务通过定义良好的接口和契约联系起来。服务是SOA的基石

2、微服务和SOA的区别

微服务是SOA架构演进的结果。两者说到底都是对外提供接口的一种架构设计方式,随着互联网的发展,复杂的平台、业务的出现,导致SOA架构向更细粒度、更通过化程度发展,就成了所谓的微服务了。

总之,微服务是SOA发展出来的产物,它是一种比较现代化的细粒度的SOA实现方式。

SOA与微服务的区别在于如下几个方面:

  1.  微服务相比于SOA更加精细,微服务更多的以独立的进程的方式存在,互相之间并无影响;
  2.  微服务提供的接口方式更加通用化,例如HTTP RESTful方式,各种终端都可以调用,无关语言、平台限制;
  3.  微服务更倾向于分布式去中心化的部署方式,在互联网业务场景下更适合。

为什么要使用微服务?

技术为业务而生,架构也为业务而出现,当然SOA和微服务也是因为业务的发展而出现。出现SOA和微服务框架与业务的发展、平台的壮大密不可分,下面借用dubbo的网站架构发展图和说明:

阿里P8架构师谈:Restful、SOAP、RPC、SOA、微服务之间的区别
  •  单一应用架构
  •  当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
  •  此时,用于简化增删改查工作量的 数据访问框架(ORM) 是关键。
  •  垂直应用架构
  •  当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。
  •  此时,用于加速前端页面开发的 Web框架(MVC) 是关键。
  •  分布式服务架构
  •  当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。
  •  此时,用于提高业务复用及整合的 分布式服务框架(RPC) 是关键。
  •  流动计算架构
  •  当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。
  •  此时,用于提高机器利用率的 资源调度和治理中心(SOA) 是关键。

平台随着业务的发展从 All in One 环境就可以满足业务需求(以Java来说,可能只是一两个war包就解决了)。

发展到需要拆分多个应用,并且采用MVC的方式分离前后端,加快开发效率;在发展到服务越来越多,不得不将一些核心或共用的服务拆分出来,其实发展到此阶段,如果服务拆分的足够精细,并且独立运行,我觉得就可以将之理解为一个微服务了。

 

TRANSLATE with x English
Arabic Hebrew Polish
Bulgarian Hindi Portuguese
Catalan Hmong Daw Romanian
Chinese Simplified Hungarian Russian
Chinese Traditional Indonesian Slovak
Czech Italian Slovenian
Danish Japanese Spanish
Dutch Klingon Swedish
English Korean Thai
Estonian Latvian Turkish
Finnish Lithuanian Ukrainian
French Malay Urdu
German Maltese Vietnamese
Greek Norwegian Welsh
Haitian Creole Persian  
Bing Webmaster Portal Back