Flask之REST&API設計
一、REST(一種軟件架構(gòu)風格)?
?一)、問題?
?網(wǎng)絡應用程序,分為前端和后端兩個部分。當前的發(fā)展趨勢,就是前端設備層出不窮(手機、平板、桌面電腦、其他專用設備......)。?
?因此,必須有一種統(tǒng)一的機制,方便不同的前端設備與后端進行通信。這致使API構(gòu)架的流行。
?二)、基本概念
?REST是"Representational State Transfer"縮寫,即是"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。而"表現(xiàn)層"其實指的是"資源(Resource)"的表現(xiàn)層。
?一種軟件構(gòu)架風格,設計風格,而不是標準,只是提供了一組設計原則和約束條件。
它主要用于客戶端和服務端交互的軟件?;谶@個風格設計的軟件可以更簡潔,更有層次,更易于實現(xiàn)緩存機制等。
?REST其實是一種組織Web服務的架構(gòu),而并不是我們想象的那樣是實現(xiàn)Web服務的一種新的技術(shù),更沒有要求一定要使用HTTP。其目標是為了創(chuàng)建具有良好擴展性的分布式系統(tǒng)。
1. 資源(Resource) 就是網(wǎng)絡上的一個實體,或是網(wǎng)絡上的具體信息,可以是一段文本、一張圖片、一首歌曲、一部電影。 每個資源都會對應的URL,且是唯一的標識符,想要獲取資源只需要調(diào)用對應URL。
2. 表現(xiàn)層(Representation) "資源"是一種信息實體,它可以有多種外在表現(xiàn)形式。 而"資源"具體呈現(xiàn)出來的形式,就叫它的"表現(xiàn)層"。
3. 狀態(tài)轉(zhuǎn)換(State Transfer) 訪問一個網(wǎng)站,就是客戶端和服務端的交互過程,這個過程中就會涉及到數(shù)據(jù)和狀態(tài)的變化。 互聯(lián)網(wǎng)通信協(xié)議HTTP,是無狀態(tài)協(xié)議。即所有狀態(tài)都保存在服務端。?
?客戶端要操作服務端必須通過某種方式,讓服務端發(fā)生"狀態(tài)轉(zhuǎn)換",而這轉(zhuǎn)換是建立在表現(xiàn)層之上的,所以就是"表現(xiàn)層狀態(tài)轉(zhuǎn)換"。?
?客戶端用到的手段只能是HTTP協(xié)議,在操作方式的動詞:?
?GET/POST/PUT/DELETE。
?對應GET獲取資源,
POST新建資源(或更新資源),
PUT更新資源,
DELETE刪除資源。
?三)、構(gòu)架級約束
1.使用客戶/服務器模型??蛻艉头掌髦g通過一個統(tǒng)一的接口來互相通訊
2.層次化的系統(tǒng)。在一個REST系統(tǒng)中,客戶端并不會固定地與一個服務器打交道
3.無狀態(tài)。在一個REST系統(tǒng)中,服務端并不會保存有關(guān)客戶的任何狀態(tài)。也就是說,客戶端自身負責用戶狀態(tài)的維持,并在每次發(fā)送請求時都需要提供足夠的信息
4.可緩存。REST系統(tǒng)需要能夠恰當?shù)鼐彺嬲埱?,以盡量減少服務端和客戶端之間的信息傳輸,以提高性能
5.統(tǒng)一的接口。一個REST系統(tǒng)需要使用一個統(tǒng)一的接口來完成子系統(tǒng)之間以及服務與用戶之間的交互。這使得REST系統(tǒng)中的各個子系統(tǒng)可以獨自完成演化?
?注意:一個系統(tǒng)滿足了上面所列出的五條約束,那么該系統(tǒng)就被稱為是RESTful
二、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的具體網(wǎng)址。在RESTful架構(gòu)中,每個網(wǎng)址代表一種資源,所以網(wǎng)址不能有動詞,只能有名詞。而所用名詞往往與數(shù)據(jù)庫表單名對應。
?五)、HTTP動詞對于資源的具體操作類型,由HTTP動詞表示。
HTTP常用動詞:?
?- GET(SELECT) 從服務器取資源?
?- POST(CREATE or UPDATE) 服務器中創(chuàng)建資源或更新資源?
?- PUT(UPDATE) 在服務器更新資源(客戶端提供改變后的完整資源)?
?- PATCH(UPDATE) 在服務器更新資源(客戶端提供改變的屬性)?
?- DELETE(DELETE) 從服務器刪除資源 - HEAD 獲取資源的元數(shù)據(jù)
?- OPTHONS 獲取信息,關(guān)于資源的那些屬性是客戶端可以改變的例如:?
?- GET /students 獲取所有學生?
?- POST /student 新建學生?
?- GET /students/id 獲取某一個學生
?- PUT /students/id 更新某個學生的信息(需要提供學生的全部信息)
?- PATHC /students/id 更新某個學生的信息(需要提供學生變更信息) - DELETE /students/id 刪除某個學生
?六)、過濾信息當記錄數(shù)量過多,服務器不可能將它們返回給用戶。
APIT應該提供參數(shù),過濾返回結(jié)果。 ?limit=10 ?offset=10 ?page=2&per_page=10 ?sortby=name&order=desc ?student_id=id?
?七)、狀態(tài)碼
?服務器向用戶返回的狀態(tài)碼和提示信息。?
00 OK - [GET]:服務器成功返回用戶請求的數(shù)據(jù) 201 CREATED -[POST/PUT/PATCH]:用戶新建或修改數(shù)據(jù)成功?
?202 Accepted - [*] :表示一個請求已經(jīng)進入后臺排隊(異步任務)?
?204 NO CONTENT - [DELETE]:表示數(shù)據(jù)刪除成功?
?400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發(fā)出的請求有錯誤?
?401 Unauthorized - [*] :表示用戶沒有權(quán)限(令牌,用戶名,密碼錯誤)?
?403 Forbidden - [*]:表示用戶得到授權(quán),但是訪問是被禁止的?
?404 NOT FOUND - [*]:用戶發(fā)出的請求針對的是不存在的記錄?
?406 Not Acceptable - [*]:用戶請求格式不可得 410 Gone - [GET] :用戶請求的資源被永久移除,且不會再得到的?
?422 Unprocesable entity -[POST/PUT/PATCH]:當創(chuàng)建一個對象時,發(fā)生一個驗證錯誤
500 INTERNAL SERVER EROR - [*] :服務器內(nèi)部發(fā)生錯誤?
?2xx —— 正確的響應?
?3xx —— 重定向?
?4xx —— 客戶端錯誤?
?5xx —— 服務端錯誤
八)、返回結(jié)果
錯誤處理:
如果狀態(tài)碼是4xx,就應該向客戶端返回出錯信息,一般來說,返回信息中將error作為鍵名?
?針對不同操作,服務器向用戶返回的結(jié)果應符合一下規(guī)范:?
?GET /collection:返回資源對象的列表(數(shù)組,集合)?
?GET /collection/id:返回單個資源對象?
?POST /collection:返回新生成的資源對象?
?PUT /collection/id:返回完整的資源對象?
?PATCH /collection/id:返回完整的資源對象 DELETE /collection/id:返回一個空文檔注意:服務器返回的數(shù)據(jù)格式,應該盡量是JSON?
?九)、原生實現(xiàn)數(shù)據(jù)和狀態(tài)的返回@blue.route('/user/',methods=['POST','GET','PUT','DELETE','PATCH'])
def user(userid):?
?if request.method == 'GET': # 獲取用戶?
?????pass
elif request.method == 'POST': # 更新或創(chuàng)建用戶密碼
# 獲取數(shù)據(jù)
username = request.form.get('username')
password = request.form.get('password')
data = {?
?????????'msg':'ok',?
?????????'status': 201
}\
? ? ? # 不為空
? ? ? ? if not username or not password:?
?????????????data['msg'] = '參數(shù)不正確'?
?????????????data['status'] = 422 return jsonify(data),422
????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), 423
? ? return jsonify(data),201?
?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()
三、Flask-RESTful插件
?Flask-RESTful添加快速構(gòu)建REST API的支持,也是一個能夠和現(xiàn)有的ORM庫協(xié)同工作的輕量級的擴展。Flask-RESTful鼓勵以最小的設置的最佳實踐。
?一)、基本使用 安裝 pip install flask-restful?
?相關(guān)文檔:http://www.pythondoc.com/Flask-RESTful/quickstart.html?
?配置?
?#ext.py文件中?
?from flask-restful import Api?
?api = Api()
使用?
?# 定義一個資源 # views.py 已經(jīng)可以替換為apis.py,之前的路由功能換種寫法?
?class HelloWorld(Resource):
?????def get(self): # get 請求
?????return {
????????'msg':'hello world'
????????}
????def post(self): #post請求
?????return {
????????'msg':'你好!????
????????'}?
?#添加一個資源?
?#ext.py文件中?
?#add_resource 注冊路由到框架上
?#如果沒有指定 endpoint,Flask-RESTful會根據(jù)類名生成一個?
?#但有時候如 url_for 需要 endpoint ,因次最好明確給 endopint 賦值
?api.add_redource(HelloWorld,'/hello/',endopint ='hello')?
?備注:之前路的操作都是在views.py中,現(xiàn)在只需要提供API借口,所以功能就會不一樣。?
?在項目進行完基本的拆分之后,可以將views.py改變api.py。
注意:Flask-RES-JSON插件,F(xiàn)lask-RESTless插件?
?二)、帶參數(shù)操作
?#apis.py?
?class UserAPI(Resource):?
????def get(self,userid):?
?????????str = '(get)userid: %d' % userid?
?????????return {
????????????'msg':str
????????????}?
?????def post(self,id):
?????????str = '(post)userid: %d' %id
?????????return {
????????????'msg':str
????????????}?
?????#ext.py 文件
?????from App.apis import UserAPI ????????api.add_resource(UserAPI,'/user//',endopint='user')
? ? ? 注意:Flask-RESTful提供的最主要的基礎是資源(resources)。資源(Resources)是構(gòu)建在Flask試圖之上,只要在你的資源(resource)上定義方法就能夠容易地訪問 多個HTTP方法。[無需原生操作,因為一個資源而進行不同的判斷操作處理]
? ? 三)、端點操作(Endopints)
很多時候在一個 API 中,你的資源可以通過多個 URL 訪問。
? ? ? ? 你可以把 多個 URL傳給 Api對象 Api.add_resource()方法。每個URL都能訪問到你的 Resource.
api.add_resource(HelloWorld,'/hello/','/haha/','/hehe/')
? ? 四)、輸出格式定制
默認情況下,在你返回的迭代中所有字段將會原樣呈現(xiàn)。
實際更多的需要一個字典類型數(shù)據(jù),之后通過 JSON序列化即可。
Flask-RESTful 提供了 fields模塊和 marshal_with() 裝飾器來進行數(shù)據(jù)格式化。
? ? ? ? # @marshal_with(需要返回的數(shù)據(jù)格式)
? 如果返回的數(shù)據(jù),在預定義的結(jié)構(gòu)中不存在,會被自動過濾掉;
? 如果返回的數(shù)據(jù),在預定義的結(jié)構(gòu)中存在,數(shù)據(jù)會正常返回;
? 如果返回的數(shù)據(jù)比預定義的結(jié)構(gòu)字段少,預定義的字段會顯示默認值;
? #支持類型
常用基本類型
String
Integer
類表類型
List
級聯(lián)類型
Nested
結(jié)構(gòu)嵌套
fields.List(fields.Nesred())
? # 示例1
"""獲取一只貓的數(shù)據(jù)基本結(jié)構(gòu)
? {
????'msg':'ok',
????'status':200,
????'data':{
????'id':1,
????'name':'TOM',
????'color':'紅色'
????}
}
"""
? ? catmodel_fields = {
????????????'id':fields.Integer,
????????????'name':fields.String,
????????????'color':fields.String
}
onecar_fields = {
????????????'msg': fields.String(default='ok'),
????????????'status': fields.Integer(default=200),
????????????'data': fields.Nested(catmodel_fields)? # 嵌套
? ? }
? ? class OneCatResource(Resource):
????@marshal_with(onecar_fields)
????def get(self):
? ? ????cat = Cat.query.first()
? ? ????????data = {
???????????????????# msg 使用默認值,可以省略不寫
? ? ? ? ? ? ? ? ?# status 使用默認只,也可以不寫
????????????????????'data': cat
? ????? }
? ? return data
# 示例2
? ? """ 獲取所有貓的數(shù)據(jù)結(jié)構(gòu)
????{
? ? ????'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
? ? 五)、請求參數(shù)解析
基本使用
# https://127.0.0.1/showview/?page=1
parser = reqparse.RequestParser()
#接受參數(shù)page,類型是str,錯誤提示help
parser.add_argument('page',type=str,help='請輸入頁碼')
class ShouView(Resource):
? ? def? get(self):
parser = parser.parse_args()
? ? ? ? c_page = parser.get('page') or 1
? ? def post(self):
? parser = parser.parse_args()
? c_page = parser.get('page') or 1
? ? ? ? 必須參數(shù) required = Ture
? # 請求參數(shù)必須傳入
? parser.add_argument('page', type=int, help='請輸入頁碼', required=True)
多參數(shù)(列表)
? ? # 如果要接受一個鍵有多個值的話,可以傳入 action='append'
? ? # https://127.0.0.1/showview/?name='liming'&page=1&name='zhangsan'
? ? parser.add_argument('name', type=str, action='append')
參數(shù)位置
? ? 參數(shù)位置: form、args、headers、cookies、files(上傳文件)