一涎嚼、REST
- 問題
網絡應用程序阱州,分為前端和后端兩個部分。當前的發(fā)展趨勢法梯,就是前端設備層出不窮(手機苔货、平板、桌面電腦立哑、其他專用設備......)夜惭。
因此,必須有一種統(tǒng)一的機制铛绰,方便不同的前端設備與后端進行通信诈茧。這致使API構架的流行。
- 基本概念
REST是"Representational State Transfer"縮寫捂掰,即是"表現層狀態(tài)轉化"敢会。而"表現層"其實指的是"資源(Resource)"的表現層曾沈。
一種軟件架構風格、設計風格鸥昏、而不是標準塞俱,只是提供了一組設計原則和約束條件。
它主要用于客戶端和服務器交互類的軟件互广×搽纾基于這個風格設計的軟件可以更簡潔,更有層次惫皱,更易于實現緩存機制等像樊。
REST其實是一種組織Web服務的架構,而并不是我們想象的那樣是實現Web服務的一種新的技術旅敷,更沒有要求一定要使用HTTP生棍。其目標是為了創(chuàng)建具有良好擴展性的分布式系統(tǒng)。
1. 資源(Resource)
就是網絡上的一個實體媳谁,或是網絡上的具體信息涂滴,可以是一段文本、一張圖片晴音、一首歌曲柔纵、一部電影。
每個資源都會對應的URL锤躁,且是唯一的標識符搁料,想要獲取資源只需要調用對應URL。
2. 表現層(Representation)
"資源"是一種信息實體系羞,它可以有多種外在表現形式郭计。
而"資源"具體呈現出來的形式,就叫它的"表現層"椒振。
3. 狀態(tài)轉換(State Transfer)
訪問一個網站昭伸,就是客戶端和服務端的交互過程,這個過程中就會涉及到數據和狀態(tài)的變化澎迎。
互聯(lián)網通信協(xié)議HTPP庐杨,是無狀態(tài)協(xié)議。即所有狀態(tài)都保存在服務端夹供。
客戶端要操作服務端必須通過某種方式辑莫,讓服務端發(fā)生"狀態(tài)轉換",而這轉換是建立在表現層之上的罩引,所以就是"表現層狀態(tài)轉換"。
客戶端用到的手段只能是HTTP協(xié)議枝笨,在操作方式的動詞: GET/POST/PUT/DELETE袁铐。
對應GET獲取資源揭蜒,POST新建資源(或更新資源),PUT更新資源剔桨,DELETE刪除資源屉更。
- 架構級約束
1.使用客戶/服務器模型∪髯海客戶和服務器之間通過一個統(tǒng)一的接口來互相通訊
2.層次化的系統(tǒng)瑰谜。在一個REST系統(tǒng)中,客戶端并不會固定地與一個服務器打交道
3.無狀態(tài)树绩。在一個REST系統(tǒng)中萨脑,服務端并不會保存有關客戶的任何狀態(tài)。也就是說饺饭,客戶端自身負責用戶狀態(tài)的維持渤早,并在每次發(fā)送請求時都需要提供足夠的信息
4.可緩存。REST系統(tǒng)需要能夠恰當地緩存請求瘫俊,以盡量減少服務端和客戶端之間的信息傳輸鹊杖,以提高性能
5.統(tǒng)一的接口。一個REST系統(tǒng)需要使用一個統(tǒng)一的接口來完成子系統(tǒng)之間以及服務與用戶之間的交互扛芽。這使得REST系統(tǒng)中的各個子系統(tǒng)可以獨自完成演化
一個系統(tǒng)滿足了上面所列出的五條約束骂蓖,那么該系統(tǒng)就被稱為是RESTful。
- 什么是RESTful框架
1. 每一個URL代表一種資源
2. 客戶端和服務器之間川尖,傳遞這種資源的某種表現層
3. 客戶端通過四個HTTP動詞登下,對服務端進行操作,實現"表現層狀態(tài)轉換"
RESTful API是目前比較成熟的一套互聯(lián)網應用程序的API設計理論空厌。
二庐船、返回JSON格式數據
天氣API: https://www.sojson.com/api/weather.html
- jsonify序列化操作
# 返回json格式數據
@blue.route('/getjson/')
def getjson():
data = {
'data': 'hello flask',
'status': 200
}
return jsonify(data)
實現前后端的分離,后臺人員只需要提供API接口接口嘲更。
- 后臺人員工作職責
- 定制接口
- 模型定制
- 面向接口編程
只關注請求地址
值關注請求結果格式
- 示例
- 在app/views.py中添加獲取商品列表數據API 【后臺人員】
# 學生成績API
@blue.route('/getscore/')
def getscore():
data = {
'msg':'學生成績列表',
'status': '200',
'content': [20,32,53,90,43,67,99,89]
}
return jsonify(data)
備注: 獲取數據 http://127.0.0.1:8000/getscore/
- 在app/static/html/testlist.html 【前端人員】
# 發(fā)起Ajax請求筐钟,獲取對應學生成績列表json數據,解析json數據赋朦,并渲染到頁面中
<script>
$(function () {
$.getJSON("http://127.0.0.1:8000/getscore/", function(json){
if (json.status == 200){
$('body').append($('<h3></h3>').html(json.msg))
for(var i=0; i<json.content.length; i++){
$('body').append($('<h3></h3>').html('成績:'+json.content[i]))
}
}
else{
$('body').append($('<h3></h3>').html('哥們篓冲,你錯了!'))
}
});
})
</script>
備注: 靜態(tài)頁面在瀏覽器中打開 http://127.0.0.1:8000/static/html/scorelist.html
書寫靜態(tài)頁面時,要注意是web相關的內容宠哄,而不是模板壹将,所以注釋等方式是會不一樣的!C怠诽俯!
三、RESTful API設計
- 協(xié)議
API與用戶的通信協(xié)議承粤,通常使用HTTP(S)協(xié)議暴区。
- 域名
應該盡量將API部署在專用域名之下闯团。
如: http://api.zyz.com
如果確定API很簡單,不會有大規(guī)模擴從仙粱,可以考慮放在主域名之下房交。
如: http://www.zyz.com/api/
- 版本
應該將API的版本號放入URL。
如: http://api.zyz.com/v1/
也有將版本號放在HTTP的頭信息中伐割,但不如放在URL中方便直觀候味,Github就是這么做的。
- 路徑
路徑又稱"終點"(endpoint)隔心,表示API的具體網址白群。
在RESTful架構中,每個網址代表一種資源济炎,所以網址不能有動詞川抡,只能有名詞。
而所用名詞往往與數據庫表單名對應须尚。
- HTTP動詞
對于資源的具體操作類型崖堤,由HTTP動詞表示。
HTTP常用動詞:
- GET(SELECT) 從服務器取資源
- POST(CREATE or UPDATE) 服務器中創(chuàng)建資源或更新資源
- PUT(UPDATE) 在服務器更新資源(客戶端提供改變后的完整資源)
- PATCH(UPDATE) 在服務器更新資源(客戶端提供改變的屬性)
- DELETE(DELETE) 從服務器刪除資源
- HEAD 獲取資源的元數據
- OPTHONS 獲取信息耐床,關于資源的那些屬性是客戶端可以改變的
例如:
- GET /students 獲取所有學生
- POST /student 新建學生
- GET /students/id 獲取某一個學生
- PUT /students/id 更新某個學生的信息(需要提供學生的全部信息)
- PATHC /students/id 更新某個學生的信息(需要提供學生變更信息)
- DELETE /students/id 刪除某個學生
- 過濾信息
當記錄數量過多密幔,服務器不可能將它們返回給用戶。APIT應該提供參數撩轰,過濾返回結果胯甩。
?limit=10
?offset=10
?page=2&per_page=10
?sortby=name&order=desc
?student_id=id
- 狀態(tài)碼
服務器向用戶返回的狀態(tài)碼和提示信息。
200 OK - [GET]:服務器成功返回用戶請求的數據
201 CREATED -[POST/PUT/PATCH]:用戶新建或修改數據成功
202 Accepted - [*] :表示一個請求已經進入后臺排隊(異步任務)
204 NO CONTENT - [DELETE]:表示數據刪除成功
400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發(fā)出的請求有錯誤
401 Unauthorized - [*] :表示用戶沒有權限(令牌堪嫂,用戶名偎箫,密碼錯誤)
403 Forbidden - [*]:表示用戶得到授權,但是訪問是被禁止的
404 NOT FOUND - [*]:用戶發(fā)出的請求針對的是不存在的記錄
406 Not Acceptable - [*]:用戶請求格式不可得
410 Gone - [GET] :用戶請求的資源被永久移除皆串,且不會再得到的
422 Unprocesable entity -[POST/PUT/PATCH]:當創(chuàng)建一個對象時淹办,發(fā)生一個驗證錯誤
500 INTERNAL SERVER EROR - [*] :服務器內部發(fā)生錯誤
2xx —— 正確的響應
3xx —— 重定向
4xx —— 客戶端錯誤
5xx —— 服務端錯誤
- 錯誤處理
如果狀態(tài)碼是4xx,就應該向用戶返回出錯信息恶复。一般來說怜森,返回的信息中將error做為鍵名
- 返回結果
針對不同操作,服務器想用戶返回的結果應該符合以下規(guī)范:
GET /collection:返回資源對象的列表(數組谤牡,集合)
GET /collection/id:返回單個資源對象
POST /collection:返回新生成的資源對象
PUT /collection/id:返回完整的資源對象
PATCH /collection/id:返回完整的資源對象
DELETE /collection/id:返回一個空文檔
- 核心點
RESTfull 的核心是 resource 副硅,對一個 resource ,用不同的 HTTP verb 做增刪查改翅萤。
- 其他
服務器返回的數據格式恐疲,應該盡量使用JSON
- 原生實現
@blue.route('/api/v1/users/',methods=['GET','POST','DELETE','PUT','PATCH'])
def users():
if request.method == 'GET': # 獲取用戶
pass
elif request.method == 'POST': # 更新或創(chuàng)建用戶密碼
# 獲取數據
username = request.form.get('username')
password = request.form.get('password')
data = {
'msg':'ok',
'status': 201
}
# 不為空
if not username or not password:
data['msg'] = '參數不正確'
data['status'] = 422
return jsonify(data)
user = User()
user.u_name = username
user.u_passwd = generate_passwd(password)
try:
db.session.add(user)
db.session.commit()
except Exception as e:
data['msg'] = '用戶已存在'
data['status'] = 423
return jsonify(data)
return jsonify(data)
elif request.method == 'DELETE': # 刪除用戶
pass
elif request.method == 'PUT': # 更新賬和密碼
pass
elif request.method == 'PATCH': # 修改密碼
pass
else:
abort(405)
# 密碼加密處理
def generate_passwd(passwd):
hash = hashlib.md5()
hash.update(passwd.encode('utf-8'))
return hash.hexdigest()
jsonify: json序列化
四、Flask-RESTful插件
Flask-RESTful添加快速構建REST API的支持,也是一個能夠和現有ORM庫協(xié)同工作的輕量級的擴展培己。Flask-RESTful鼓勵以最小的設置的最佳實踐糜烹。
中文文檔: http://www.pythondoc.com/Flask-RESTful/
- 基本使用
- 安裝
pip install flask-restful
- 配置
# ext.py文件中
from flask_restful import Api
api = Api()
api.init_app(app)
- 使用(項目拆分)
# 定義一個資源
# views.py已經可以替換為apis.py,之前的路由功能換種寫法
class HelloWorld(Resource):
def get(self): # get請求
return {'msg': 'hello world'}
def post(self): # post請求
return {'msg': '你好!'}
# 添加一個資源
# add_resource 注冊路由到框架上
# 如果沒有指定 endpoint漱凝,Flask-RESTful 會根據類名生成一個
# 但有時候如 url_for 需要 endpoint,因此最好明確給 endpoint 賦值
api.add_resource(HelloWorld, '/hello/',endpoint='hello')
Flask-Rest-JSONAPI插件诸迟、Flask-Restless插件
- 項目拆分
備注:
之前路由的操作都是在views.py中茸炒,現在只需要提供API接口,所以功能就會不一樣阵苇;
在項目進行完基本拆分之后壁公,可以將views.py改為apis.py;
資源不止一個绅项,而不能都只是有一個紊册,而是有多個,為了方便管理快耿,可以將apis變?yōu)榘男问侥叶福奖愎芾?
# App/apis/__init__.py
api = Api()
def init_api(app):
api.init_app(api)
# App/__init_.py [之前init_blue 換為 init_api即可]
init_api(app)
# App/apis/HelloResource.py 定義資源
class HelloWorld(Resource):
def get(self): # get請求
return {'msg': 'hello world'}
# App/apis/__init__.py 添加資源
api.add_resource(HelloResource, '/hello/', endpoint='hello')
- 帶參數操作
# App/apis/UserApi.py文件
class UserResource(Resource):
def get(self,id):
str = '(get)userid: %d' % id
return {'msg':str}
def post(self,id):
str = '(post)userid: %d' % id
return {'msg': str}
# App/apis/__init__.py文件
from App.apis import UserAPI
api.add_resource(UserResource, '/user/<int:id>/', endpoint='user')
Flask-RESTful 提供的最主要的基礎就是資源(resources)。資源(Resources)是構建在 Flask視圖 之上掀亥,只要在你的資源(resource)上定義方法就能夠容易地訪問多個 HTTP 方法撞反。[無需原生操作,因為一個資源而進行不同的判斷操作處理]
- 端點操作(Endpoints)
很多時候在一個 API 中搪花,你的資源可以通過多個 URL 訪問遏片。
你可以把多個 URL 傳給 Api 對象的 Api.add_resource() 方法。每一個 URL 都能訪問到你的 Resource撮竿。
api.add_resource(HelloWorld,
'/hello/',
'/haha/',
'/hehe/')
- 輸出格式定制
默認情況下吮便,在你的返回迭代中所有字段將會原樣呈現。
實際更多的需要一個字典類型數據幢踏,之后通過JSON序列化即可髓需。
Flask-RESTful 提供了 fields 模塊和 marshal_with() 裝飾器來進行數據格式化。
# @marshal_with(需要返回的數據格式)
如果返回的數據惑折,在預定義的結構中不存在授账,數據會被自動濾掉;
如果返回的數據惨驶,在預定的結構中存在白热,數據會正常返回;
如果返回的數據比預定的結構字段少粗卜,預定義的字段會顯示默認值屋确;
# 支持類型
- 常用基本類型
String
Integer
- 列表類型
List
- 級聯(lián)類型
Nested
- 結構嵌套
fields.List(fields.Nested())
# 示例1
""" 獲取一只貓的數據基本結構
{
'msg':'ok',
'status':200,
'data': {
'id': 1,
'name': 'TOM',
'color': '紅色'
}
}
"""
catmodel_fields = {
'id': fields.Integer,
'name': fields.String,
'color': fields.String
}
onecat_fields = {
'msg': fields.String(default='ok'),
'status': fields.Integer(default=200),
'data': fields.Nested(catmodel_fields) # 嵌套
}
class OneCatResource(Resource):
@marshal_with(onecat_fields)
def get(self):
cat = Cat.query.first()
data = {
# msg 使用默認值,可以省略不寫
# status 使用默認只,也可以不寫
'data': cat
}
return data
# 示例2
""" 獲取所有貓的數據結構
{
'msg':'ok',
'status': 200,
'data': [
{
'id': 1,
'name': 'TOM1',
'color': '紅色'
},
{
'id': 2,
'name': 'TOM2',
'color': '紅色'
},
{
'id': 3,
'name': 'TOM3',
'color': '紅色'
},
...
]
}
"""
catmodel_fields = {
'id': fields.Integer,
'name': fields.String,
'color': fields.String
}
cats_fields = {
'msg': fields.String(default='ok'),
'status': fields.Integer(default=200),
'data': fields.List(fields.Nested(catmodel_fields))
}
class CatResource(Resource):
@marshal_with(cats_fields)
def get(self):
cats = Cat.query.all()
data = {
'msg': 'ok!',
'status': 200,
'data': cats
}
return data
- 請求參數解析
- 基本使用
# https://127.0.0.1/showview/?page=1
parser = reqparse.RequestParser()
# 接受參數page,類型是str,錯誤提示help
parser.add_argument('page', type=str, help='請輸入頁碼')
class ShowView(Resource):
def get(self):
parse = parser.parse_args()
c_page = parse.get('page') or 1
def post(self):
parse = parser.parse_args()
c_page = parse.get('page') or 1
- 必須參數 required=True
# 該參數必須傳入
parser.add_argument('page', type=int, help='請輸入頁碼', required=True)
- 多參數(列表)
# 如果要接受一個鍵有多個值的話攻臀,可以傳入 action='append'
# https://127.0.0.1/showview/?name='liming'&page=1&name='zhangsan'
parser.add_argument('name', type=str, action='append')
- 參數位置
參數位置: form焕数、args、headers刨啸、cookies堡赔、files(上傳文件)