RESTful风格

什么是REST

  • restful
    REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:“我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。” 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

  • REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。

  • 理解RESTful
    要理解RESTful架构,需要理解Representational State Transfer这个词组到底是什么意思,它的每一个词都有些什么涵义。

下面我们结合REST原则,围绕资源展开讨论,从资源的定义、获取、表述、关联、状态变迁等角度,列举一些关键概念并加以解释。

  • 资源与URI
  • 统一资源接口
  • 资源的表述
  • 资源的链接
  • 状态的转移

资源与URI

  • REST全称是表述性状态转移,那究竟指的是什么的表述? 其实指的就是资源。任何事物,只要有被引用到的必要,它就是一个资源。资源可以是实体(例如手机号码),也可以只是一个抽象概念(例如价值) 。下面是一些资源的例子:
    某用户的手机号码
    某用户的个人信息
    最多用户订购的GPRS套餐
    两个产品之间的依赖关系
    某用户可以办理的优惠套餐
    某手机号码的潜在价值

  • 要让一个资源可以被识别,需要有个唯一标识,在Web中这个唯一标识就是URI(Uniform Resource Identifier)。

  • URI既可以看成是资源的地址,也可以看成是资源的名称。如果某些信息没有使用URI来表示,那它就不能算是一个资源, 只能算是资源的一些信息而已。URI的设计应该遵循可寻址性原则,具有自描述性,需要在形式上给人以直觉上的关联。这里以github网站为例,给出一些还算不错的URI:
    https://github.com/git
    https://github.com/git/git
    https://github.com/git/git/blob/master/block-sha1/sha1.h
    https://github.com/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08
    https://github.com/git/git/pulls
    https://github.com/git/git/pulls?state=closed
    https://github.com/git/git/compare/master…next
    下面让我们来看看URI设计上的一些技巧:

  • 使用_或-来让URI可读性更好
    曾经Web上的URI都是冰冷的数字或者无意义的字符串,但现在越来越多的网站使用_或-来分隔一些单词,让URI看上去更为人性化。 例如国内比较出名的开源中国社区,它上面的新闻地址就采用这种风格, 如http://www.oschina.net/news/38119/oschina-translate-reward-plan。

  • 使用/来表示资源的层级关系
    例如上述/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08就表示了一个多级的资源, 指的是git用户的git项目的某次提交记录,又例如/orders/2012/10可以用来表示2012年10月的订单记录。

  • 使用?用来过滤资源
    很多人只是把?简单的当做是参数的传递,很容易造成URI过于复杂、难以理解。可以把?用于对资源的过滤, 例如/git/git/pulls用来表示git项目的所有推入请求,而/pulls?state=closed用来表示git项目中已经关闭的推入请求, 这种URL通常对应的是一些特定条件的查询结果或算法运算结果。

  • ,或;可以用来表示同级资源的关系
    有时候我们需要表示同级资源的关系时,可以使用,或;来进行分割。例如哪天github可以比较某个文件在随意两次提交记录之间的差异,或许可以使用/git/git /block-sha1/sha1.h/compare/e3af72cdafab5993d18fae056f87e1d675913d08;bd63e61bdf38e872d5215c07b264dcc16e4febca作为URI。 不过,现在github是使用…来做这个事情的,例如/git/git/compare/master…next。

统一资源接口

  • RESTful架构应该遵循统一接口原则,统一接口包含了一组受限的预定义的操作,不论什么样的资源,都是通过使用相同的接口进行资源的访问。接口应该使用标准的HTTP方法如GET,PUT和POST,并遵循这些方法的语义。
  • 如果按照HTTP方法的语义来暴露资源,那么接口将会拥有安全性和幂等性的特性,例如GET和HEAD请求都是安全的, 无论请求多少次,都不会改变服务器状态。而GET、HEAD、PUT和DELETE请求都是幂等的,无论对资源操作多少次, 结果总是一样的,后面的请求并不会产生比第一次更多的影响。

下面列出了GET,DELETE,PUT和POST的典型用法:

  • GET
    安全且幂等
    获取表示
    变更时获取表示(缓存)
    200(OK) - 表示已在响应中发出
    204(无内容) - 资源有空表示
    301(Moved Permanently) - 资源的URI已被更新
    303(See Other) - 其他(如,负载均衡)
    304(not modified)- 资源未更改(缓存)
    400 (bad request)- 指代坏请求(如,参数错误)
    404 (not found)- 资源不存在
    406 (not acceptable)- 服务端不支持所需表示
    500 (internal server error)- 通用错误响应
    503 (Service Unavailable)- 服务端当前无法处理请求

  • POST
    不安全且不幂等
    使用服务端管理的(自动产生)的实例号创建资源
    创建子资源
    部分更新资源
    如果没有被修改,则不过更新资源(乐观锁)
    200(OK)- 如果现有资源已被更改
    201(created)- 如果新资源被创建
    202(accepted)- 已接受处理请求但尚未完成(异步处理)
    301(Moved Permanently)- 资源的URI被更新
    303(See Other)- 其他(如,负载均衡)
    400(bad request)- 指代坏请求
    404 (not found)- 资源不存在
    406 (not acceptable)- 服务端不支持所需表示
    409 (conflict)- 通用冲突
    412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
    415 (unsupported media type)- 接受到的表示不受支持
    500 (internal server error)- 通用错误响应
    503 (Service Unavailable)- 服务当前无法处理请求

  • PUT
    不安全但幂等
    用客户端管理的实例号创建一个资源
    通过替换的方式更新资源
    如果未被修改,则更新资源(乐观锁)
    200 (OK)- 如果已存在资源被更改
    201 (created)- 如果新资源被创建
    301(Moved Permanently)- 资源的URI已更改
    303 (See Other)- 其他(如,负载均衡)
    400 (bad request)- 指代坏请求
    404 (not found)- 资源不存在
    406 (not acceptable)- 服务端不支持所需表示
    409 (conflict)- 通用冲突
    412 (Precondition Failed)- 前置条件失败(如执行条件更新时的冲突)
    415 (unsupported media type)- 接受到的表示不受支持
    500 (internal server error)- 通用错误响应
    503 (Service Unavailable)- 服务当前无法处理请求

  • PATCH(自己添加)
    不安全不幂等
    更新资源的局部

  • DELETE
    不安全但幂等
    删除资源
    200 (OK)- 资源已被删除
    301 (Moved Permanently)- 资源的URI已更改
    303 (See Other)- 其他,如负载均衡
    400 (bad request)- 指代坏请求
    404 (not found)- 资源不存在
    409 (conflict)- 通用冲突
    500 (internal server error)- 通用错误响应
    503 (Service Unavailable)- 服务端当前无法处理请求
    下面我们来看一些实践中常见的问题:

  • POST和PUT用于创建资源时有什么区别?

  • POST和PUT在创建资源的区别在于,所创建的资源的名称(URI)是否由客户端决定。 例如为我的博文增加一个java的分类,生成的路径就是分类名/categories/java,那么就可以采用PUT方法。不过很多人直接把POST、GET、PUT、DELETE直接对应上CRUD,例如在一个典型的rails实现的RESTful应用中就是这么做的。

  • 客户端不一定都支持这些HTTP方法吧?
    的确有这种情况,特别是一些比较古老的基于浏览器的客户端,只能支持GET和POST两种方法。

  • 在实践上,客户端和服务端都可能需要做一些妥协。例如rails框架就支持通过隐藏参数_method=DELETE来传递真实的请求方法, 而像Backbone这样的客户端MVC框架则允许传递_method传输和设置X-HTTP-Method-Override头来规避这个问题。

  • 统一接口是否意味着不能扩展带特殊语义的方法?
    统一接口并不阻止你扩展方法,只要方法对资源的操作有着具体的、可识别的语义即可,并能够保持整个接口的统一性。

  • 像WebDAV就对HTTP方法进行了扩展,增加了LOCK、UPLOCK等方法。而github的API则支持使用PATCH方法来进行issue的更新,例如:
    不过,需要注意的是,像PATCH这种不是HTTP标准方法的,服务端需要考虑客户端是否能够支持的问题

  • 统一资源接口对URI有什么指导意义?
    统一资源接口要求使用标准的HTTP方法对资源进行操作,所以URI只应该来表示资源的名称,而不应该包括资源的操作。

  • 通俗来说,URI不应该使用动作来描述。例如,下面是一些不符合统一接口要求的URI:
    GET /getUser/1
    POST /createUser
    PUT /updateUser/1
    DELETE /deleteUser/1

  • 如果GET请求增加计数器,这是否违反安全性?
    安全性不代表请求不产生副作用,例如像很多API开发平台,都对请求流量做限制。像github,就会限制没有认证的请求每小时只能请求60次。
    但客户端不是为了追求副作用而发出这些GET或HEAD请求的,产生副作用是服务端"自作主张"的。
    另外,服务端在设计时,也不应该让副作用太大,因为客户端认为这些请求是不会产生副作用的。

### RESTful 风格 API 的设计原则 RESTful API 设计遵循一系列指导方针,旨在创建易于理解和使用的接口。这些原则确保了接口的一致性、可用性和可扩展性[^1]。 #### 资源导向 RESTful API 将系统的核心组件视为资源。每个资源通过唯一的 URI (统一资源标识符) 进行访问。例如,在电子商务平台中,“产品”可以被定义为一种资源,并可通过 `/products/{id}` 访问特定的产品实例[^3]。 #### 使用标准 HTTP 方法操作资源 为了保持一致性并简化客户端的理解成本,RESTful API 利用了 HTTP 提供的标准动词来执行 CRUD 操作: - `GET` 请求用于获取资源数据; - `POST` 用来提交新实体到指定集合下; - `PUT` 和 `PATCH` 分别表示更新整个现有记录或者部分修改字段; - `DELETE` 执行删除动作[^4]。 #### 状态无连接性 每次请求都应包含处理该次调用所需的所有信息;服务器不应依赖于任何存储在它上面的状态或上下文来完成这个过程。这意味着每一个事务都是独立存在的,不会受到之前或其他并发会话的影响[^5]。 #### 统一接口 API 应当提供一个统一的方式来进行交互,无论是在不同的端点之间还是跨多个服务提供商的情况下。这通常涉及到采用一致性的命名约定、错误响应模式以及支持相同类型的媒体格式(如 JSON 或 XML)。此外,良好的文档说明也是至关重要的组成部分之一[^2]。 ### 实现 RESTful API 实现 RESTful API 可以选用多种编程语言和技术栈,比如 Python Flask/Django, Node.js Express, Java Spring Boot 等等。这里给出一段简单的Python代码片段作为例子: ```python from flask import Flask, jsonify, request app = Flask(__name__) @app.route('/api/v1/resources', methods=['GET']) def get_resources(): # 处理 GET 请求逻辑... pass if __name__ == '__main__': app.run(debug=True) ``` 上述示例展示了如何利用Flask框架快速搭建起能够接收来自外部世界的HTTP请求的服务端程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值