ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 什么是 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 应用在实际开发中。