## 什么是 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