Flask 的 RESTful 架構(gòu) 的實例
在上一個小節(jié)中講解了 RESTful 架構(gòu)的概念,本節(jié)通過具體的實例加深對 RESTful 架構(gòu)的理解。
1. 程序功能和結(jié)構(gòu)
1.1 程序功能
本小節(jié)使用 RESTful 架構(gòu)完成一個在線通訊錄的程序,程序的界面如下:
程序提供如下功能:
-
在網(wǎng)站首頁展示所有的聯(lián)系人的信息,每個聯(lián)系人包含有姓名和電話兩個屬性;
-
添加新聯(lián)系人,在文本框中輸入姓名或者電話后,然后點擊 “新增” 按鈕完成;
-
修改聯(lián)系人,在文本框中修改姓名或者電話后,然后點擊 “修改” 按鈕完成;
-
刪除聯(lián)系人,通過點擊 “完成” 按鈕完成。
1.2 程序結(jié)構(gòu)
程序包括 4 個源文件,如下所示:
文件 | 功能 |
---|---|
app.py | 后端服務(wù)程序,提供展示、新增、修改、刪除聯(lián)系人的功能 |
templates/index.html | 頁面模板文件,提供展示、新增、修改、刪除聯(lián)系人的界面 |
static/style.css | 對頁面模板進(jìn)行美化的 css 文件 |
static/script.js | 前端調(diào)用后端服務(wù)的 js 文件 |
1.3 源程序下載
2. RESTful 架構(gòu)
2.1 資源
在通訊錄的 RESTful 架構(gòu)中,聯(lián)系人被抽象為一種資源,使用 URI 表示如下:
http://localhost/users/123
每個聯(lián)系人都有自己的 URI,同時,每個聯(lián)系人有一個唯一的 id,/users/123 是 id 為 123 的聯(lián)系人對應(yīng)的 URI。
2.2 訪問資源的 URI
客戶端可以新增、修改、刪除聯(lián)系人,相應(yīng)的 URI 如下:
HTTP 方法 | 行為 | URI |
---|---|---|
POST | 新增聯(lián)系人 | http://localhost/users |
PUT | 修改 id 為 123 的聯(lián)系人 | http://localhost/users/123 |
DELETE | 刪除 id 為 123 的聯(lián)系人 | http://localhost/users/123 |
3. 后端服務(wù) app.py
3.1 引入相關(guān)模塊
#!/usr/bin/python3
from flask import Flask, render_template, request, jsonify
from datetime import timedelta
app = Flask(__name__)
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = timedelta(seconds=1)
在第 2 行,從 flask 模塊中引入函數(shù) jsonify,該函數(shù)將 JSON 格式的數(shù)據(jù)轉(zhuǎn)換為字符串。
在第 6 行,配置 Flask 應(yīng)用的 SEND_FILE_MAX_AGE_DEFAULT 屬性,該屬性設(shè)置靜態(tài)文件緩存過期時間,在這個程序中將其設(shè)置為 1 秒,timedelta(seconds=1) 表示長度為 1 秒的時間。
這個程序使用了兩個靜態(tài)文件 style.css 和 script.js,默認(rèn)情況下,F(xiàn)lask 框架會緩存靜態(tài)文件(12 個小時),如果在服務(wù)端更新了靜態(tài)文件,瀏覽器訪問靜態(tài)文件無法及時獲得更新。為了方便調(diào)試程序,在這里配置靜態(tài)文件緩存過期時間為 1 秒。
3.2 聯(lián)系人數(shù)據(jù)庫
class User:
nextId = 0
def __init__(self, name, phone):
self.id = User.nextId
User.nextId += 1
self.name = name
self.phone = phone
tom = User('tom', '10086')
jerry = User('jerry', '12306')
users = [tom, jerry]
使用類 User 描述一個聯(lián)系人,每個聯(lián)系人包含有 3 個屬性:id、name、phone。
User.nextId 記錄了下一個可用的用戶 id,在 User.__init__ 中,使用 User.nextId 作為當(dāng)前新建用戶的 id,然后使用 User.nextId += 1 更新下一個可用的用戶 id。
在第 10 行和第 11 行,創(chuàng)建了兩個聯(lián)系人: tom 和 jerry。使用全局變量 users 記錄全部的聯(lián)系人,將 tom 和 jerry 加入到列表 users 中。
3.3 展示所有的聯(lián)系人
@app.route('/')
def index():
return render_template('index.html', users = users)
設(shè)置頁面路徑 / 的處理函數(shù) index,函數(shù) index 渲染模板文件 index.html。將全局變量 users 傳遞給頁面模板,users 中記錄了所有的聯(lián)系人,因此頁面中會展示所有的聯(lián)系人。
3.4 新增聯(lián)系人
@app.route('/users', methods=['POST'])
def addUser():
name = request.json['name']
phone = request.json['phone']
user = User(name, phone)
users.append(user)
return jsonify({'error': None});
通過 POST 方法訪問頁面路徑 /users 的處理函數(shù)是 addUser,在 RESTful 架構(gòu)中,POST /users 表示新增一個聯(lián)系人。
在第 3 行和第 4 行,request.json 是一個字典,記錄了客戶端發(fā)送的 JSON 格式的參數(shù):聯(lián)系人的姓名和電話;在第 6 行和第 7 行,將新建的聯(lián)系人加入到全局變量 users;最后,使用 jsonify 將 JSON 格式的數(shù)據(jù)轉(zhuǎn)換為字符串,{‘error’: None} 表示操作成功。
3.5 更新聯(lián)系人
@app.route('/users/<int:userId>', methods=['PUT'])
def updateUser(userId):
name = request.json['name']
phone = request.json['phone']
for user in users:
if user.id == userId:
user.name = name
user.phone = phone
break
return jsonify({'error': None});
在第 1 行,通過 PUT 方法訪問頁面路徑 /users/userId 的處理函數(shù)是 updateUser,在 RESTful 架構(gòu)中,PUT /users/userId 表示新增一個聯(lián)系人。
/users/<int:userId> 表示了一個動態(tài)路由,它可以匹配如下的路徑:
- /users/1,id 為 1 的聯(lián)系人對應(yīng)的 URI
- /users/2,id 為 2 的聯(lián)系人對應(yīng)的 URI
- /users/3,id 為 3 的聯(lián)系人對應(yīng)的 URI
對符號 <int:userId> 的解釋如下:
- int 表示匹配整數(shù),/users/<int:userId> 可以匹配 /users/1 (1 是一個整數(shù)),不能匹配 /users/tom (tom 不是整數(shù));
- userId 表示匹配的值,/users/<int:userId> 和 /users/1 匹配后,userId 為 1,它作為參數(shù)被傳遞給頁面處理函數(shù) updateUser。
在第 3 行和第 4 行,request.json 是一個字典,記錄了客戶端發(fā)送的 JSON 格式的參數(shù):聯(lián)系人的姓名和電話;在第 6 行,在全局變量 users 根據(jù) userId 查找指定的 user 并更新;最后,使用 jsonify 將 JSON 格式的數(shù)據(jù)轉(zhuǎn)換為字符串,{‘error’: None} 表示操作成功。
3.6 刪除聯(lián)系人
@app.route('/users/<int:userId>', methods=['DELETE'])
def deleteUser(userId):
index = 0
for user in users:
if user.id == userId:
del users[index]
break
index += 1
return jsonify({'error': None});
if __name__ == '__main__':
app.run(debug = True)
在第 1 行,通過 DELETE 方法訪問頁面路徑 /users 的處理函數(shù)是 deleteUser,在 RESTful 架構(gòu)中,DELETE /users/userId 表示新增一個聯(lián)系人。
/users/<int:userId> 表示了一個動態(tài)路由,在 3.5 小節(jié)進(jìn)行了詳細(xì)的解釋。
在第 4 行,根據(jù) userId 在全局變量 users 中查找相應(yīng)的 user 并刪除;最后,使用 jsonify 將 JSON 格式的數(shù)據(jù)轉(zhuǎn)換為字符串,{‘error’: None} 表示操作成功。
4. 模板文件 templates/index.html
模板文件 templates/index.html 是網(wǎng)站的首頁面,分為 3 個部分描述。
4.1 引入 css 和 js 文件
<html>
<head>
<meta charset='utf-8'>
<script src="https://lib.baomitu.com/jquery/2.2.4/jquery.min.js"></script>
<link href="{{url_for('static', filename='style.css')}}" rel="stylesheet">
<script src="{{url_for('static', filename='script.js')}}"></script>
</head>
在第 4 行,引入庫 jquery,在前端需要使用 jquery 的 ajax 功能調(diào)用后端服務(wù)。
在第 5 行,引入靜態(tài)文件 style.css,函數(shù) url_for(‘static’, filename=‘style.css’) 的輸出為 ‘/static/style.css’。
在第 6 行,引入靜態(tài)文件 script.js,函數(shù) url_for(‘static’, filename=‘script.js’) 的輸出為 ‘/static/script.js’。
4.2 新增聯(lián)系人
<body>
<h1>通訊錄</h1>
<div class="row">
<input type="text" placeholder='姓名'>
<input type="text" placeholder='電話'>
<span class="button" onclick="addUser(this);">增加</span>
</div>
新增聯(lián)系人的界面包含 3 個部分:
- 一個文本字段用于填寫姓名;
- 一個文本字段用于填寫電話;
- 一個按鈕,用于增加聯(lián)系人,設(shè)置按鈕的 onclick 函數(shù),點擊按鈕時執(zhí)行函數(shù) addUser(this),其中 this 指向按鈕對應(yīng)的 DOM 元素。
4.3 展示所有的聯(lián)系人
<div>
{% for user in users %}
<div class="row">
<input type="text" value="{{user.name}}" placeholder='姓名'>
<input type="text" value="{{user.phone}}" placeholder='電話'>
<span class="button" onclick="updateUser(this, {{user.id}});">修改</span>
<span class="button" onclick="deleteUser(this, {{user.id}});">刪除</span>
</div>
{% endfor %}
</div>
</body>
</html>
Flask 程序?qū)⑷肿兞?users 傳遞給渲染模板文件 templates/index.html,全局變量 users 包含有所有的聯(lián)系人。
在第 2 行,逐行展示所有的聯(lián)系人,每個聯(lián)系人包括 4 個部分:
- 一個文本字段展示姓名,值為 user.name;
- 一個文本字段展示電話,值為 user.phone;
- 一個按鈕,用于修改聯(lián)系人,點擊按鈕時執(zhí)行函數(shù) updateUser(this),其中 this 指向按鈕對應(yīng)的 DOM 元素;
- 一個按鈕,用于刪除聯(lián)系人,點擊按鈕時執(zhí)行函數(shù) updateUser(this),其中 this 指向按鈕對應(yīng)的 DOM 元素。
設(shè)置按鈕的 onclick 函數(shù),點擊按鈕時執(zhí)行函數(shù) addUser(this),其中 this 指向按鈕對應(yīng)的 DOM 元素。
5. 美化界面 static/style.css
.button {
color: white;
background-color: black;
padding-left: 2px;
padding-right: 2px;
border: 1px solid black;
border-radius: 4px;
cursor: pointer;
}
.row {
margin-top: 4px;
margin-bottom: 4px;
}
樣式 .button 用于美化按鈕,在第 2 行,設(shè)置 button 的背景和前景;在第 6 行,設(shè)置 button 的邊框;在第 7 行,設(shè)置 button 為圓角。
樣式 .row 用于設(shè)置聯(lián)系人之間的行間距。
6. 前端調(diào)用后端服務(wù) static/script.js
6.1 配置 ajax
function ajaxError()
{
alert('ajax error');
}
function ajaxSuccess(result)
{
if (result.error) {
alert('操作失敗');
return;
}
location.reload();
}
在 RESTful 架構(gòu)中,客戶端使用 ajax 技術(shù)請求服務(wù)端的服務(wù)。當(dāng) ajax 請求失敗時,調(diào)用 ajaxError,提示用戶 ajax 請求服務(wù)失??;當(dāng) ajax 請求成功時,調(diào)用 ajaxSuccess,提示用戶 ajax 請求服務(wù)成功。
在網(wǎng)站的首頁展示所有的聯(lián)系人,當(dāng)新增、修改、刪除聯(lián)系人后,需要刷新首頁面,因此,在第 12 行,當(dāng) ajax 調(diào)用服務(wù)成功后,需要 location.reload() 刷新頁面,從服務(wù)端重新加載所有的聯(lián)系人。
6.2 新增聯(lián)系人
function addUser(button)
{
var children = $(button).parent().children();
var name = children.eq(0).val();
var phone = children.eq(1).val();
var data = JSON.stringify({'name': name, 'phone': phone});
$.ajax({
'url': '/users',
'type': 'POST',
'contentType': 'application/json',
'data': data,
'dataType': 'json',
'error': ajaxError,
'success': ajaxSuccess
});
}
點擊 “新增” 按鈕后,執(zhí)行函數(shù) addUser(button),button 指向的是 “新增” 按鈕。在 templates/index.html 中,按鈕、聯(lián)系人姓名、聯(lián)系人電話于相同的 DIV 中,如下所示:
<div class="row">
<input type="text" placeholder='姓名'>
<input type="text" placeholder='電話'>
<span class="button" onclick="addUser(this);">增加</span>
</div>
在第 3 行到第 5 行,表達(dá)式的含義如下所示:
表達(dá)式 | 含義 |
---|---|
$(button).parent() | 指向按鈕的父節(jié)點 |
$(button).parent().children() | 表示 div 的 3 個子節(jié)點 |
children.eq(0) | 指向聯(lián)系人姓名 |
children.eq(1) | 指向聯(lián)系人電話 |
children.eq(2) | 指向新增按鈕 |
在第 4 行,根據(jù)聯(lián)系人的姓名和電話創(chuàng)建一個新聯(lián)系人,將它作為參數(shù)、調(diào)用后端新增聯(lián)系人的服務(wù)。
在第 8 行,通過 jquery 的 ajax 函數(shù)調(diào)用后端服務(wù),設(shè)置 url 為 ‘/users/’、type 為 ‘POST’ ,表示 RESTful 架構(gòu)下的新增聯(lián)系人。
6.3 更新聯(lián)系人
function updateUser(button, userId)
{
var children = $(button).parent().children();
var name = children.eq(0).val();
var phone = children.eq(1).val();
var data = JSON.stringify({'name': name, 'phone': phone});
$.ajax({
'url': '/users/' + userId,
'type': 'PUT',
'contentType': 'application/json',
'data': data,
'dataType': 'json',
'error': ajaxError,
'success': ajaxSuccess
});
}
點擊 “更新” 按鈕后,執(zhí)行函數(shù) updateUser(button, userId),button 指向的是 “新增” 按鈕,userId 是需要更新的聯(lián)系人 id。
在第 3 行到第 5 行,獲取需要聯(lián)系人的姓名和電話,使用了和 6.2 小節(jié)相同的方法,請參考 6.2 小節(jié)。
在第 8 行,通過 jquery 的 ajax 函數(shù)調(diào)用后端服務(wù),設(shè)置 url 為 ‘/users/userId’、type 為 ‘PUT’ ,表示 RESTful 架構(gòu)下的更新聯(lián)系人。
6.4 刪除聯(lián)系人
function deleteUser(button, userId)
{
var children = $(button).parent().children();
var data = JSON.stringify({});
$.ajax({
'url': '/users/' + userId,
'type': 'DELETE',
'contentType': 'application/json',
'data': data,
'dataType': 'json',
'error': ajaxError,
'success': ajaxSuccess
});
}
點擊 “刪除” 按鈕后,執(zhí)行函數(shù) deleteUser(button, userId),button 指向的是 “新增” 按鈕,userId 是需要更新的聯(lián)系人 id。
在第 3 行到第 5 行,獲取需要聯(lián)系人的姓名和電話,使用了和 6.2 小節(jié)相同的方法,請參考 6.2 小節(jié)。
在第 8 行,通過 jquery 的 ajax 函數(shù)調(diào)用后端服務(wù),設(shè)置 url 為 ‘/users/userId’、type 為 ‘DELETE’ ,表示 RESTful 架構(gòu)下的刪除聯(lián)系人。
7. 小結(jié)
本節(jié)使用 RESTful 架構(gòu)完成一個在線通訊錄的程序,通過實例講解了 RESTful 架構(gòu)的設(shè)計與實現(xiàn),使用思維導(dǎo)圖概括如下: