初始化
所有 Flask 程序都必須創(chuàng)建一個(gè) app 實(shí)例。Web 服務(wù)器使用 Web 服務(wù)器網(wǎng)關(guān)接口協(xié)議(Web Server Gateway Interface, WSGI)把接收自客戶端的所有請求都轉(zhuǎn)交給這個(gè)對象處理。 app 實(shí)例是 Flask 類的對象:
from flask import Flask
app = Flask(__name__)
Flask 類的構(gòu)造函數(shù)自有一個(gè)必須指定的參數(shù), 即 app 主模塊或包的名字。
Flask 用 name 這個(gè)參數(shù)決定程序的根目錄, 以便稍后能夠找到相對于程序根目錄的資源文件位置。
路由和視圖函數(shù)
客戶端(例如 Web 瀏覽器)把請求發(fā)送給 Web 服務(wù)器, Web 服務(wù)器再把請求發(fā)送給 Flask app 實(shí)例。 app 實(shí)例需要知道對每個(gè) URL 請求運(yùn)行哪些代碼, 所以 app 實(shí)例保存了一個(gè) URL 到 Python 函數(shù)的映射關(guān)系。處理 URL 和函數(shù)之間關(guān)系的程序稱為路由。
在 Flask 中使用 app 實(shí)例提供的 app.route 裝飾器把所裝飾的函數(shù)注冊為路由:
@app.route('/')
def index():
return '<h1>Hello, 世界!</h1>'
裝飾器是可以把函數(shù)注冊為事件的處理程序。
前例是把 index() 函數(shù)注冊為 app 根地址的處理程序。如果部署的程序的服務(wù)器域名為 www.example.com, 在瀏覽器中訪問 http://www.example.com 后會(huì)觸發(fā)服務(wù)器執(zhí)行 index() 函數(shù)。這個(gè)函數(shù)的返回值稱為 響應(yīng), 它是客戶端接收到的內(nèi)容。如果客戶端是 Web 瀏覽器, 響應(yīng)就是顯示給用戶看的文檔。
像 index() 這樣的函數(shù)稱之為 視圖函數(shù)(view function)。視圖函數(shù)返回的響應(yīng)可以是包含 HTML 的簡單字符串,也可以是復(fù)雜的表單。
可變 URL:
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
路由中的動(dòng)態(tài)部分默認(rèn)使用字符串, 也可以使用 int/float/path 類型, path 類型也是字符串, 但不把斜線作為分割符, 而將其當(dāng)作動(dòng)態(tài)片段的一部分。
@app.route('/user/<int:id>')
啟動(dòng)服務(wù)器
app 實(shí)例使用 run 方法啟動(dòng) Flask 集成的 Web 服務(wù)器:
if __name__ == '__main__':
app.run(debug=True)
__name__ == '__main__' 確保了只有直接 執(zhí)行這個(gè)腳本時(shí)才啟動(dòng) Web 服務(wù)器。如果這個(gè)腳本由其它腳本引入, 程序假定父級(jí)腳本會(huì)啟動(dòng)不同的服務(wù)器, 因此不會(huì)執(zhí)行 app.run()。
服務(wù)器啟動(dòng)后會(huì)進(jìn)入輪詢, 等待并處理請求, 輪詢會(huì)一直運(yùn)行,直到程序停止,例如按 Ctrl-C 鍵。
一個(gè)完整的 app
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello, 世界!</h1>'
@app.route('user/<name>')
def user(name):
return '<h1>hello, %s!</h1>' % name
if __name__ == '__main__':
app.run(debug=True)
啟動(dòng)這個(gè) app:
(venv) $ python hello.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader
在瀏覽器中鍵入:
http://localhost:5000/user/Dave
會(huì)顯示:
<h1>Hello, Dave!</h1>
請求/響應(yīng)循環(huán)
app 和請求上下文
Flask 從客戶端收到請求時(shí), 要讓視圖函數(shù)能訪問一些對象, 這樣才能處理請求。請求對象封裝了客戶端(例如瀏覽器)發(fā)送的 HTTP 請求。
要讓視圖函數(shù)能訪問請求對象, 一個(gè)顯而易見的方式是把請求對象作為參數(shù)傳遞給視圖函數(shù), 不過這會(huì)導(dǎo)致程序中每個(gè)視圖函數(shù)都增加一個(gè)參數(shù)。如果視圖函數(shù)還要訪問其它對象, 那么視圖函數(shù)會(huì)變得越來越臃腫和難以維護(hù)。
為此, Flask 使用 上下文 臨時(shí)把某些對象變?yōu)?em>全局可訪問:
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<p>你的瀏覽器是 %s</p>' % user_agent
在這個(gè)例子中我們把 request 當(dāng)作全局變量來使用。事實(shí)上, request 不可能是全局變量, 你想想, 在多個(gè)線程同時(shí)處理不同客戶端發(fā)送的不同請求時(shí), 每個(gè)線程看到的 request 對象必然不同。 Flask 使用上下文讓特定的變量在每一個(gè)線程中全局可訪問, 與此同時(shí)卻不會(huì)干擾其它線程。
多線程 Web 服務(wù)器會(huì)創(chuàng)建一個(gè)線程池, 再從線程池中選擇一個(gè)線程用于處理接收到的請求。
在 Flask 中有兩種上下文: app 上下文 和 請求上下文。下表列出了這兩種上下文提供的全局變量:
| 變量名 | 上下文 | 說明 |
|---|---|---|
| current_app | app上下文 | 當(dāng)前所激活app的app實(shí)例 |
| g | app上下文 | 處理請求時(shí)用作臨時(shí)存儲(chǔ)的對象。每次請求都會(huì)重設(shè)這個(gè)變量 |
| request | 請求上下文 | 請求對象, 封裝了客戶端發(fā)出的 HTTP 請求中的內(nèi)容 |
| session | 請求上下文 | 用戶會(huì)話, 用于存儲(chǔ)請求之間需要"記住"的值的字典 |
Flask 在分發(fā)請求之前激活(或推送)app上下文和請求上下文, 請求處理完成后再將其刪除。 app 上下文在被推送后, 就可以在線程中使用 current_app 和 g 變量。類似地, 在請求上下文被推送后, 就可以使用 request 和 session 變量。如果我們使用這些變量時(shí)沒有激活 app 上下文或請求上下文, 那么程序就會(huì)出錯(cuò)。
激活虛擬環(huán)境后進(jìn)入 Python shell, 下面演示app上下文的使用方法:
>>> from hello import app
>>> from flask import current_app
>>> current_app.name
...
RuntimeError: Working outside of application context.
>>> app_ctx = app.app_context()
>>> app_ctx.push() # 推送 app 上下文
>>> current_app.name
'hello'
>>> app_ctx.pop() # 彈出 app 上下文
在這個(gè)例子中, 沒有激活 app 上下文之前就調(diào)用 current_app.name 就會(huì)導(dǎo)致錯(cuò)誤, 但是推送完上下文之后就可以調(diào)用了。
注意, 在 app 實(shí)例上調(diào)用 .app_context() 方法便獲得了一個(gè)程序上下文。
請求調(diào)度
程序收到客戶端發(fā)來的請求時(shí), 要找到處理該請求的視圖函數(shù)。Flask 通過在 app 的 URL 映射中查找請求的 URL 來完成這個(gè)任務(wù)。 URL 映射是 URL 和視圖函數(shù)之間的對應(yīng)關(guān)系。 Flask 使用 app.route 裝飾器/非裝飾器形式的 app.add_url_rule() 生成映射。
我們在 Python shell 中查看 Flask 的 app 中映射是什么樣子的:(所有操作請確保你已經(jīng)激活了虛擬環(huán)境)
(venv) $ python
>>> from hello import app
>>> app.url_map
>>> app.url_map
Map([<Rule '/' (HEAD, OPTIONS, GET) -> index>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>,
<Rule '/user/<name>' (HEAD, OPTIONS, GET) -> user>])
/ 和 /usr/<name> 路由在 app 中使用 app.route 裝飾器定義。 /static/<filename> 路由是 Flask 添加的特殊路由, 用于訪問靜態(tài)文件。
URL 映射中的 HEAD、OPTIONS、GET 是請求方法。Flask 為每個(gè)路由都指定了請求方法, 這樣不同的請求發(fā)送到相同的 URL 上時(shí), 會(huì)使用不同的視圖函數(shù)進(jìn)行處理。 HEAD 和 OPTIONS 方法由 Flask 自動(dòng)處理, 因此可以說上面的 app 中 URL 映射中的 3 個(gè)路由都使用 GET 方法。
請求鉤子
有時(shí)需要在請求之前或之后執(zhí)行代碼會(huì)很有用。例如, 在請求開始時(shí)我們可能需要?jiǎng)?chuàng)建數(shù)據(jù)庫連接/認(rèn)證發(fā)起請求的用戶。為了避免在每個(gè)視圖函數(shù)中都使用重復(fù)的代碼, Flask 提供了注冊通用函數(shù)的功能, 注冊的函數(shù)可在請求被分發(fā)到視圖函數(shù)之前/之后被調(diào)用。
請求鉤子 使用裝飾器實(shí)現(xiàn)。 Flask 支持以下 4 種鉤子:
- before_first_request: 注冊一個(gè)函數(shù), 在處理第一個(gè)請求之前運(yùn)行。
- before_request: 注冊一個(gè)函數(shù), 在每次請求之前運(yùn)行。
- after_request: 注冊一個(gè)函數(shù), 如果沒有未處理的異常拋出, 則在每次請求之后運(yùn)行。
- teardown_request: 注冊一個(gè)函數(shù), 即使有未處理的異常拋出, 也在每次請求之后運(yùn)行。
在請求鉤子和視圖函數(shù)之間共享數(shù)據(jù)一般使用上下文全局變量 g。 例如 before_request 處理程序可以從數(shù)據(jù)庫中加載已登錄用戶, 并將其保存到 g.user 中。隨后調(diào)用視圖函數(shù)時(shí), 視圖函數(shù)再使用 g.user 獲取用戶。
響應(yīng)
Flask 調(diào)用視圖函數(shù)后, 會(huì)將其返回值作為響應(yīng)的內(nèi)容。大多數(shù)情況下, 響應(yīng)就是一個(gè)簡單的字符串, 作為 HTML 頁面回送客戶端。
在響應(yīng)的文本之后添加一個(gè)狀態(tài)碼:
@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400
表示請求無效。
視圖函數(shù)返回的響應(yīng)還可以接收第三個(gè)參數(shù), 這是一個(gè)由首部(header)組成的字典, 可以添加到 HTTP 響應(yīng)中。
Flask 還可以返回 Response 對象。 make_response() 函數(shù)可接受 1/2/3 個(gè)參數(shù)(和視圖函數(shù)的返回值一樣), 并返回一個(gè) Response 對象。
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
重定向是一種特殊的響應(yīng)類型。這種響應(yīng)類型沒有頁面文檔, 只告訴瀏覽器一個(gè)新的地址用以加載新頁面。Flask 已經(jīng)提供了 redirect() 函數(shù):
from flask import redirect
@app.route('/')
def index():
return redirect('http://www.example.com')
還有一種特殊的 abort 響應(yīng), 用于處理錯(cuò)誤:
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
Flask 擴(kuò)展
使用Flask-Script支持命令行選項(xiàng)
Flask 的開發(fā) Web 服務(wù)器支持很多啟動(dòng)設(shè)置選項(xiàng),但只能在腳本中作為參數(shù)傳給 app.run()函數(shù)。這種方式并不十分方便,傳遞設(shè)置選項(xiàng)的理想方式是使用命令行參數(shù)。
Flask-Script 是一個(gè) Flask 擴(kuò)展,為 Flask 程序添加了一個(gè)命令行解析器。Flask-Script 自帶了一組常用選項(xiàng),而且還支持自定義命令。
# 安裝
(venv) $ pip install flask-script
要使用 flask-script 需要在 hello.py 修改下程序:
from flask import Flask
from flask.ext.script import Manager
app = Flask(__name__)
manager = Manager(app)
@app.route('/')
def index():
return '<h1>Hello,World</h1>'
if __name__ == '__main__':
manager.run()
專為 Flask 開發(fā)的擴(kuò)展都暴漏在 flask.ext 命名空間下。Flask-Script 輸出了一個(gè)名為Manager 的類,可從 flask.ext.script 中引入。
這個(gè)擴(kuò)展的初始化方法也適用于其他很多擴(kuò)展:把 app 實(shí)例作為參數(shù)傳給擴(kuò)展的構(gòu)造函數(shù),初始化主類的實(shí)例。創(chuàng)建的對象可以在各個(gè)擴(kuò)展中使用。在這里,服務(wù)器由 manager.run() 啟動(dòng),啟動(dòng)后就能解析命令行了。
注意, 在 Python 3 中要這樣導(dǎo)入 flask-script 擴(kuò)展, from
flask_script import Manager
現(xiàn)在運(yùn)行 hello.py,會(huì)顯示一個(gè)幫助消息:
$ python hello.py
usage: hello.py [-h] {shell,runserver} ...
positional arguments:
{shell,runserver}
shell 在 Flask 應(yīng)用上下文中運(yùn)行 Python shell
runserver 運(yùn)行 Flask 開發(fā)服務(wù)器:app.run()
optional arguments:
-h, --help 顯示幫助信息并退出
shell 命令用于在程序的上下文中啟動(dòng) Python shell 會(huì)話。你可以使用這個(gè)會(huì)話中運(yùn)行維護(hù)任務(wù)或測試,還可調(diào)試異常。顧名思義, runserver 命令用來啟動(dòng) Web 服務(wù)器。運(yùn)行 python hello.py runserver 將以調(diào)試模式啟動(dòng) Web 服務(wù)器,但是我們還有很多選項(xiàng)可用:
$ python hello.py runserver --help
usage: hello.py runserver [-?] [-h HOST] [-p PORT] [--threaded]
[--processes PROCESSES] [--passthrough-errors] [-d]
[-D] [-r] [-R]
--host 參數(shù)是個(gè)很有用的選項(xiàng),它告訴 Web 服務(wù)器在哪個(gè)網(wǎng)絡(luò)接口上監(jiān)聽來自客戶端的連接。默認(rèn)情況下,F(xiàn)lask 開發(fā) Web 服務(wù)器監(jiān)聽 localhost 上的連接,所以只接受來自服務(wù)器所在計(jì)算機(jī)發(fā)起的連接。下述命令讓 Web 服務(wù)器監(jiān)聽公共網(wǎng)絡(luò)接口上的連接,允許同網(wǎng)中的其他計(jì)算機(jī)連接服務(wù)器:
(venv) $ python hello.py runserver --host 0.0.0.0
* Running on http://0.0.0.0:5000/
* Restarting with reloader
現(xiàn)在,Web 服務(wù)器可使用 http://a.b.c.d:5000/ 網(wǎng)絡(luò)中的任一臺(tái)電腦進(jìn)行訪問,其中 “a.b.c.d” 是服務(wù)器所在計(jì)算機(jī)的外網(wǎng) IP 地址。