Flask Web Development —— 基本應用程序結構(下)

作者| ipython

5、請求-響應循環

現在你已經玩過一個基本的Flask應用程序,你也許想要知道更多關于Flask如何施展魔力。下面章節描述了一些框架設計方面的特點。

5.1、應用程序Context和請求Context

當Flask從客戶端收到一個請求,它需要提供幾個可用對象給視圖函數處理。request對象是個不錯的例子,它封裝了客戶端發送的HTTP請求。

Flask視圖函數訪問request對象的最好方式,就是作為一個參數發送它,但這需要每個單一視圖函數在應用程序中有一個額外的參數。考慮一下,如果request對象不是唯一一個視圖函數需要訪問完成請求的對象,事情將會變得更加復雜。

為了避免弄亂視圖函數那些可能需要或不需要的參數,Flask使用context來臨時確定可訪問的全局對象。也多虧了context,視圖函數可以寫成下面這樣:

from flask import request

@app.route('/')
def index():
    user_agent = request.headers.get('User-Agent')
    return '

<p>Your browser is %s</p>

' % user_agent

注意,在這個視圖函數中,request是如何被作為一個全局變量來使用的。現實中,request是不能作為全局變量的,如果是多線程服務器,同一時間線程作用于不同客戶端的不同請求,所以每一個線程需要看到request中的不同對象。contexts使得Flask確定可訪問的全局變量而不干擾其他線程。

注:線程是可以獨立管理的最小指令序列。一個進程中有多個活動的線程是非常常見的,有時分享內存或文件句柄資源。多線程web服務器會啟動一個線程池并從池中選擇一個線程來處理每個傳入的請求。

Flask有兩類context:應用級context 和 請求級context。表2-1展示了這些context提供的變量。

表2-1. Flask全局context

Flask激活(或壓棧)應用級context和請求級context在調度請求之前,然后刪除他們當請求被處理后。當應用程序context被壓入棧,線程中current_app和g變量變得可用;同樣的,當請求級context被壓入棧,request和session變量也同樣變得可用。如果這些變量中的任何一個不是由激活的應用級或請求級context訪問,會產生錯誤。在后面的章節會詳細討論四個context變量,所以不要擔心你不理解它們的用處。

下面的Python shell會話演示了應用級context是如何工作的:

>>> from hello import app
>>> from flask import current_app
>>> current_app.name
Traceback (most recent call last):
...
RuntimeError: working outside of the application context
>>> app_ctx = app.app_context()
>>> app_ctx.push()
>>> current_app.name
'hello'
>>> app_ctx.pop()

在這個示例中,當應用級context沒有激活,但是卻作為有效的context被壓入棧中,current_app.name報錯。注意在應用程序實例中一個應用級context是如何通過調用app.app_context()來獲得的。

5.2、請求調度

當一個應用程序收到客戶端的請求,它需要找到響應的視圖函數為之服務。對于這個任務,Flask會在應用程序的URL映射中查找請求的URL,該映射包含URLs和操作它們的視圖函數。Flask通過app.route裝飾器或非裝飾器版本app.add_url_rule()來建立這個映射。

看一下Flask應用程序中URL映射是怎樣的,你可以在Python shell中檢查hello.py創建的映射。測試中,請確保你的虛擬環境是激活狀態:

(venv) % python
>>> from hello import app
>>> app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
  <Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
  <Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])

/和/user/<name>路由是由應用程序中的app.route所定義。/static/<filename>路由是由Flask添加,用于訪問靜態文件的一個特殊路由。你將在第三章學習更多關于靜態文件的內容。

URL映射中所示的HEAD、OPTIONS、GET元素為request方法,由路由處理。Flask連接方法到每個路由,這樣不同的請求方法發送到相同的URL可以被不同的視圖函數處理。HEAD和OPTIONS方法由Flask自動管理,所以實際上可以說,在這個應用程序中URL映射的三個路由都連接到GET方法了。在第四章你將學習為路由指定不同的請求方法。

5.3、請求Hooks

有些時候在每個請求處理之前或之后執行代碼是非常有用的。例如,在開始每一個請求前可能有必要創建數據庫連接,或對用戶請求進行驗證。為了避免復制處理這些操作的代碼到每一個視圖函數中,Flask給你選擇注冊相同函數來調用,在請求被分配給視圖函數之前或之后。

請求hooks由裝飾器實現。下面是四個Flask支持的hooks:

  • before_first_request:在第一個請求被處理前注冊一個函數運行。
  • before_request:在每一個請求前注冊一個函數運行。
  • after_request:如果沒有未處理的異常發生,在每一個請求后注冊一個函數運行。
  • teardown_request:即使未處理的異常發生,在每一個請求后注冊一個函數運行。

在請求hook函數和視圖函數之間共享數據的慣用方法就是使用g全局context。例如,before_request處理程序可以從數據庫加載已登錄的用戶并保存在g.user中。之后,當視圖函數被調用,可以從那訪問用戶。

請求hooks的示例會在未來的章節中展示給大家,所以不用擔心,

5.4、響應

當Flask調用一個視圖函數,并期望它的返回值去響應該請求。大多數的響應是將簡單字符串構成的HTML頁面發回給客戶端。

但是HTTP協議需要比字符串更多的信息作為請求的響應。一個HTTP響應中非常重要的部分是狀態碼,Flask默認設置200來指示請求已經成功處理。

當視圖函數需要用不同的狀態碼響應,可以在響應文本后添加數字碼作為第二個返回值。例如,下面的視圖函數返回400錯誤狀態碼的請求:

@app.route('/')
def index():
    return '<h1>Bad Request</h1>', 400

視圖函數返回的響應還可以攜帶第三個參數,添加一個頭部字典給HTTP響應。通常很少用到,但是你可以在第十四章看到示例。

除了返回一個、兩個或三個值的元組,Flask視圖函數可以選擇返回response對象。make_response()函數可攜帶一個、兩個或三個參數,和視圖函數返回的值一樣,并返回一個response對象。有時候在視圖函數中執行這個轉換是非常有用的,然后使用response對象中的方法進一步配置響應。下面的示例創建response對象并設置cookie:

from flask import make_response

@app.route('/')
def index():
    response = make_response('

<h1>This document carries a cookie!</h1>

')
    response.set_cookie('answer', '42')
    return response

有一類特殊的響應稱作重定向。這類響應不包含頁面文檔;只是給瀏覽器一個新的URL去加載新的頁面。重定向通常和web表單一起使用,你將在第四章學習。

重定向通常由302響應狀態碼注明并且重定向的URL由頭部的Location給出。重定向響應可以使用三個值的返回生成,也可通過響應對象生成,但是鑒于它頻繁的使用,Flask提供redirect()函數來創建這樣的響應:

from flask import redirect

@app.route('/')
def index():
    return redirect('http://www.example.com')

另一個具有中斷功能的特殊響應用來錯誤處理。下面的示例,當URL給出的id動態參數不是一個合法的用戶時返回狀態碼404:

from flask import abort

@app.route('/user/<id>')
def get_user(id):
    user = load_user(id)
    if not user:
        abort(404)
    return '

<h1>Hello, %s</h1>

' % user.name

注意終止不是指將控制權返回給調用它的函數,而是指通過拋出異常將控制權返回給web服務。

6、Flask擴展

Flask是可擴展的。它故意騰出地給重要的功能,例如數據庫和用戶授權,給你自由去選擇最適合你的應用程序的包,或寫一個自己想要的。

社區開發了非常多的擴展用于各種用途,如果這還不夠,可以使用任何Python標準包和庫。為了讓你了解一個擴展是如何并入一個應用程序的,下面的章節給hello.py添加一個擴展,增加應用程序的命令行參數。

6.1、Flask-Script命令行選項

Flask開發,其web服務器支持一系列的啟動配置選項,但是配置它們的唯一方式只有在腳本中傳遞參數給app.run()并調用。這不是非常的方便,理想方法是通過命令行參數傳遞配置選項。

Flask-Script是給你的Flask應用程序添加命令行解釋的擴展。它打包了一組通用的選項,還支持自定義命令。

使用pip安裝擴展:

(venv) $ pip install flask-script

示例2-3展示了在 hello.py 應用程序中添加命令行解釋的變化。

示例2-3. hello.py:使用Flask-Script

from flask.ext.script import Manager

manager = Manager(app)

# ...

if __name__ == '__main__':
    manager.run()

專為Flask開發的擴展暴露在flask.ext命名空間下。Flask-Script從flask.ext.script中導出一個名為Manager的類。

初始化這個擴展的方法和其他許多擴展一樣:主類實例的初始化是通過將應用程序實例作為參數傳遞給構造函數實現的。創建的對象適當的用于每一個擴展。在這個示例中,服務器啟動通過manager.run()來路由,且命令行在這被解析。

建議:如果你有克隆在GitHub上的應用程序,你現在可以運行git checkout 2c來切換到這個版本的應用程序。

因為這些變化,應用程序獲得一組基本的命令行選項。運行hello.py顯示可用信息:

$ python hello.py
usage: hello.py [-h] {shell, runserver} ...

positional arguments:
  {shell, runserver}
    shell           在Flask應用程序上下文的內部運行一個Python Shell。
    runserver       運行Flask開發服務器,例如:app.run()

optional arguments:
  -h, --help        顯示這個幫助信息并退出

shell命令用于在應用程序上下文中啟動一個Python shell會話。你可以使用這個會話去運行維護任務,或測試,或調試錯誤。

runserver命令,就像它的名稱一樣,啟動web服務。運行python hello.py runserver在調試模式下啟動web服務,還有更多的選項:

(venv) $ python hello.py runserver --help
usage: hello.py runserver [-h] [-t HOST] [-p PORT] [--threaded]
                          [--processes PROCESSES] [--passthrough-errors] [-d]
                          [-r]

運行Flask開發服務器,例如:app.run()

optional arguments:
  -h, --help             顯示這個幫助信息并退出
  -t HOST, --host HOST
  -p PORT, --port PORT
  --threaded
  --processes PROCESSES
  --passthrough-errors
  -d, --no-debug
  -r, --no-reload 

--host參數是一個非常有用的選項,因為它能告訴web服務器監聽哪個網絡接口的客戶端連接。默認,Flask開發的web服務器監聽localhost的連接,所以只有來自內部計算機運行的服務器可以接收。下面的命令使得web服務器監聽公網接口,其他網絡上的計算機可以連接:

(venv) $ python hello.py runserver --host 0.0.0.0
 * Running on http://0.0.0.0:5000/
 * Restarting with reload

現在web服務器應該可以從網絡中的任何一臺計算機訪問[http://a.b.c.d:5000,“a.b.c.d”是運行服務的計算機的外部IP地址。

這一章介紹了請求響應的概念,但說的更多的是響應。Flask使用模板為生成響應提供非常好的支持,這是非常重要的話題,下一章會重點講它。

原文轉自:https://segmentfault.com/a/1190000000737219

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容