Appearance
REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席
RESTful(Representational State Transfer)是一种软件架构风格,它以资源为中心,通过 HTTP(超文本传输协议)提供一组简单、统一、易于使用的接口,目的是为了解决 Web 应用中分布式系统的互操作性和可扩展性问题。
类似的风格还用
RESTful 是一种符合 REST(Representational State Transfer)设计原则的软件架构风格。除了 RESTful 之外,还有许多其他的软件架构风格和设计约束,如:
SOAP(Simple Object Access Protocol):SOAP 是一种基于 XML 的 Web 服务消息传递协议,规定了客户端和服务器之间如何调用和传递数据。
RPC(Remote Procedure Call):RPC 是一种远程过程调用技术,允许一个程序在本地调用另一台计算机上的子程序,就像调用本地子程序一样。RPC 可以基于不同的传输协议(例如,HTTP、TCP/IP)和序列化协议(例如,XML、JSON、binary)实现。
GraphQL:GraphQL 是一种数据查询语言,由 Facebook 提出,旨在提供更灵活且高效的数据查询方式。不同于 RESTful 的资源导向,GraphQL 通过一个统一的查询入口获取所需的所有数据,客户端可以准确地请求所需的数据,避免了数据过度或不足的情况。
OData(Open Data Protocol):OData 是一种基于REST架构风格的数据访问协议,定义了客户端和服务器进行数据通信的标准,主要用于构建丰富的数据API。与 RESTful 类似,OData 遵循无状态、可缓存等原则,同时提供了更多的查询和操作功能。
Message Queue Architecture:这是一种采用消息队列的通信方式进行协调和通信的架构。一个组件产生消息并输送至消息队列,由另一个组件消费消息。这种方式有利于降低组件间的耦合性,提高扩展性,同时保证了消息的顺序以及可靠传输。
Event-Driven Architecture:事件驱动架构是一种组件通过发送、接收和处理事件进行通信和协调的架构。组件在事件发生时被动地获取并处理事件,有利于提高系统的响应能力和扩展性。
以上各种架构风格各有优劣,根据不同的应用场景和需求进行选择。
介绍 Representational State Transfer
- Representational:表示。RESTful 是一种用于 Web 服务的架构风格,可以通过 HTTP 协议来表示和传输 Web 资源。资源可以是任何东西,比如 HTML 页面,JSON、XML 格式的数据 也可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在等。资源可以通过 URI 进行访问,而 HTTP 协议可以表示和传输这些资源
URI
http://www.example.com/index.html
是一个URI,其中http是协议,www.example.com是主机名,/index.html是路径。URI是Web中最重要的概念之一,它使得我们能够在互联网上定位和访问各种资源。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
- State Transfer:状态转移。RESTful 通过 HTTP 协议的各种方法(比如 GET、POST、DELETE 等)来实现资源状态的转移(转换),并以 HTTP 的方式传输这些转移后得到的资源信息。客户端和服务端之间通过 HTTP 协议交互,服务端不需要关心客户端的具体实现,只需要关注资源的状态,并提供 API 接口,让客户端通过 URI 进行访问。
Representational State Transfer(REST)是一种软件架构风格,它主要用于Web服务的设计和实现。REST основ于客户端-服务器架构,提倡可以缓存的无状态通信。在REST中,资源是通过URL(统一资源定位符)进行标识,可通过HTTP动词(如GET, POST, PUT, DELETE等)进行资源操作。因此他的特点:
资源导向:REST是基于资源的,将Web应用中的所有实体定位为资源,通过URL标识。
无状态:客户端与服务器之间的通信应该保持无状态,即每个请求都包含处理请求所需的所有信息。这使得响应可以方便地缓存,并提高了系统的可扩展性。同时,无状态意味着服务器无需维护客户端的上下文信息。
客户端-服务器架构:客户端负责用户界面和操作,服务器负责实现业务逻辑和数据管理。因此,客户端和服务器的职责清晰分离。
缓存:通过对响应结果进行缓存,可以降低服务器的负载,提高响应速度,以及减小延迟。
使用标准HTTP方法:REST架构使用标准HTTP方法进行资源操作,如GET、POST、PUT、DELETE等。这些方法能够清晰地描述对资源的操作行为。
易于扩展和整合:REST易于扩展的架构使其能够方便地整合其他软件模块。
高可用性和可靠性:由于REST的无状态设计、缓存机制和易于扩展性,它可以提供更高的可用性和可靠性。
规则规范
使用HTTP协议:RESTful API应该基于HTTP协议,因为它本身是对Web的资源操作,而HTTP是Web领域的通行协议,但是安全性与权限控制:API应该考虑访问安全性,建议使用HTTPS协议。同时,通过OAuth、API密钥等方式进行访问授权和权限控制
URL定位资源:API的URL应该代表一个具体的资源,尽量使用名词(而非动词)来表示资源,例如:/users而不是/getUsers。此外,建议使用复数形式来表示资源集合,将资源名与数据库的表名或模型匹配。这样设计能确保资源在 API 和数据库中的一致性,并使得在 API 层处理底层数据更为直接和容易,API中的名词也应该使用复数。假设有一个用户表(User),URL 资源可能是这样设计的:
GET /users // 获取所有用户
GET /users/:id // 获取指定 ID 的用户
POST /users // 创建新用户
PUT /users/:id // 更新指定 ID 的用户
DELETE /users/:id // 删除指定 ID 的用户
- 使用HTTP谓词表示操作:API应该使用标准的HTTP谓词(GET、POST、PUT、DELETE等)来表示对资源的操作,而不是通过URL中的动词实现。常用的HTTP动词有下面五个(括号里是对应的SQL命令)。
- GET(读取):从服务器取出资源(一项或多项)。
- POST(创建):在服务器新建一个资源。
- PUT(完整更新):在服务器更新资源(客户端提供改变后的完整资源)。
- PATCH(部分更新):在服务器更新资源(客户端提供改变的属性)。
- DELETE(删除):从服务器删除资源。
PUT
和 PATCH
都是用于更新资源的 HTTP 方法,但它们之间存在一些关键区别:
1. PUT 方法
- PUT 方法是幂等的,这意味着无论调用多少次,结果总是相同。这为客户端提供了更可靠的更新方式,尤其是在网络不稳定的环境中。
- PUT 方法要求客户端提供整个资源的全部属性进行更新。换句话说,客户端需要发送资源的所有字段,无论它们是否发生改变。服务器端接收到请求后,将使用一份新的完整数据来替换旧数据。
2. PATCH 方法
- PATCH 方法是非幂等的,每次调用的结果都可能不同。这使得客户端需要更小心地处理请求错误和重试操作。
- PATCH 方法允许客户端部分更新资源。客户端只需发送需要修改的字段,而不是整个资源。服务器端接收到请求后,仅更新请求中指定的字段。
举例说明:假设有一个用户资源,由 id
、name
和 email
三个字段组成。如果我们要更新用户的 email
字段:
使用 PUT 方法更新
http
PUT /users/1
{
"name": "John Doe",
"email": "john.doe@example.com"
}
注意,尽管我们只更新了 email
字段,但要发送整个资源。
使用 PATCH 方法更新
http
PATCH /users/1
{
"email": "john.doe@example.com"
}
只需发送要更新的字段。
总结:PUT
和 PATCH
的主要区别在于它们更新资源的方式。使用 PUT
时,需要提供完整的资源数据,而使用 PATCH
时,只需提供需要更新的字段。另外,PUT
是幂等的,而 PATCH
是非幂等的。在实际应用中,选择使用哪种方法取决于具体需求和场景。
其他不常用的HTTP动词
- HEAD:获取资源的元数据。
- OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
- 资源层次结构:如果资源之间存在层次关系,可以在URL中通过 '/' 表示。例如,获取一个用户的订单列表可以使用:
例子
- GET /zoos:列出所有动物园
- POST /zoos:新建一个动物园
- GET /zoos/ID:获取某个指定动物园的信息
- PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
- PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
- DELETE /zoos/ID:删除某个动物园
- GET /zoos/ID/animals:列出某个指定动物园的所有动物
- DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物
- 使用查询参数进行筛选和分页:当需要对资源进行筛选或分页时,可以使用URL的查询参数实现。例如:/users?gender=male&page=2表示筛选出男性用户且获取第二页的列表。
例子
- ?limit=10:指定返回记录的数量
- ?offset=10:指定返回记录的开始位置。
- ?page=2&per_page=100:指定第几页,以及每页的记录数。
- ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
- ?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。
- 状态码表示返回结果:合理使用HTTP状态码来表示操作的结果。HTTP 状态码就是一个三位数,分成五个类别:
- 1xx:相关信息
- 2xx:操作成功
- 3xx:重定向
- 4xx:客户端错误
- 5xx:服务器错误 这五大类总共包含100多种状态码,覆盖了绝大部分可能遇到的情况。每一种状态码都有标准的(或者约定的)解释,客户端只需查看状态码,就可以判断出发生了什么情况,所以服务器应该返回尽可能精确的状态码。
常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)状态码的完全列表参见这里:
- 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
- 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
- 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
- 204 NO CONTENT - [DELETE]:用户删除数据成功。
- 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
- 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
- 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
- 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成
返回结构统一与描述清晰:返回结果应该具有清晰的结构,且字段描述清晰。建议使用JSON作为返回数据格式,方便跨平台通信。针对不同操作,服务器向用户返回的结果应该符合以下规范,前面都是讲的请求规范,请求之后的数据规范对应不同的操作应该有相应的返回:
- GET /collection:请求获取一个资源对象列表,响应返回一个包含多个资源对象的数组。例如,GET /users 可以返回所有用户的列表。
- GET /collection/resource:请求获取一个单独的资源对象,响应返回一个单独的资源对象。例如,GET /users/1 可以返回ID为1的用户对象。
- POST /collection:请求创建一个新的资源对象,响应返回新创建的资源对象。例如,POST /users 可以创建一个新的用户对象,并返回该新用户对象。
- PUT /collection/resource:请求更新一个完整的资源对象,响应返回更新后的完整资源对象。例如,PUT /users/1 可以更新ID为1的用户对象,并返回更新后的完整用户对象。
- PATCH /collection/resource:请求更新一个部分的资源对象,响应返回更新后的完整资源对象。例如,PATCH /users/1 可以更新ID为1的用户对象的某些属性,并返回更新后的完整用户对象。
- DELETE /collection/resource:请求删除一个资源对象,响应返回一个空文档。例如,DELETE /users/1 可以删除ID为1的用户对象,并返回一个空文档。
版本控制:为了兼容旧版本应用和用户,可以通过URL的前缀或者请求头中的Accept参数来控制API的版本。例如:
Details
/v1/users
或
Accept: application/vnd.company.myapp-v3+json。
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0
两者的区别在一个是在 URL 中声明版本,一种是在请求头中声明版本
注意点
有一种不恰当的做法是,即使发生错误,也返回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."
}
}
URI包含动词。因为"资源"表示一种实体,所以应该是名词,URI不应该有动词,动词应该放在HTTP协议中,某个URI是/posts/show/1,其中show是动词,这个URI就设计错了,正确的写法应该是/posts/1,然后用GET方法表示show。
但如果某些动作是HTTP动词表示不了的应该把动作做成一种资源
POST /accounts/1/transfer/500/to/2
正确的写法是把动词transfer改成名词transaction,资源不能是动词,但是可以是一种服务:
POST /transaction HTTP/1.1
Host: 127.0.0.1
from=1&to=2&amount=500.00
同理筛选(Filtering)、排序(Sorting)、分页(Pagination)、字段选择接口URL尽量保持简短,对于集合资源不同纬度的筛选,可通过组合不同的Query Param来实现。
GET /v1/externalEmployees [Bad]
GET /v1/internalEmployees [Bad]
GET /v1/internalAndSeniorEmployees [Bad]
GET /v1/employee?state=internal&title=senior [OK]
使用小写字母,多个单词用"-"分隔,提高URL的可读性,RFC3986 定义了URI是大小写敏感的(除了scheme和host部分),为了避免歧义,接口路径应尽量使用小写字母。
对于由多个单词构成的路径,推荐使用'-'(hyphen)分隔,避免使用驼峰命名(camelCase)、下划线命名(snake_case)、首字母大写的驼峰命名(PascalCase),以提高可读性,
camelCase : /v1/customer/1000/shippingAddress [Bad]
snake_case : /v1/customer/1000/shipping_address [Bad]
PascalCase : /v1/customer/1000/ShippingAddress [Bad]
hyphen : /v1/customer/1000/shipping-address [OK]
资源嵌套层次避免过深,尽量不超过2层
GET /v1/user/1000/company 查询ID等于1000的用户的公司信息
GET /v1/user/1000/company/10 查询ID等于1000的用户所属公司中ID=10的公司信息,嵌套两层