1. 程式人生 > >Flask 框架 - 檢視及路由 - 2

Flask 框架 - 檢視及路由 - 2

1 學習目標

  1. 能夠寫出帶有引數的路由及檢視函式

  2. 能夠說出 url_for 函式的作用

  3. 能夠說出自定義轉換器的步驟

2 路由基本定義

  • 明確路由定義的引數,請求方式指定
  • PostMan 的使用

2.1 指定路由地址

# 指定訪問路徑為 demo1
@app.route('/demo1')
def demo1():
    return 'demo1'

2.2 給路由傳參示例

有時我們需要將同一類 URL 對映到同一個檢視函式處理,比如:使用同一個檢視函式來顯示不同使用者的個人資訊。

# 路由傳遞引數
@app.route('/user/<user_id>')
def user_info(user_id):
    return 'hello %s' % user_id
  • 路由傳遞的引數預設當做 string 處理,也可以指定引數的型別  <轉換器名字:引數名>
# 路由傳遞引數
@app.route('/user/<int:user_id>')
def user_info(user_id):
    return 'hello %d' % user_id

這裡指定int,尖括號中的內容是動態的,在此暫時可以理解為接受 int 型別的值,實際上 int 代表使用 IntegerConverter 去處理 url 傳入的引數

無論 user_id 是什麼型別,都可以用 %s 來輸出 

2.3 指定請求方式

在 Flask 中,定義一個路由,預設的請求方式為:

  • GET
  • OPTIONS(自帶)
  • HEAD(自帶)

如果想新增請求方試,那麼可以如下指定:

@app.route('/demo2', methods=['GET', 'POST'])
def demo2():
    # 直接從請求中取到請求方式並返回
    return request.method

demo2 請求方式為:

3 使用 PostMan 對請求進行測試

3.1 安裝 PostMan 外掛

PostMan 是一款功能強大的網頁除錯與傳送網頁 HTTP 請求的 Chrome 外掛,可以直接去對我們寫出來的路由和檢視函式進行除錯,作為後端程式設計師是必須要知道的一個工具。

安裝方式1:去 Chrome 商店直接搜尋 PostMan 擴充套件程式進行安裝

安裝方式2:https://www.getpostman.com/ 官網下載桌面版

安裝方式3:將已下載好的 PostMan 外掛資料夾拖入到瀏覽器

開啟 Chrome 的擴充套件程式頁面,開啟 開發者模式 選項

將外掛資料夾拖入到瀏覽器(或者點選載入已解壓的擴充套件程式選擇資料夾)

使用 PostMan,開啟之後,會彈出註冊頁面,選擇下方的 Skip this,go straight to the app 進行程式

3.2 postman使用

我們使用postman來測試一下這個函式:

 

使用如下:

4 檢視常用邏輯

  • 返回 JSON
  • 重定向
    • url_for
  • 自定義狀態碼

4.1 JSON資料格式&返回JSON

4.1.1 JSON 介紹

新建一個json檔案,我們來看一下json資料的個數。

json其實說白了就是一個字串,格式類似於python中的字典。

作用:用於瀏覽器與伺服器進行資料傳輸。

json:瀏覽器與伺服器直接進行資料傳輸的一種資料格式。

4.1.2 字典轉換為json

我們在程式碼中定義一個字典,然後將字典轉換為json格式的字串返回給瀏覽器
 

訪問結果:

驗證一下這個格式是否正確如下:(將上圖的json字串複製到下圖的大框中,然後點選校驗)

網址:http://www.kjson.com/

最終看到正確的json及說明是正確的json。

4.1.3 json轉換為字典

4.1.4 返回JSON

在使用 Flask 寫一個介面時候需要給客戶端返回 JSON 資料,在 Flask 中可以直接使用 jsonify 生成一個 JSON 的響應,並且以application/json的內容格式返回到瀏覽器.

# 返回JSON
@app.route('/demo4')
def demo4():
    json_dict = {
        "user_id": 10,
        "user_name": "laowang"
    }
    # jsonify 會指定響應內容的資料格式(告訴客戶端,返回的資料格式是什麼)
    return jsonify(json_dict)

直接返回json字串和通過jsonify返回json字串的區別:

jsonify:返回的時候content-type為application/json

直接返回:content-type為預設text/html

為什麼要用jsonify:這個跟前端邏輯有關係

不推薦使用 json.dumps 轉成 JSON 字串直接返回,因為返回的資料要符合 HTTP 協議規範,如果是 JSON 需要指定 content-type:application/json

這裡大家先記住,如果要返回json格式的字串,並且讓前端知道是一個json格式的字串,那就必須設定content-type:application/json。

5 重定向

重定向到 京東官網

# 重定向
@app.route('/demo5')
def demo5():
    return redirect('http://www.jd.com')

重定向到自己寫的檢視函式

可以直接填寫自己 url 路徑(這種寫法有缺陷,如果user的路徑修改了,那麼我們的重定向的url也需要修改)

也可以使用 url_for 生成指定檢視函式所對應的 url(推薦使用,需要導包)

語法:url_for(“函式名”)


@app.route('/demo1')
def demo1():
    return 'demo1'

# 重定向
@app.route('/demo5')
def demo5():
    return redirect(url_for('demo1'))

重定向到帶有引數的檢視函式

在 url_for 函式中傳入引數

# 路由傳遞引數
@app.route('/user/<int:user_id>')
def user_info(user_id):
    return 'hello %d' % user_id

# 重定向
@app.route('/demo5')
def demo5():
    # 使用 url_for 生成指定檢視函式所對應的 url
    return redirect(url_for('user_info', user_id=100))

6 自定義狀態碼

在 Flask 中,可以很方便的返回自定義狀態碼,以實現不符合 http 協議的狀態碼,例如:status code: 666

@app.route('/demo6')
def demo6():
    return '狀態碼為 666', 666

還可以為自定義的狀態碼進行說明

語法: “狀態碼 狀態碼名字”   (雙引號引起來   空格隔開)

7 正則匹配路由

7.1 預設轉換器的缺點:

如果我們現在想限制user/後邊只能有6個數字,int轉換器做不到

7.2 自定義轉換器

定義一個自定義的轉換器:RegexConverter

繼承BaseConverter基類,重寫regex屬性

訪問:

如果是4位數字,就找不到:

我們來看下原理:

看下int轉換器:IntegerConverter

注:基類中 regex 為類屬性,而重寫 regex 屬性實質上是添加了例項屬性 regex。

7.3 自定義轉換器升級

發現上面的正則轉換器,有點笨,只能處理一種正則表示式,所以需要進行升級。

7.3.1 實現步驟

在 web 開發中,可能會出現限制使用者訪問規則的場景,那麼這個時候就需要用到正則匹配,根據自己的規則去限定請求引數再進行訪問

具體實現步驟為:

  1. 匯入轉換器基類:在 Flask 中,所有的路由的匹配規則都是使用轉換器物件進行記錄
  2. 自定義轉換器:自定義類繼承轉換器基類
  3. 新增轉換器到預設的轉換器字典中
  4. 使用自定義轉換器實現自定義匹配規則

7.3.2 程式碼實現

匯入轉換器基類

from werkzeug.routing import BaseConverter

自定義轉換器:繼承轉換器基類

# 自定義正則轉換器
class RegexConverter(BaseConverter):
    def __init__(self, url_map, *args):
        super(RegexConverter, self).__init__(url_map)
        # 將接受的第1個引數當作匹配規則進行儲存
        self.regex = args[0]

新增轉換器到預設的轉換器字典中,並指定轉換器使用時名字為:re

app = Flask(__name__)

# 將自定義轉換器新增到轉換器字典中,並指定轉換器使用時名字為: re
app.url_map.converters['re'] = RegexConverter

使用轉換器去實現自定義匹配規則

當前此處定義的規則是:6位數字

# <re("\\d{6}"):user_id>  或者  <re(r"\d{6}"):user_id>
@app.route('/user/<re("[0-9]{6}"):user_id>')
def user_info(user_id):
    return "user_id 為 %s" % user_id

執行測試:http://127.0.0.1:5000/user/123 ,如果訪問的url不符合規則,會提示找不到頁面

8 補充-startflask

配置:live Templates 活動模板, 配置完之後,就可以快速編碼-程式碼塊。

注意第七步:

原本不是change:而是define.

點選Define,彈出如下框. 選擇Python( 這一步的意思就是,你這個程式碼塊要在哪裡使用.我們在python程式碼中使用)

 

輸入startflask敲回車:


 就會生成程式碼。

9 自定義轉換器其他兩個函式實現

9.1 轉換器 to_python

斷點除錯訪問:(如上圖,在36行打上斷點,然後訪問下邊的url)

觀察下圖可以發現,user_ids的值是一個字串:

但是我們想接收到的是一個列表怎麼辦?換句話說,如何才能在檢視函式中接收到的 user_ids 就是一個列表?

接下來就來看一下我們要分析的 to_python

先定義一個轉換器,並且重寫 to_python 如下

注:regex 還可以寫為:regex = r"(\d+,?)+\d$"

使用轉換器.

再次訪問之後,發現跟之前沒有區別。這裡的user_ids也沒有變成我們想要的列表.

怎麼辦呢? 做如下修改:

再次訪問:

斷點如下:

最終結果:

思考:這個int轉換器是如何將匹配到的字串轉換為int型別之後給user_id的?(提示:to_python)

 to_python 分析:

9.2 轉換器to_url

9.2.1 問題

我們先來看一個例子:

新建demo3函式。重定向到demo2

上圖紅框程式碼說明:

其實demo2,在訪問的時候url應該是:127.0.0.1:5000/users/1,2,3,4  然後list轉換器將1,2,3,4轉換為列表。

那麼上圖中通過url_for傳遞資料user_ids資料的時候,應該傳遞字串:1,2,3,4

但是我們程式中可能就是一個user id的一個列表。

所以我們這裡就傳遞列表過去。

來訪問:

發現報錯。肯定報錯了。這是因為url中怎麼可能傳遞一個列表的資料呢?

上述url是經過瀏覽器編碼了。

其實應該是如下:

其實修改一下我們傳遞的資料即可,如下:

但是我們的邏輯中可能user的id本身是一個列表。傳的時候,我們確實也可以將列表轉換為一個字串,傳遞到下邊。但是這件事情,能不能別人幫咱們處理呢?

9.2.2 to_url介紹

來看一下to_url:

注意,demo3的程式碼,還應該是列表:

我們先來研究下to_url

訪問/demo3,斷點除錯如下:

發現to_url中的value是我們url_for傳遞的引數。user_ids。

得出如下結論:

那就簡單了。我們利用to_url,對value做一個轉換即可:

再次訪問:

斷點除錯如下:

斷點走完,最終結果:

9.3 總結

繼承於自定義轉換器之後,還可以實現 to_python 和 to_url 這兩個函式去對匹配引數做進一步處理:

to_python:

  • 該函式引數中的 value 值代表匹配到的值,可輸出進行檢視
  • 匹配完成之後,對匹配到的引數作最後一步處理再返回,比如:轉成 int 型別的值再返回:
class RegexConverter(BaseConverter):
    def __init__(self, url_map, *args):
        super(RegexConverter, self).__init__(url_map)
        # 將接受的第1個引數當作匹配規則進行儲存
        self.regex = args[0]

    def to_python(self, value):
        return int(value)

執行測試,在檢視函式中可以檢視引數的型別,由之前預設的 str 已變成 int 型別的值

to_url:

  • 在使用 url_for 去獲取檢視函式所對應的 url 的時候,會呼叫此方法對 url_for 後面傳入的檢視函式引數做進一步處理
  • 具體可參見 Flask 的 app.py 中寫的示例程式碼:ListConverter

9.4 系統自帶轉換器

DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

系統自帶的轉換器具體使用方式在每種轉換器的註釋程式碼中有寫,請留意每種轉換器初始化的引數。

10 異常捕獲

10.1 HTTP 異常主動丟擲

abort 方法

  • 丟擲一個給定狀態程式碼的 HTTPException 或者 指定響應,例如想要用一個頁面未找到異常來終止請求,你可以呼叫 abort(404)。
  • 引數:code – HTTP的錯誤狀態碼
# abort(404)
abort(500)

丟擲狀態碼的話,只能丟擲 HTTP 協議的錯誤狀態碼

常見的狀態碼:HTTP Status Code

10.2 捕獲錯誤

errorhandler 裝飾器

  • 註冊一個錯誤處理程式,當程式丟擲指定錯誤狀態碼的時候,就會呼叫該裝飾器所裝飾的方法

引數:

  • code_or_exception – HTTP的錯誤狀態碼或指定異常

例如統一處理狀態碼為500的錯誤給使用者友好的提示:

@app.errorhandler(500)
def internal_server_error(error):
    return '伺服器搬家了'

捕獲指定異常

@app.errorhandler(ZeroDivisionError)
def zero_division_error(e):
    return '除數不能為0'

11 請求鉤子

在客戶端和伺服器互動的過程中,有些準備工作或掃尾工作需要處理,比如:

  • 在請求開始時,建立資料庫連線;
  • 在請求開始時,根據需求進行許可權校驗;
  • 在請求結束時,指定資料的互動格式;

為了讓每個檢視函式避免編寫重複功能的程式碼,Flask提供了通用設施的功能,即請求鉤子。

請求鉤子是通過裝飾器的形式實現,Flask支援如下四種請求鉤子:

  • before_first_request
    • 在處理第一個請求前執行
  • before_request
    • 在每次請求前執行
    • 如果在某修飾的函式中返回了一個響應,檢視函式將不再被呼叫
  • after_request
    • 如果沒有丟擲錯誤,在每次請求後執行
    • 接受一個引數:檢視函式作出的響應
    • 在此函式中可以對響應值在返回之前做最後一步修改處理
    • 需要將引數中的響應在此引數中進行返回
  • teardown_request:
    • 在每次請求後執行
    • 接受一個引數:錯誤資訊,如果有相關錯誤丟擲

總結:請求鉤子類似於裝飾器,可以在不修改函式內部的邏輯前提下增加邏輯.

程式碼測試

from flask import Flask
from flask import abort

app = Flask(__name__)

# 在第一次請求之前呼叫,可以在此方法內部做一些初始化操作
@app.before_first_request
def before_first_request():
    print("before_first_request")


# 在每一次請求之前呼叫,這時候已經有請求了,可能在這個方法裡面做請求的校驗
# 如果請求的校驗不成功,可以直接在此方法中進行響應,直接return之後那麼就不會執行檢視函式
@app.before_request
def before_request():
    print("before_request")
    # if 請求不符合條件:
    #     return "請求失敗!"


# 在執行完檢視函式之後會呼叫,並且會把檢視函式所生成的響應傳入,可以在此方法中對響應做最後一步統一的處理
@app.after_request
def after_request(response):
    print("after_request")
    response.headers["Content-Type"] = "application/json"
    return response


# 請每一次請求之後都會呼叫,會接受一個引數,引數是伺服器出現的錯誤資訊
# 沒有報異常也會在index之後執行此函式,只不過沒有將異常資訊傳遞給error而已.
@app.teardown_request
def teardown_request(error):
    print("teardown_request")


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

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

 在第1次請求時的列印:

before_first_request
before_request
after_request
teardown_request

在第2次請求時的列印:

before_request
after_request
teardown_request