一考杉、REST
- 問題
網(wǎng)絡(luò)應(yīng)用程序,分為前端和后端兩個部分舰始。當(dāng)前的發(fā)展趨勢崇棠,就是前端設(shè)備層出不窮(手機、平板丸卷、桌面電腦枕稀、其他專用設(shè)備......)。
因此谜嫉,必須有一種統(tǒng)一的機制萎坷,方便不同的前端設(shè)備與后端進行通信。這致使API構(gòu)架的流行骄恶。
- 基本概念
REST是"Representational State Transfer"縮寫食铐,即是"表現(xiàn)層狀態(tài)轉(zhuǎn)化"。而"表現(xiàn)層"其實指的是"資源(Resource)"的表現(xiàn)層僧鲁。
一種軟件架構(gòu)風(fēng)格虐呻、設(shè)計風(fēng)格、而不是標準寞秃,只是提供了一組設(shè)計原則和約束條件斟叼。
它主要用于客戶端和服務(wù)器交互類的軟件〈菏伲基于這個風(fēng)格設(shè)計的軟件可以更簡潔朗涩,更有層次,更易于實現(xiàn)緩存機制等绑改。
REST其實是一種組織Web服務(wù)的架構(gòu)谢床,而并不是我們想象的那樣是實現(xiàn)Web服務(wù)的一種新的技術(shù),更沒有要求一定要使用HTTP厘线。其目標是為了創(chuàng)建具有良好擴展性的分布式系統(tǒng)识腿。
1\. 資源(Resource)
就是網(wǎng)絡(luò)上的一個實體,或是網(wǎng)絡(luò)上的具體信息造壮,可以是一段文本渡讼、一張圖片、一首歌曲、一部電影成箫。
每個資源都會對應(yīng)的URL展箱,且是唯一的標識符,想要獲取資源只需要調(diào)用對應(yīng)URL蹬昌。
2\. 表現(xiàn)層(Representation)
"資源"是一種信息實體混驰,它可以有多種外在表現(xiàn)形式。
而"資源"具體呈現(xiàn)出來的形式皂贩,就叫它的"表現(xiàn)層"账胧。
3\. 狀態(tài)轉(zhuǎn)換(State Transfer)
訪問一個網(wǎng)站,就是客戶端和服務(wù)端的交互過程先紫,這個過程中就會涉及到數(shù)據(jù)和狀態(tài)的變化治泥。
互聯(lián)網(wǎng)通信協(xié)議HTPP,是無狀態(tài)協(xié)議遮精。即所有狀態(tài)都保存在服務(wù)端居夹。
客戶端要操作服務(wù)端必須通過某種方式,讓服務(wù)端發(fā)生"狀態(tài)轉(zhuǎn)換"本冲,而這轉(zhuǎn)換是建立在表現(xiàn)層之上的准脂,所以就是"表現(xiàn)層狀態(tài)轉(zhuǎn)換"。
客戶端用到的手段只能是HTTP協(xié)議檬洞,在操作方式的動詞: GET/POST/PUT/DELETE狸膏。
對應(yīng)GET獲取資源,POST新建資源(或更新資源)添怔,PUT更新資源湾戳,DELETE刪除資源。
- 架構(gòu)級約束
1.使用客戶/服務(wù)器模型广料±裕客戶和服務(wù)器之間通過一個統(tǒng)一的接口來互相通訊
2.層次化的系統(tǒng)。在一個REST系統(tǒng)中艾杏,客戶端并不會固定地與一個服務(wù)器打交道
3.無狀態(tài)韧衣。在一個REST系統(tǒng)中,服務(wù)端并不會保存有關(guān)客戶的任何狀態(tài)购桑。也就是說畅铭,客戶端自身負責(zé)用戶狀態(tài)的維持,并在每次發(fā)送請求時都需要提供足夠的信息
4.可緩存勃蜘。REST系統(tǒng)需要能夠恰當(dāng)?shù)鼐彺嬲埱笏敦员M量減少服務(wù)端和客戶端之間的信息傳輸,以提高性能
5.統(tǒng)一的接口元旬。一個REST系統(tǒng)需要使用一個統(tǒng)一的接口來完成子系統(tǒng)之間以及服務(wù)與用戶之間的交互榴徐。這使得REST系統(tǒng)中的各個子系統(tǒng)可以獨自完成演化
一個系統(tǒng)滿足了上面所列出的五條約束,那么該系統(tǒng)就被稱為是RESTful匀归。
- 什么是RESTful框架
1\. 每一個URL代表一種資源
2\. 客戶端和服務(wù)器之間坑资,傳遞這種資源的某種表現(xiàn)層
3\. 客戶端通過四個HTTP動詞,對服務(wù)端進行操作穆端,實現(xiàn)"表現(xiàn)層狀態(tài)轉(zhuǎn)換"
RESTful API是目前比較成熟的一套互聯(lián)網(wǎng)應(yīng)用程序的API設(shè)計理論袱贮。
二、返回JSON格式數(shù)據(jù)
天氣API: https://www.sojson.com/api/weather.html
- jsonify序列化操作
# 返回json格式數(shù)據(jù)
@blue.route('/getjson/')
def getjson():
data = {
'data': 'hello flask',
'status': 200
}
return jsonify(data)
實現(xiàn)前后端的分離体啰,后臺人員只需要提供API接口接口攒巍。
- 后臺人員工作職責(zé)
- 定制接口
- 模型定制
- 面向接口編程
只關(guān)注請求地址
值關(guān)注請求結(jié)果格式
- 示例
- 在app/views.py中添加獲取商品列表數(shù)據(jù)API 【后臺人員】
# 學(xué)生成績API
@blue.route('/getscore/')
def getscore():
data = {
'msg':'學(xué)生成績列表',
'status': '200',
'content': [20,32,53,90,43,67,99,89]
}
return jsonify(data)
備注: 獲取數(shù)據(jù) http://127.0.0.1:8000/getscore/
- 在app/static/html/testlist.html 【前端人員】
# 發(fā)起Ajax請求,獲取對應(yīng)學(xué)生成績列表json數(shù)據(jù)荒勇,解析json數(shù)據(jù)柒莉,并渲染到頁面中
<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相關(guān)的內(nèi)容兢孝,而不是模板,所以注釋等方式是會不一樣的=鲑恕?缧贰!
三橘沥、RESTful API設(shè)計
- 協(xié)議
API與用戶的通信協(xié)議窗轩,通常使用HTTP(S)協(xié)議。
- 域名
應(yīng)該盡量將API部署在專用域名之下座咆。
如: http://api.zyz.com
如果確定API很簡單痢艺,不會有大規(guī)模擴從,可以考慮放在主域名之下介陶。
如: http://www.zyz.com/api/
- 版本
應(yīng)該將API的版本號放入URL腹备。
如: http://api.zyz.com/v1/
也有將版本號放在HTTP的頭信息中,但不如放在URL中方便直觀斤蔓,Github就是這么做的植酥。
- 路徑
路徑又稱"終點"(endpoint),表示API的具體網(wǎng)址弦牡。
在RESTful架構(gòu)中友驮,每個網(wǎng)址代表一種資源,所以網(wǎng)址不能有動詞驾锰,只能有名詞卸留。
而所用名詞往往與數(shù)據(jù)庫表單名對應(yīng)。
- HTTP動詞
對于資源的具體操作類型椭豫,由HTTP動詞表示耻瑟。
HTTP常用動詞:
- GET(SELECT) 從服務(wù)器取資源
- POST(CREATE or UPDATE) 服務(wù)器中創(chuàng)建資源或更新資源
- PUT(UPDATE) 在服務(wù)器更新資源(客戶端提供改變后的完整資源)
- PATCH(UPDATE) 在服務(wù)器更新資源(客戶端提供改變的屬性)
- DELETE(DELETE) 從服務(wù)器刪除資源
- HEAD 獲取資源的元數(shù)據(jù)
- OPTHONS 獲取信息旨指,關(guān)于資源的那些屬性是客戶端可以改變的
例如:
- GET /students 獲取所有學(xué)生
- POST /student 新建學(xué)生
- GET /students/id 獲取某一個學(xué)生
- PUT /students/id 更新某個學(xué)生的信息(需要提供學(xué)生的全部信息)
- PATHC /students/id 更新某個學(xué)生的信息(需要提供學(xué)生變更信息)
- DELETE /students/id 刪除某個學(xué)生
- 過濾信息
當(dāng)記錄數(shù)量過多,服務(wù)器不可能將它們返回給用戶喳整。APIT應(yīng)該提供參數(shù)谆构,過濾返回結(jié)果。
?limit=10
?offset=10
?page=2&per_page=10
?sortby=name&order=desc
?student_id=id
- 狀態(tài)碼
服務(wù)器向用戶返回的狀態(tài)碼和提示信息框都。
200 OK - [GET]:服務(wù)器成功返回用戶請求的數(shù)據(jù)
201 CREATED -[POST/PUT/PATCH]:用戶新建或修改數(shù)據(jù)成功
202 Accepted - [*] :表示一個請求已經(jīng)進入后臺排隊(異步任務(wù))
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]:當(dāng)創(chuàng)建一個對象時,發(fā)生一個驗證錯誤
500 INTERNAL SERVER EROR - [*] :服務(wù)器內(nèi)部發(fā)生錯誤
2xx —— 正確的響應(yīng)
3xx —— 重定向
4xx —— 客戶端錯誤
5xx —— 服務(wù)端錯誤
- 錯誤處理
如果狀態(tài)碼是4xx谓罗,就應(yīng)該向用戶返回出錯信息粱哼。一般來說,返回的信息中將error做為鍵名
- 返回結(jié)果
針對不同操作檩咱,服務(wù)器想用戶返回的結(jié)果應(yīng)該符合以下規(guī)范:
GET /collection:返回資源對象的列表(數(shù)組皂吮,集合)
GET /collection/id:返回單個資源對象
POST /collection:返回新生成的資源對象
PUT /collection/id:返回完整的資源對象
PATCH /collection/id:返回完整的資源對象
DELETE /collection/id:返回一個空文檔
- 核心點
RESTfull 的核心是 resource ,對一個 resource 税手,用不同的 HTTP verb 做增刪查改蜂筹。
- 其他
服務(wù)器返回的數(shù)據(jù)格式,應(yīng)該盡量使用JSON
- 原生實現(xiàn)
@blue.route('/api/v1/users/',methods=['GET','POST','DELETE','PUT','PATCH'])
def users():
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)
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添加快速構(gòu)建REST API的支持艺挪,也是一個能夠和現(xiàn)有ORM庫協(xié)同工作的輕量級的擴展。Flask-RESTful鼓勵以最小的設(shè)置的最佳實踐兵扬。
中文文檔: 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已經(jīng)可以替換為apis.py麻裳,之前的路由功能換種寫法
class HelloWorld(Resource):
def get(self): # get請求
return {'msg': 'hello world'}
def post(self): # post請求
return {'msg': '你好!'}
# 添加一個資源
# add_resource 注冊路由到框架上
# 如果沒有指定 endpoint,F(xiàn)lask-RESTful 會根據(jù)類名生成一個
# 但有時候如 url_for 需要 endpoint器钟,因此最好明確給 endpoint 賦值
api.add_resource(HelloWorld, '/hello/',endpoint='hello')
Flask-Rest-JSONAPI插件津坑、Flask-Restless插件
- 項目拆分
備注:
之前路由的操作都是在views.py中,現(xiàn)在只需要提供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')
- 帶參數(shù)操作
# 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 提供的最主要的基礎(chǔ)就是資源(resources)韧拒。資源(Resources)是構(gòu)建在 Flask視圖 之上淹接,只要在你的資源(resource)上定義方法就能夠容易地訪問多個 HTTP 方法十性。[無需原生操作,因為一個資源而進行不同的判斷操作處理]
- 端點操作(Endpoints)
很多時候在一個 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ù)鄙才,在預(yù)定義的結(jié)構(gòu)中不存在,數(shù)據(jù)會被自動濾掉促绵;
如果返回的數(shù)據(jù)攒庵,在預(yù)定的結(jié)構(gòu)中存在,數(shù)據(jù)會正常返回败晴;
如果返回的數(shù)據(jù)比預(yù)定的結(jié)構(gòu)字段少浓冒,預(yù)定義的字段會顯示默認值;
# 支持類型
- 常用基本類型
String
Integer
- 列表類型
List
- 級聯(lián)類型
Nested
- 結(jié)構(gòu)嵌套
fields.List(fields.Nested())
# 示例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
}
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
""" 獲取所有貓的數(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 ShowView(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=True
# 該參數(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(上傳文件)
作者:西門奄
鏈接:http://www.reibang.com/u/77035eb804c3
來源:簡書
簡書著作權(quán)歸作者所有或油,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。