flask restful-api實現及基於flask-httpauth實現基礎權限管控(一)

本系列教程分爲四個階段

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基礎

----

  1. 一個flask小程序(使用pycharm開發)
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
        return 'hello world'

if __name__ == (__main__):
        app.run(debug=True)

如上一些參數的說明:

  1. from flask import Flask 引入flask中的Flask類模塊
  2. app = Flask(__name__) 實例化Flask類
  3. @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中會進行系統的介紹。


寫在最後的話:
技術這一行,是一個不斷進步學習的過程,我們在學習別人的東西的同時一定要多思考,多實踐,才能變成自己的知識,以用於後續的變現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章