本系列教程分爲四個階段
1.flask restful web service
2.flask restful api
3.flask httpauth實現權限管控
4.uwsgi管理flask應用
文章摘自http://www.pythondoc.com/flask-restful/
並由本人修正其中的一些bug及版本更新導致的程序問題,去其糟粕,取其精華。
flask是當下python比較流行的web開發框架,遂在使用flask做了大大小小的項目之後,便想着做一篇博客,一方面以用來記錄關鍵性的知識,另一方面也便以那些想入門或者想了解python、flask的人。
----
本文需要讀者有一定的python基礎
----
- 一個flask小程序(使用pycharm開發)
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'hello world'
if __name__ == (__main__):
app.run(debug=True)
如上一些參數的說明:
- from flask import Flask 引入flask中的Flask類模塊
app = Flask(__name__)
實例化Flask類- @app.route('/') flask路由註釋器
運行如上的程序便可以啓動一個flask程序,此時訪問http://localhost:5000, 便可以訪問flask的程序。
如若想對程序的端口自定義,則在最後一行進行如下修改:app.run('0.0.0.0', 8080)
注意:此種方式只適用於使用python flask_app.py方式,若使用flask run方式則不生效,如有需要請自行查詢資料,後續生產環境不會採用任何直接運行的方式,均會使用uwsgi等代理方式管理程序,後續我們會介紹uwsgi相關使用。
此時程序將會啓動在本地的8080端口;
什麼是 REST?
六條設計規範定義了一個 REST 系統的特點:
客戶端-服務器: 客戶端和服務器之間隔離,服務器提供服務,客戶端進行消費。
無狀態: 從客戶端到服務器的每個請求都必須包含理解請求所必需的信息。換句話說, 服務器不會存儲客戶端上一次請求的信息用來給下一次使用。
可緩存: 服務器必須明示客戶端請求能否緩存。
分層系統: 客戶端和服務器之間的通信應該以一種標準的方式,就是中間層代替服務器做出響應的時候,客戶端不需要做任何變動。
統一的接口: 服務器和客戶端的通信方法必須是統一的。
按需編碼: 服務器可以提供可執行代碼或腳本,爲客戶端在它們的環境中執行。這個約束是唯一一個是可選的。
使用flask實現restful services
實現web services的第一個入口
#!flask/bin/python
from flask import Flask, jsonify
app = Flask(__name__)
tasks = [
{
'id': 1,
'title': u'Buy geries',
'description': u'Milk, Cheese, Pizza, Fruit, Tylenol',
'done': False
},
{
'id': 2,
'title': u'Learn Python',
'description': u'Need to find a good Python tutorial on the web',
'done': False
}
]
@app.route('/todo/api/v1.0/tasks', methods=['GET'])
def get_tasks():
return jsonify({'tasks': tasks})
if __name__ == '__main__':
app.run(debug=True)
如上tasks列表爲我們模擬的數據屬性,接口返回必須以json或str格式,否則報錯。
這裏的@app.route中我們定義了web URL訪問的路由,及http請求資源(methods)的方式,這裏涉及到一個知識點,http請求方式,下面列出常用的http請求資源方式:
========== =============================================== =============================
HTTP 方法 URL 動作
========== =============================================== ==============================
GET http://[hostname]/todo/api/v1.0/tasks 檢索任務列表
GET http://[hostname]/todo/api/v1.0/tasks/[task_id] 檢索某個任務
POST http://[hostname]/todo/api/v1.0/tasks 創建新任務
PUT http://[hostname]/todo/api/v1.0/tasks/[task_id] 更新任務
DELETE http://[hostname]/todo/api/v1.0/tasks/[task_id] 刪除任務
========== ================================================ =============================
下面我們根據常用的http請求資源方式進行接下來的restful services的編寫,在上面一個例子中我們已經實現了第一種GET方法,獲取任務列表中的所有數據,但很多時候我們頁面上並不需要展示所有數據,只需要展示部分數據,然後根據需要進行多次獲取,那就用到第二種GET方法,準確的說應該是GET路由參數。
注意:在瀏覽器中,所有請求均爲GET方法,之所以HTTP方法分以上幾種,大部分時候是由前端通過ajax/axios等技術請求獲取數據後進行數據渲染到頁面中,所以在測試restful services時,建議使用curl,或者藉助postman工具做專業化的開發測試。
注意:使用postman調用service時,要在請求頭中添加Content-Type: application/json
這裏我們藉助curl訪問我們剛纔創建的tasks函數:
$ curl -i http://localhost:5000/todo/api/v1.0/tasks
# Result:
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 294
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 04:53:53 GMT
{
"tasks": [
{
"description": "Milk, Cheese, Pizza, Fruit, Tylenol",
"done": false,
"id": 1,
"title": "Buy geries"
},
{
"description": "Need to find a good Python tutorial on the web",
"done": false,
"id": 2,
"title": "Learn Python"
}
]
}
可見tasks函數只是將tasks字典返回,並沒有什麼變化,接下來我們將嘗試對數據的其他操作:
獲取指定數據、修改數據、刪除數據
獲取單個任務數據:
@app.route('/todo/api/v1.0/tasks/<int:id>', methods=['GET'])
def get_task():
task = list(filter(lambda t: t['id'] == task_id, tasks))
if len(task) == 0:
# abort(404)
return jsonify({'error': 'no such data.'})
return jsonify({'task': task[0]})
這裏我們用到了filter()高階函數及lambda實現字典過濾,後續還會用到map()函數;
注意:在python3中需要對filter()、map()函數進行轉 <list> 後方可賦值使用,否則報錯。
訪問get_task方法:
# 訪問<id>爲2的數據
$ curl -i http://localhost:5000/todo/api/v1.0/tasks/2
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 151
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:21:50 GMT
{
"task": {
"description": "Need to find a good Python tutorial on the web",
"done": false,
"id": 2,
"title": "Learn Python"
}
}
# 訪問不存在的<id>數據
$ curl -i http://localhost:5000/todo/api/v1.0/tasks/3
HTTP/1.0 404 NOT FOUND
Content-Type: text/html
Content-Length: 238
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:21:52 GMT
{'error': 'no such data.'}
上述測試中我們進行了2次訪問,一次爲字典中存在的<id>爲2的數據,一個是<id>爲3不存在的數據,我們會看到數據返回是友好的自定義異常返回,在原文中使用的abort方法筆者不太建議使用,在實際應用中我們會有多個接口可能會出現訪問不到數據返回404的情況,我們並不希望所有的404返回都是如下信息:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>404 Not Found</title>
<h1>Not Found</h1>
這樣並不友好,且並不能幫助我們前端開發又或自測的時候有效的發現問題,所以通過return方法返回我們定義的異常問題。
當然這裏我們也把自定義abort異常方法實現,方法如下:
from flask import make_response
@app.errorhandler(404)
def not_found(error):
print(error)
return make_response(jsonify({'error': 'Not found'}), 404)
說明:
@app.errorhandler(404)爲flask中的異常裝飾器
def not_found(error) 函數定義中的傳參error不可留空,原因很簡單,裝飾器會去捕獲traceback並傳入not_found函數,可以自行查看error打印具體信息。
make_response函數爲flask服務返回方法。
對應的abort返回信息爲:
$ curl -i http://localhost:5000/todo/api/v1.0/tasks/3
HTTP/1.0 404 NOT FOUND
Content-Type: application/json
Content-Length: 26
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:36:54 GMT
{
"error": "Not found"
}
上述介紹獲取單個數據的方法,及flask的異常處理,接下來就是POST方法, 一般用於數據的添加、更新,表單提交等,方法如下:
from flask import request #http請求處理模塊,獲取請求頭、參數、提交數據等信息
@app.route('/todo/api/v1.0/tasks', methods=['POST'])
def create_task():
if not request.json or not 'title' in request.json:
return jsonify({'error': 'args not matching'})
task = {
'id': tasks[-1]['id'] + 1,
'title': request.json['title'],
'description': request.json.get('description', ""),
'done': False
}
tasks.append(task)
return jsonify({'task': task}), 201
請求該方法:
$ curl -i -H "Content-Type: application/json" -X POST -d '{"title":"Read a book"}' http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 201 Created
Content-Type: application/json
Content-Length: 104
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:56:21 GMT
{
"task": {
"description": "",
"done": false,
"id": 3,
"title": "Read a book"
}
}
注意:如果你在 Windows 上並且運行 Cygwin 版本的 curl,上面的命令不會有任何問題。然而,如果你使用原生的 curl,命令會有些不同:
curl -i -H "Content-Type: application/json" -X POST -d "{"""title""":"""Read a book"""}" http://localhost:5000/todo/api/v1.0/tasks
再次請求我們剛開始的GET方法請求數據,查看數據變化:
$ curl -i http://localhost:5000/todo/api/v1.0/tasks
HTTP/1.0 200 OK
Content-Type: application/json
Content-Length: 423
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Mon, 20 May 2013 05:57:44 GMT
{
"tasks": [
{
"description": "Milk, Cheese, Pizza, Fruit, Tylenol",
"done": false,
"id": 1,
"title": "Buy geries"
},
{
"description": "Need to find a good Python tutorial on the web",
"done": false,
"id": 2,
"title": "Learn Python"
},
{
"description": "",
"done": false,
"id": 3,
"title": "Read a book"
}
]
}
此時可以看到我們定義的tasks原始字典中,多出了<id>爲3 的新增數據,說明我們POST數據添加接口正常。
關於剩下的更新、刪除方法,就不再一一闡述了,以下直接列出實現方法:
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['PUT'])
def update_task(task_id):
task = list(filter(lambda t: t['id'] == task_id, tasks))
if len(task) == 0:
abort(404)
if not request.json:
abort(400)
if 'title' in request.json and type(request.json['title']) != unicode:
abort(400)
if 'description' in request.json and type(request.json['description']) is not unicode:
abort(400)
if 'done' in request.json and type(request.json['done']) is not bool:
abort(400)
task[0]['title'] = request.json.get('title', task[0]['title'])
task[0]['description'] = request.json.get('description', task[0]['description'])
task[0]['done'] = request.json.get('done', task[0]['done'])
return jsonify({'task': task[0]})
@app.route('/todo/api/v1.0/tasks/<int:task_id>', methods=['DELETE'])
def delete_task(task_id):
task = list(filter(lambda t: t['id'] == task_id, tasks))
if len(task) == 0:
abort(404)
tasks.remove(task[0])
return jsonify({'result': True})
以上就是flask實現restful web services的方法,當然flask可以實現的遠不止於此,這裏只是起到拋磚引玉的效果,後續的工作開發中大家會慢慢的發現flask的魅力,強大。
原文中這裏緊接着介紹了關於web services的基礎認證,筆者思量之後決定不在此處進行闡述,在後續的關於restful services的httpauth中會進行系統的介紹。
寫在最後的話:
技術這一行,是一個不斷進步學習的過程,我們在學習別人的東西的同時一定要多思考,多實踐,才能變成自己的知識,以用於後續的變現。