使用 Flask 開發 Web 應用(一)

微框架之「微」

Flask 強調本身是一個用於 Web 開發的微框架。咱們知道,開發 Web 應用主要的工做,就是對一個 Web 請求,接收其請求數據(輸入),根據業務邏輯進行處理,而後返回相應的響應結果(輸出)。Flask 微框架的「微」字,體如今它專一於上面這個流程的兩端,即處理輸入數據和生成輸出數據。至於中間如何進行處理,那是開發人員的事情,Flask 並未提供便利。python

基本的開發思路

主觀感覺上,咱們更多的是將 Flask 當作一個庫而不是框架在使用的。這賦予了咱們更大的靈活性,獲得輸入數據後,如何驗證,如何處理,使用什麼技術處理,咱們具備極大的自由。只要最終咱們可以用上 Flask 提供的輸出工具把結果拋回去就能夠了。程序員

Flask 沒有強加給開發人員諸如 MVC 或 MTV 這樣的設計模式,不少時候,響應一個請求的代碼從較高的層面來看,僅僅是編寫一個 Flask 稱爲視圖的函數:編程

@app.route('/path/to/here')  # 0. 用修飾器的形式描述路由規則
def perform(**kwargs):
    """請求處理函數"""
    # 1. 圍繞 request 對象獲取輸入數據
    params = fetch_params(request, kwargs)
    # 2. 根據業務須要進行必要的處理
    result = deal_with(params)
    # 3. 使用 Flask 提供的機制,一般是 render_template() 或 jsonify() 生成響應結果
    return create_response(result)

Flask 沒有所謂模型層的概念,模板層也不是必需的(雖然從包的依賴上看對 Jinja 的依賴是必需的),可是徹底可使用其它模板技術甚至徹底不使用模板。開發中須要關注的,就是獲取輸入數據,進行必要的處理,而後輸出對應的信息。json

筆者認爲,偏偏是如此簡單直觀的設計哲學,才讓 Flask 深受歡迎。這篇文章將從這幾個簡單的步驟展開,記述筆者本身對如何使用 Flask 的理解。flask

輸入數據的獲取

從技術的層面,一個請求的輸入數據有幾個層面的含義:設計模式

  • 它是遵循 HTTP 規範(一般是 HTTP 1.1)及一些擴展(例如咱們可能自行約定特殊的 header 字段)的請求報文。這是背景知識,咱們須要知道 HTTP 請求大概是怎樣的形態。api

  • 在 Flask 應用程序中一般表現爲「全局」對象 request,它是 flask.Request 類的實例。這是對 HTTP 請求報文的抽象和封裝,它提供了設計良好的屬性和方法,來表現一個 HTTP 請求。瀏覽器

注意這個全局是打了引號的。後面咱們會進行討論,在這裏能夠粗略的理解在處理單個請求的過程當中這個對象就像是全局變量同樣。安全

一般來講,咱們理解的輸入數據應該更加扁平直觀一些,它們應該是原始數據類型的某種較簡單(列表、字典)的組合,這些數據一般是從 request 對象中提取的。通常來講,存在幾個來源:服務器

  • 請求的路徑可能自己被當作輸入參數一部分,Flask 提供了一些便利,用於從請求路徑中提取變量。詳情請參考 Flask 快速上手指南Werkzeug URL 路由模塊文檔

  • 請求的 QUERY_STRING 部分,能夠經過 request.args 得到。

  • 請求以表單形式提交的數據(即以 application/x-www-form-urlencoded MIME Type 提交的報文),能夠經過 request.form 得到。

  • request.argsrequest.formMultiDict 的實例,一般能夠當作普通的字典使用,不過能夠用於處理同名屬性出現屢次的問題。

  • request 還提供了其它屬性提供開發人員可能感興趣的輸入信息,包括 request.datarequest.headersrequest.filesrequest.cookies 等。

Flask 並未提供對數據的驗證。這是程序員須要也必需自行處理的,由於來自網絡的數據是不安全的。一般有以下作法:

  • 從路由規則提取的來自請求路徑的信息,通常來講比較簡單,手寫簡單的驗證便可。

  • 對於 request.argsrequest.form,簡單但強大的驗證數據的方法是經過 Flask-WTF 擴展,得益於 WTForms 庫提供的驗證機制,能夠靈活的進行數據驗證。

  • 其它輸入來源獲得的數據,例如 request.data 獲得的 JSON 文本或 XML 文本,或者手工處理,或者藉助注入 JSNON Schema 或 XML Schema 等技術進行驗證。

結束響應和生成輸出數據

當處理輸入的過程成功得以執行,或者在處理數據的過程當中產生了沒法繼續處理的錯誤,咱們須要結束響應過程,並根據狀況生成輸出數據。

雖然大多數狀況下,咱們響應請求輸出數據的時候,只須要生成報文便可,可是咱們須要知道,Web 響應包含三個部分:狀態碼報頭報文。Flask 提供了必要的手段讓咱們能夠指定狀態碼並在報頭注入必要的信息。

從 HTTP 的角度看,響應有三種類型:

  • 具備 HTTP 2xx 狀態碼,從 HTTP 的角度這是成功的響應。

  • 具備 HTTP 3xx 狀態碼,從 HTTP 的角度這是特殊的用於跳轉到新的 URL 地址,須要瀏覽器或者客戶端從新發起請求的響應。

  • 具備 HTTP 4xx 或 5xx 狀態碼,從 HTTP 的角度這返回了錯誤信息,4xx 系列通常來講是請求存在問題,5xx 系列通常來講是服務器存在問題。

從輸出的內容形態來看,一般有:

  • 適合人們閱讀的內容,一般是 HTML,經過瀏覽器進行渲染。通常來講,會須要經過 render_template() 函數結合模板文件的處理結果進行渲染,當內容較爲簡單時,直接以字符串形式生成 HTML 也是可行的。

  • 適合機器閱讀的內容,經常使用於接口服務的開發,例如經過調用 jsonify() 函數返回 JSON 格式的數據。

  • 無內容。特定的狀態碼如 204 No Content 不得包含響應內容。這一般也是用於接口服務的開發。

在結束響應方面,存在兩種方式:

  • 天然的方式,即請求處理函數以 return 語句返回必要的信息,能夠是一個 flask.Response 類的實例,也能夠是用於構建這樣實例的參數。

  • 中途跳出的方式,即以拋出異常(通常來講是對應了不一樣狀態碼的特定的異常)的方式(包括調用 Flask 提供的 abort() 函數),來提前結束處理。

能夠看到,Flask 在結束響應和生成輸出方面考慮了很多狀況,提供了許多的便利。然而,仍然存在一處缺失:就是當咱們以特定的異常或調用 abort() 函數跳出處理時,它假定錯誤信息是以 HTML 的方式提供的,當咱們遵循特定的約定開發接口服務器時,須要繼承特定的異常重寫必要的報頭和報文。

微框架之「框架」

雖然許多時候以及 Flask 的許多部分咱們都是當作庫來使用的,然而 Flask 畢竟是一個框架。簡單來講,這意味着代碼的組織必然受到框架內在特色的影響。

雖然從 0.7 版開始,Flask 就支持了基於類形態的視圖機制,可是筆者更傾向於以函數的方式編寫視圖,也就是採用更傳統的過程式形態。這讓代碼保持簡單,易於閱讀和維護。

使用修飾器改變響應處理行爲

使用類的形態帶來的好處是能夠開發人員編寫一個或者多個視圖類,它們具備必定的行爲,再供給它們的子類使用。這些行爲主要的做用是在執行真正的響應代碼以前和/或以後進行必要的處理。若是缺乏必要的手段,採用過程形態編寫程序,這樣的通用代碼若是分散在各個視圖函數中,就顯得冗餘了。

幸運的是,得益於 Python 的修飾器機制,咱們一樣能夠簡單的在視圖函數得以執行以前並在執行以後,進行必要的預處理和/或後期處理,而無需編寫冗餘的代碼。

舉個例子,對於要求用戶已經登陸方可執行的響應處理,若是以類形態編程,可能會表現爲一個父類,在真正分發請求之間檢查用戶是否已經登陸,子類中就無需再考慮相應的問題,用戶未登陸時請求不會被分發到相應的方法上。

以過程式形態來編寫,將這樣的判斷寫到每個函數裏,顯然是冗餘的。所以,諸如擴展 Flask-Login 是經過提供名爲 login_required 的修飾器來進行的,代碼看起來相似:

@app.route('/path/to/here')
@login_required
def perform(**kwarg):
    params = fetch_params(request, kwargs)
    result = deal_with(params)
    return create_response(result)

這樣,要求用戶已經登陸的行爲從代碼上看簡化爲一行修飾器的使用,因爲它與函數定義的位置至關近,實際上代碼的可讀性是更高的,閱讀這段代碼時,腦海中更容易強化要求用戶已經登陸的約定。

對於今天較先進的程序設計語言提供的修飾器(Decorator)或者標註(Annotation)這樣的技術,筆者的理解是這是一種容許開發人員定製的超級語法糖,它可以在編譯時(對編譯型語言)或模塊導入時(對於解釋型語言)或多或少的修改源代碼。好的實現,可讓源代碼具備很高的易讀性和可維護性的同時,具有更強大的功能。

在 Python 中,修飾器是一個便利的攔截和修改調用參數和返回值的機制。所以要深刻駕馭 Flask,瞭解如何編寫修飾器是有必要的。

使用 Blueprint 切分視圖模塊

以類形態組織代碼,自然具備良好的模塊隔離。以過程式形態,咱們也能夠將邏輯上相關的響應處理函數整理歸併到不一樣的 Python 模塊中。更進一步,咱們還可使用 Flask 提供的 Blueprint 概念,Blueprint 在 API 與全局的 Flask 應用程序相似,使用 Blueprint 在實踐中還具備一些額外的好處,例如能夠較爲方便的調整一個 Blueprint 中全部響應處理函數的路由規則,以及避免對全局的 Flask 對象循環依賴等。

保持面向對象的設計思想

即使是過程式形態組織代碼,咱們總體的思路依然是面向對象的。每一個模塊中的響應處理函數能夠認爲是當前運行中的 Flask 應用程序實例的方法。咱們能夠經過 flask.current_app 對象得到應用程序上下文中的這個實例。此外,能夠經過 flask.g 來存儲實例屬性。經過這樣的思考,咱們仍然能夠保持面向對象的程序設計思想。

使用回調和信號機制

Flask 具備請求上下文的概念,提供了必要的回調機制,回調有三類:

  • before_request 回調,當收到請求時被調用,用於請求數據的預處理,必要時也能夠返回響應對象以抑制響應處理函數的執行。

  • after_request 回調,當請求處理正常結束時被調用(若是處理過程拋出了未被處理的異常則不會被調用),用於響應結果的後處理。它們接受一個響應對象做爲參數,並應該返回一個響應對象。

  • teardown_request 回調,在每一個請求處理結束時被調用,一般用於清理或者回收資源。

能夠看到 before_request 回調和 after_request 回調也能夠用於請求處理函數的預處理和後處理。teardown_request 回調的使用場景較爲特殊,以後咱們會更進一步說明。

每一類回調都可以設置多個,調用時,before_request 回調按照註冊順序進行,after_request 回調和 teardown_request 回調按照註冊的逆序執行。

除了回調以外,Flask 也提供了更加靈活的信號機制,除了可以處理的時機比回調更多,也容許開發人員自定義信號。

WSGI 應用程序

Flask 是遵循 WSGI 規範的應用程序實現。WSGI 是許多 Python Web 框架——包括著名的 Django 框架——都共同遵循的規範。這意味着,基於 Flask 編寫的應用程序,能夠運行在不一樣的 WSGI 服務器實現上,而且能夠結合各類 WSGI 中間件使用。

「全局」變量

許多 WSGI 服務器的實現,例如 uWSGIGunicorn 均支持多種不一樣的併發實現:多進程、多線程以及協程等。特別的,這些進程、線程或者協程一般會在多個請求中重複使用,所以,全局變量的設置須要尤其注意,它們在不一樣的請求中應該是被隔離的。

Flask 提供的「全局」變量,如 requestsession 以及供開發人員使用的 g,在同一個請求的處理過程當中是「全局」的,而 Flask(藉助底層的 Werkzeug)當心的處理這些「全局」信息,不會侵入其它的請求中,開發人員無需在乎使用的是何種併發技術以及併發部件是否被重複使用。

簡單來講,當開發人員須要設置在一個請求過程當中共享的全局變量時,應該將這些變量做爲 g 的屬性,Flask 知道應該如何處理它們。

此前將全局這個詞打上引號,是由於更確切的說,這些變量是 Werkzeug 所謂的上下文局部變量,Werkzeug 文檔中有專門的論述,值得閱讀。

teardown_request 回調

以前咱們提到,Flask 只專一與獲取請求和生成響應,這兩個環節中間的業務邏輯,開發人員有徹底的自主。當咱們進行業務邏輯的開發時,可能會在請求開始申請一些資源,當請求結束時,不管成功或者失敗,咱們都須要釋放這些資源,這時候,咱們就須要使用 teardown_request 回調了。

此外,若是不是針對請求上下文,而是針對應用程序上下文結束時進行的清理操做,可使用 teardown_appcontext 回調。

相關文章
相關標籤/搜索