## 什么是 RESTful ?
REST 全称是 Representational State Transfer,中文意思是表述性状态转移(注:通常译为表征性状态转移)。 它首次出现在 2000 年 Roy Fielding 的博士论文中,Roy Fielding 是 HTTP 规范的主要编写者之一。
Roy Fielding 在论文中提到:“我这篇文章的写作目的,就是想在**符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构**。REST 指的是一组架构约束条件和原则。” 如果一个架构符合 REST 的约束条件和原则,我们就可以称之为 RESTful 架构。
通俗地讲:**RESTful 就是客户端与服务器进行数据交互的一种规范**,而且是当今**绝大多数开发者都在遵循的规范**。
应用 RESTful 架构,可以想像成读者去图书馆找书,读者相当于客户端,图书馆相当于服务器。不同种类的书籍,对应不同分类,且有固定的分类缩写。如编号以 T 开头的图书,表示工业技术类图书,编号以 J 开头的图书,表示艺术类图书。不管去哪一个图书馆,这些分类缩写都是相同的,任何一位读者只要知道图书种类,就可在标有相应分类缩写的书架区域找到相应书籍。**RESTful 就是 Web 开发行业的规范,符合这种规范,就是一套 RESTful 架构**。
## 为什么学习RESTful?
近年来,**随着前后端分离技术的普遍应用,API 接口技术已经成为前后端开发人的必修课之一**。在业内,**不论使用什么编程语言开发 API,都需要遵守 RESTful 规范**。因此,不论你是使用 API 的前端开发人员,还是直接开发 API 接口的后端开发人员,都必须熟悉 RESTful Web 规范,否则将很难同其他人配合。
## 如何学习RESTful ?
我们通过理论介绍加动手实践的方式完成 RESTful Web 的学习。实践环节,我们选用 Django Rest framework 框架带领读者亲自搭建一套 RESTful 架构的 API。Django Rest framework 是基于 Django 框架开发的**用来帮助开发者快速构建 RESTful Web API 的强大而又灵活的工具**。在实现 API 的过程中,Django Rest framework 为我们实现了大量的操作,使用该框架仅需书写少量代码,就可实现 API 的构建,大大减少了工作量,**可使开发者将更多精力集中在 API 的设计**,而非 API 的实现工程。
## RESTful设计方法和规范
在初步了解了 RESTful 之后,我们接到一项任务,需要为一所学校开发一套师生管理系统,客户要求所开发的系统能在 PC 桌面通过浏览器使用,而且日后还想开发 IOS 和 Android 应用。了解需求之后,我们毫不犹豫选择了前后端分离的开发模式,并且决定遵从时下最为流行的 RESTful 规范。接下来,我们就以后端开发人员的角色,一起来了解整个开发过程。
### 1\. 域名(Domain)
根据 RESTful 规范,应该尽量使用专用的域名用于部署 API,于是我们和校方沟通,使用下方域名作为 API 访问地址:
~~~http
https://api.demo.com
~~~
但是经过沟通,发现上述域名已被占用,校方否决了我们的提议,考虑到 API 相对简单,于是我们使用下面地址部署 API:
~~~http
https://www.demo.com/api
~~~
上述地址中,**https**代表协议名称,常见的还有**http**,二者区别在于前者在传输过程中是将信息加密后传输的,而后者是明文传输;**[www.demo.com](http://www.demo.com/)**为域名,可以理解成某个机房里一台电脑的地址,通过这个地址,就能访问这台电脑提供的资源;**api**代表一个资源路径,可以想象成这台电脑中一个文件夹的路径。
### 2\. 版本(Versioning)
师生管理系统不是一成不变的,日后还要更新维护。为了区分不同版本,API 的 URL 中应当包含 API 版本信息:
~~~http
http://www.demo.com/api/1.0/foo
http://www.demo.com/api/1.1/foo
http://www.demo.com/api/2.0/foo
~~~
除了上述方法外,API 版本信息还可放在 HTTP 请求头中。[Github](https://developer.github.com/v3/media/#request-specific-version)采用的就是这种做法。
因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个 URL。版本号可以在 HTTP 请求头信息的 Accept 字段中进行区分(参见[Versioning REST Services](http://www.informit.com/articles/article.aspx?p=1566460)):
~~~http
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
~~~
实际工作中,通常采用第一种方法,因为这样的方式更加直观,方便使用。
### 3\. 路径(Endpoint)
路径即"终点"(endpoint),是访问 API 的具体网址,通过访问每个网址,可以获取到相应的资源(resource)。在师生管理系统中,所谓资源,就是我们想获取的信息,比如获取 3 年 2 班所有学生姓名,获取小明的年龄、成绩等。
路径须满足以下规范:
**1\. 资源路径中应当使用名词,杜绝动词。资源路径中的名词,应当与数据库的表名相对应。**
以下路径中包含动词,是不符合规范的例子,在实际工作中,应当避免。
~~~http
/getStudents :获取学生信息
/listTeachers :获取老师信息
/retreiveStudentByID?Id=2020 :获取ID为2020的学生信息
~~~
对于资源的操作,应该通过 HTTP 中的不同方法来区分处理资源的动作,资源路径中应当只包含名词。
~~~http
GET /students :将返回所有学生信息
POST /students :将新增的学生信息存入数据库
GET /students/4 :获取编号为4号的学生信息
PATCH(或)PUT /students/4 :更新编号为4的学生信息
~~~
**2\. API 中的名词应该使用复数。无论是子资源或者是所有资源。**
例如:
~~~http
获取单个学生信息:http://www.demo.com/students/1 :获取编号为1的学生信息
获取所有学生信息: http://www.demo.com/students :获取所有学生信息
~~~
### 4\. HTTP动词
对于资源的具体操作类型,由 HTTP 动词表示。
常用的 HTTP 动词有下面 4 个(括号里是对应的 SQL 命令)。
* **GET(SELECT)**:从服务器取出资源(一项或多项)
* **POST(CREATE)**:在服务器新建一个资源
* **PUT(UPDATE)**:在服务器更新资源(客户端提供改变后的完整资源)
* **DELETE(DELETE)**:从服务器删除资源
还有 3 个不常用的 HTTP 动词。
* **PATCH(UPDATE)**:在服务器更新(更新)资源(客户端提供改变的属性)
* **HEAD**:获取资源的元数
* **OPTIONS**:获取信息,关于资源的哪些属性是客户端可以改变的
下面是一些例子。
~~~http
GET /classes:列出所有班级
POST /classes:新建一个班级(上传文件)
GET /classes/ID:获取某个指定班级的信息
PUT /classes/ID:更新某个指定班级的信息(提供该班级的全部信息)
PATCH /classes/ID:更新某个指定班级的信息(提供该班级的部分信息)
DELETE /classes/ID:删除某个班级
GET /classes/ID/students:列出某个指定班级的所有学生
DELETE /classes/ID/students/ID:删除某个指定班级的指定学生
~~~
### 5\. 过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API 应该提供参数,过滤返回结果。比如,我们想获取全校师生的个人信息,如果将这些信息一股脑地全部展示在网页上,是不明智也是不现实的。如果数据量太大,在实际开发中我们会采用分页展示的形式。另外,如果想在一次考试后,按照成绩高低展示学生信息,那么可以通过过滤信息来实现。
所谓过滤,就是在 URL 中添加一下限制参数。下面是一些常见的参数。
~~~http
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=score&order=asc:指定返回结果按照学生的成绩(score)正序(asc)排列顺序。
~~~
参数的设计允许存在冗余,即允许 API 路径和 URL 参数允许有重复。比如,想要查询某个班级所有学生信息,我们可以设计`GET /classes/ID/students`与`GET /students?class_id=ID`两种地址,任何一种都可得到相同的结果。
### 6\. 状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)。不同的状态码代表着不同的含义,比如以 2 开头的状态码通常代表服务器成功响应,3 开头的状态码代表发生了重定性(即跳转到了别的链接),4 开头的状态码通常表示客户端这边提供的信息有误,而 5 开头的状态码则表示服务器内部出现的错误。通过返回的状态码,用户即可判断请求成功与否,不成功问题在何处。
一些常用的状态码列举如下:
* **200 OK - \[GET\]**:服务器成功返回用户请求的数据
* **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 - \[\*\]**:服务器发生错误,用户将无法判断发出的请求是否成功
状态码的完全列表参见[这里](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)或[这里](https://zh.wikipedia.org/wiki/HTTP%E7%8A%B6%E6%80%81%E7%A0%81)。
### 7\. 错误信息
如果状态码是 4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息是键值对形式的数据,将`error`作为键名,出错信息作为键值即可。比如,在一个提供查询学生信息的 API 中,要求客户端提供正确的 API key(可以理解为输入了正确的用户名和密码)才能访问,如果提供的 API key 不正确,此时服务器应拒绝访问,并返回错误信息。这样,客户端就知道了为何没能查到信息,修改成正确的 API key 即可。
~~~json
{
error: "Invalid API key"
}
~~~
### 8\. 返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
* **GET /collection**:返回资源对象的列表(数组)
* **GET /collection/resource**:返回单个资源对象
* **POST /collection**:返回新生成的资源对象
* **PUT /collection/resource**:返回完整的资源对象
* **PATCH /collection/resource**:返回完整的资源对象
* **DELETE /collection/resource**:返回一个空文档
### 9\. 超媒体链接
RESTful API 最好做到 Hypermedia(即返回结果中提供链接,连向其他 API 方法),使得用户不查文档,也知道下一步应该做什么。
比如,Github 的 API 就是这种设计,访问[api.github.com](https://api.github.com/)会得到一个所有可用API的网址列表。
~~~json
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}
~~~
从上面可以看到,如果想获取当前用户的信息,应该去访问[api.github.com/user](https://api.github.com/user),然后就得到了下面结果。
~~~json
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}
~~~
上面代码表示,服务器给出了提示信息,以及文档的网址。
### 10\. 数据格式
服务器返回的数据格式,应该尽量使用 JSON,避免使用 XML。什么是 JSON 呢?什么又是 XML 呢?两种数据格式的简单举例如下:
~~~json
# JSON
{"name":"XiaoMing",
"age":"12",
"gender":"male"}
~~~
~~~xml
# XML
<?xml version="1.0" encoding="UTF-8" ?>
<name>XiaoMing</name>
<age>12</age>
<gender>male</gender>
~~~
通过上面的对比可以看出,JSON 数据形式要远比 XML 的数据形式来得简单和易懂,所以现在的 Web 开发中 JSON 数据格式已经开始全面取代 XML 应用在实际开发中。
- 设计模式系列
- 工厂方法模式
- 序言
- Windows程序注册为服务的工具WinSW
- 基础
- 安装
- 开发规范
- 目录结构
- 配置
- 快速入门
- 架构
- 请求流程
- 架构总览
- URL访问
- 容器和依赖注入
- 中间件
- 事件
- 代码层结构
- 四个层次
- 路由
- 控制器
- 请求
- 响应
- 数据库
- MySQL实时同步数据到ES解决方案
- 阿里云DTS数据MySQL同步至Elasticsearch实战
- PHP中的MySQL连接池
- PHP异步非阻塞MySQL客户端连接池
- 模型
- 视图
- 注解
- @SpringBootApplication(exclude={DataSourceAutoConfiguration.calss})
- @EnableFeignClients(basePackages = "com.wotu.feign")
- @EnableAspectJAutoProxy
- @EnableDiscoveryClient
- 错误和日志
- 异常处理
- 日志处理
- 调试
- 验证
- 验证器
- 验证规则
- 扩展库
- 附录
- Spring框架知识体系详解
- Maven
- Maven和Composer
- 构建Maven项目
- 实操课程
- 01.初识SpringBoot
- 第1章 Java Web发展史与学习Java的方法
- 第2章 环境与常见问题踩坑
- 第3章 springboot的路由与控制器
- 02.Java编程思想深度理论知识
- 第1章 Java编程思想总体
- 第2章 英雄联盟的小案例理解Java中最为抽象的概念
- 第3章 彻底理解IOC、DI与DIP
- 03.Spring与SpringBoot理论篇
- 第1章 Spring与SpringBoot导学
- 第2章 Spring IOC的核心机制:实例化与注入
- 第3章 SpringBoot基本配置原理
- 04.SprinBoot的条件注解与配置
- 第1章 conditonal 条件注解
- 第2章 SpringBoot自动装配解析
- 05.Java异常深度剖析
- 第1章 Java异常分类剖析与自定义异常
- 第2章 自动配置Url前缀
- 06.参数校验机制与LomBok工具集的使用
- 第1章 LomBok工具集的使用
- 第2章 参数校验机制以及自定义校验
- 07.项目分层设计与JPA技术
- 第1章 项目分层原则与层与层的松耦合原则
- 第2章 数据库设计、实体关系与查询方案探讨
- 第3章 JPA的关联关系与规则查询
- 08.ORM的概念与思维
- 第1章 ORM的概念与思维
- 第2章 Banner等相关业务
- 第3章 再谈数据库设计技巧与VO层对象的技巧
- 09.JPA的多种查询规则
- 第1章 DozerBeanMapper的使用
- 第2章 详解SKU的规格设计
- 第3章 通用泛型Converter
- 10.令牌与权限
- 第1章 通用泛型类与java泛型的思考
- 常见问题
- 微服务
- demo
- PHP中Self、Static和parent的区别
- Swoole-Cli
- 为什么要使用现代化PHP框架?
- 公众号
- 一键部署微信公众号Markdown编辑器(支持适配和主题设计)
- Autodesigner 2.0发布
- Luya 一个现代化PHP开发框架
- PHPZip - 创建、读取和管理 ZIP 文件的简单库
- 吊打Golang的PHP界天花板webman压测对比
- 简洁而强大的 YAML 解析库
- 推荐一个革命性的PHP测试框架:Kahlan
- ServBay下一代Web开发环境
- 基于Websocket和Canvas实现多人协作实时共享白板
- Apipost预执行脚本如何调用外部PHP语言
- 认证和授权的安全令牌 Bearer Token
- Laradock PHP 的 Docker 完整本地开发环境
- 高效接口防抖策略,确保数据安全,避免重复提交的终极解决方案!
- TIOBE 6月榜单:PHP稳步前行,编程语言生态的微妙变化
- Aho-Corasick字符串匹配算法的实现
- Redis键空间通知 Keyspace Notification 事件订阅
- ServBay如何启用并运行Webman项目
- 使用mpdf实现导出pdf文件功能
- Medoo 轻量级PHP数据库框架
- 在PHP中编写和运行单元测试
- 9 PHP运行时基准性能测试
- QR码生成器在PHP中的源代码
- 使用Gogs极易搭建的自助Git服务
- Gitea
- webman如何记录SQL到日志?
- Sentry PHP: 实时监测并处理PHP应用程序中的错误
- Swoole v6 Alpha 版本已发布
- Proxypin
- Rust实现的Redis内存数据库发布
- PHP 8.4.0 Alpha 1 测试版本发布
- 121
- Golang + Vue 开发的开源轻量 Linux 服务器运维管理面板
- 内网穿透 FRP VS Tailscale
- 新一代开源代码托管平台Gitea
- 微服务系列
- Nacos云原生配置中心介绍与使用
- 轻量级的开源高性能事件库libevent
- 国密算法
- 国密算法(商用密码)
- GmSSL 支持国密SM2/SM3/SM4/SM9/SSL 密码工具箱
- GmSSL PHP 使用
- 数据库
- SQLite数据库的Web管理工具
- 阿里巴巴MySQL数据库强制规范
- PHP
- PHP安全测试秘密武器 PHPGGC
- 使用declare(strict_types=1)来获得更健壮的PHP代码
- PHP中的魔术常量
- OSS 直传阿里腾讯示例
- PHP源码编译安装APCu扩展实现数据缓存
- BI性能DuckDB数据管理系统
- 为什么别人可以是架构师!而我却不是?
- 密码还在用 MD5 加盐?不如试试 password_hash
- Elasticsearch 在电商领域的应用与实践
- Cron 定时任务入门
- 如何动态设置定时任务!而不是写死在Linux Crontab
- Elasticsearch的四种查询方式,你知道多少?
- Meilisearch vs Elasticsearch
- OpenSearch vs Elasticsearch
- Emlog 轻量级开源博客及建站系统
- 现代化PHP原生协程引擎 PRipple
- 使用Zephir编写C扩展将PHP源代码编译加密
- 如何将PHP源代码编译加密,同时保证代码能正常的运行
- 为什么选择Zephir给PHP编写动态扩展库?
- 使用 PHP + XlsWriter实现百万级数据导入导出
- Rust编写PHP扩展
- 阿里云盘开放平台对接进行文件同步
- 如何构建自己的PHP静态可执行文件
- IM后端架构
- RESTful设计方法和规范
- PHP编译器BPC 7.3 发布,成功编译ThinkPHP8
- 高性能的配置管理扩展 Yaconf
- PHP实现雪花算法库 Snowflake
- PHP官方现代化核心加密库Sodium
- pie
- 现代化、精简、非阻塞PHP标准库PSL
- PHP泛型和集合
- 手把手教你正确使用 Composer包管理
- JWT双令牌认证实现无感Token自动续期
- 最先进PHP大模型深度学习库TransformersPHP
- PHP如何启用 FFI 扩展
- PHP超集语言PXP
- 低延迟双向实时事件通信 Socket.IO
- PHP OOP中的继承和多态
- 强大的现代PHP高级调试工具Kint
- PHP基金会
- 基于webman+vue3高质量中后台框架SaiAdmin
- 开源免费的定时任务管理系统:Gocron
- 简单强大OCR工具EasyOCR在PHP中使用
- PHP代码抽象语法树工具PHP AST Viewer
- MySQL数据库管理工具PHPMyAdmin
- Rust编写的一款高性能多人代码编辑器Zed
- 超高性能PHP框架Workerman v5.0.0-beta.8 发布
- 高并发系列
- 入门介绍及安装
- Lua脚本开发 Hello World
- 执行流程与阶段详解
- Nginx Lua API 接口开发
- Lua模块开发
- OpenResty 高性能的正式原因
- 记一次查找 lua-resty-mysql 库 insert_id 的 bug
- 包管理工具OPM和LuaRocks使用
- 异步非阻塞HTTP客户端库 lua-resty-http
- Nginx 内置绑定变量
- Redis协程网络库 lua-resty-redis
- 动态HTML渲染库 lua-testy-template
- 单独的
- StackBlitz在线开发环境
- AI
- 基础概念
- 12312
- 基础镜像的坑
- 利用phpy实现 PHP 编写 Vision Transformer (ViT) 模型
- 语义化版本 2.0.0