第二章 程序的基本結(jié)構(gòu)
2.1 Flask簡介
Flask框架由三部分組成,F(xiàn)lask、Werkzeug、Jinja2。
Flask,為了提高自由度和可擴展性。只提供網(wǎng)絡框架的最小核心,其他功能則通過擴展實現(xiàn)。它依賴于下面兩個同樣由Flask作者開發(fā)的python模塊。
Werkzeug,一個WSGI框架,它是HTTP可以理解的信息和python程序可以理解的信息之間的翻譯官。通過它Flask程序可以順暢的閱讀HTTP信息。
Jinja2,一個html模板系統(tǒng),它的目的是簡化html語句的錄入工作量。
2.2初始化
2.2.1 Flask類必須指定根目錄參數(shù)
from flask import Flask
app = Flask(__name__)
這是為了能夠找到資源文件。
參數(shù)提供了程序根目錄位置。資源文件一般在根目錄的相對位置。
在大多數(shù)情況下,name這個參數(shù)就是所需值。這個參數(shù)此處的功能是:指向用它做參數(shù)的flask類的目錄,而不管這個類是直接執(zhí)行或被導出調(diào)用。
2.3 路由
2.3.1 Flask需要知道/index這樣的路徑實現(xiàn)什么功能
@app.route('/index')
def index():
return '<h1>Hell world!</h1>'
路由使用app.route裝飾器,它為路徑/index找到對應的函數(shù),也就是這里的index。
2.3.2 動態(tài)路徑:有時候路徑只有一部分相同,另一部分可變
@app.route('/user/<name>')
def user(name):
return'<h1>hello, %s!</h1>' % name
尖括號中的內(nèi)容就是動態(tài)部分,任何能匹配靜態(tài)部分的URL都會映射到這個路由上。
調(diào)用視圖函數(shù)時,F(xiàn)lask會將動態(tài)部分作為參數(shù)傳入函數(shù)。
2.3.3 動態(tài)類型定義:
分為int、float、path三種。
例如:/user/<int:id>
,只會匹配id為整數(shù)的URL。
path類型也是字符串,但不把斜線視作分隔符,而將其當作動態(tài)片段的一部分。
2.3.4 啟動服務器:使用程序?qū)嵗膔un方法
if __name__ = '__main__':
app.run(debug=True)
如果這個腳本由其他腳本導入,程序會假定父腳本會啟動不同的服務器。
2.4 完整的例子
2.4.1 一個完整的程序
# 示例 2-1 hello.py: 一個完整的Flask程序
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
if __name__ == '__main__':
app.run(debug=True)
使用命令$ python hello.py
啟動程序,訪問地址http://localhost:5000/。
2.4.2 動態(tài)路由版
# 示例 2-2 hello.py: 包含動態(tài)路由的 Flask 程序
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello World!</h1>'
@app.route('/user/<name>')
def user(name):
return '<h1>Hello, %s!</h1>' % name
if __name__ == '__main__':
app.run(debug=True)
訪問 http://localhost:5000/user/Dave,
Dave就是動態(tài)參數(shù)<name>
,嘗試使用不同的名字試試。
2.5 請求-響應循環(huán)
2.5.1 上下文:防止程序參數(shù)冗余
為什么需要上下文:
Flask需要根據(jù)網(wǎng)址返回相應值,這需要視圖函數(shù)訪問一些對象。
例如request對象,它封裝了客戶端發(fā)送的HTTP請求。
要想讓視圖函數(shù)能夠訪問請求對象,
一個顯而易見的方式是將其作為參數(shù)傳入視圖函數(shù),
不過這會導致程序中的每個視圖函數(shù)都增加一個參數(shù),大大增加復雜度。
Flask使用上下文臨時把某些對象變?yōu)槿肿兞浚@使得視圖函數(shù)不需要顯式聲明這個參數(shù),
但卻能夠使用它,大大降低了復雜度。
一個request使用示例:
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<p>Your brower is %s</p>' % user_agent
=服務器中可能有多個客戶端請求,把request作為全局變量會出錯,
而上下文管理使得request可以全局訪問,但又不干擾其他線程。
上下文全局變量:
current_app # 當前激活程序的程序?qū)嵗?例如運行的是hello.py,則current_app為<Flask hello>
g # 處理請求時用作臨時存儲的對象。每次請求都會重設這個變量
request # 請求對象,封裝了客戶端發(fā)出的HTTP請求中的內(nèi)容
session # 用戶對話,用于存儲請求之間需要“記住”的值的詞典
上下文調(diào)用流程:
from hello import app
from flask import current_app # 導入需要使用的上下文
app_ctx = app.app_context() # 激活上下文,取得app_context類的一個實例
app_ctx.push() # 推送上下文
print(current_app.name) # 在線程中使用導入的上下文。
app_ctx.pop() # 手動刪除上下文
flask以app._context類的方法push和pop進出棧的方式,管理多線程。
為防止push棧后,不能pop棧,盡量使用try和finnally.
2.5.2 查看映射:
Flask使用app.route裝飾器或app.add_url_rule()生成映射。
在python shell中使用app.url_map查看映射關(guān)系
>>>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>
路由,
還有一個/static/<filename>
是Flask添加的靜態(tài)文件路由。顯然<filename>
是我們講過的動態(tài)路由中的path類型。
URL 映射中的HEAD、Options、GET 是請求方法,由路由進行處理。
Flask 為每個路由都指定了請求方法,這樣不同的請求方法發(fā)送到相同的 URL 上時,會使用不同的視圖函數(shù)進行處理。
HEAD 和 OPTIONS 方法由 Flask自動處理,因此可以這么說,在這個程序中, URL映射中的 3 個路由都使用 GET 方法。
2.5.3 請求鉤子:避免請求前后的重復操作
請求鉤子是裝飾器,F(xiàn)lask支持4種鉤子:
- before_first_request:注冊一個函數(shù),在處理第一個請求之前運行。
- before_request:注冊一個函數(shù),在每次請求之前運行。
- after_request:注冊一個函數(shù),如果沒有未處理的異常拋出,在每次請求之后運行。
- teardown_request:注冊一個函數(shù),即使有未處理的異常拋出,也在每次請求之后運行。
在請求函數(shù)和視圖函數(shù)之間共享數(shù)據(jù)一般使用上下文全局變量g。
2.5.4 響應:幾種返回類型和狀態(tài)碼
響應是什么?
Flask調(diào)用視圖函數(shù)后,會將其返回值作為響應的內(nèi)容。
返回值由三部分組成:字符串、狀態(tài)碼和header字典。
大多數(shù)情況下,響應只需要字符串,作為HTML頁面回送客戶端。
但如果不是請求成功處理的正常狀態(tài),就需要增加狀態(tài)碼。
header字典有時也會用到。
請求成功和請求無效的狀態(tài)碼
@app.route('/')
def index():
return '<h1>Bad Request</h1>', 400
數(shù)字代碼作為第二個返回值,代表處理狀態(tài)。
400代表請求無效,而200代表請求已被成功處理。
Flask把狀態(tài)碼默認設置為200,所以成功處理可以不指定狀態(tài)碼。
重定向的狀態(tài)碼和簡化處理:
有時返回值不是一個頁面字符串,而是一個新地址,這叫做重定向。
重定向經(jīng)常使用302表示,指向的地址由header Location提供。
由于使用頻繁,F(xiàn)lask提供了簡化處理的形式:redirect函數(shù)
from flask import redirect
@app.route('/')
def index():
return redirect('http://www.example.com')
處理錯誤狀態(tài)碼的abort函數(shù):
HTTP有很多種錯誤,F(xiàn)lask用一個通用 的方法abort來處理。
這是所有錯誤代碼:HTTP錯誤代碼
一個abort的例子:
from flask import abort
@app.route('/user/<id>')
def get_user(id):
user = load_user(id) # 讀取動態(tài)參數(shù)id對應的用戶
if not user: # 如果用戶不存在,則返回狀態(tài)碼404
abort(404)
return '<h1>Hello, %s</h1> % user.name
2.6 Flask 擴展
2.6.1 為什么需要擴展
Flask被設計為更自由的方式,故沒有提供一些重要的功能,例如數(shù)據(jù)庫和用戶認證。
這就需要用擴展的形式來補充Flask源碼沒有的功能,
開發(fā)者可以自由選擇最適合程序的包,或者按需求自行開發(fā)(可以使用所有python標準包或代碼庫。
2.6.2 使用Flask-Script支持命令行選項
為什么需要命令行選項
Flask的開發(fā)web服務器支持很多啟動設置選項,
但只能在腳本中作為參數(shù)傳給app.run()函數(shù)。
這中方式并不十分方便,傳遞設置選項的理想方式是使用命令行參數(shù)。
Flask-Script介紹和安裝
Flask-Script是一個Flask擴展,為Flask程序添加了一個命令行解析器。
Flask-Script自帶了一組常用選項,而且還支持自定義命令。
Flask-Script擴展使用pip安裝:
pip install flask-script
Flask-Script的使用前置步驟
這個實例顯示了把命令行解析功能添加到app程序中需要修改的地方。
from flask_script import Manager # 注意原書中是flask.ext.script,這種方式現(xiàn)在已經(jīng)不推薦了
manager = Manager(app)
# ...
if __name__ == '__main__':
manager.run()
這個擴展的初始化方法也使用于其他很多擴展:
把程序?qū)嵗鳛閰?shù)傳給構(gòu)造函數(shù),初始化主類的實例。
創(chuàng)建的對象可以在各個擴展中使用。
在這里,服務器由manager.run()啟動,啟動后就能解析命令行了。
使用Flask-Script命令行
現(xiàn)在我們像以往那樣,在命令行執(zhí)行
$ python hello.py
這次有點不一樣,并沒有直接運行服務器,而是出現(xiàn)了類似下面的消息:
usage: app.py [-?] {runserver,shell} ...
positional arguments:
{runserver,shell}
runserver 運行Flask開發(fā)服務器,例如 app.run()
shell 在Flask app的上下文中運行python shell
optional arguments:
-?, --help 顯示幫助信息并退出
也就是說,在python hello.py 后面必須跟著shell或者runserver兩者之一。
shell 命令用于在程序的上下文中啟動 Python shell 會話。
你可以使用這個會話中運行維護任務或測試,還可調(diào)試異常。
runserver 命令用來啟動 Web 服務器。
運行 python hello.py runserver 將以調(diào)試模式啟動 Web服務器,但是我們還有很多選項可用
runserver的幾個參數(shù)
- -h HOST 設定為0.0.0.0表示為公網(wǎng),127.0.0.1表示為本局域網(wǎng)
- -p 設定端口 Flask默認為5000,HTTP協(xié)議默認為80
- -d 開啟調(diào)試模式
- -D 不開啟調(diào)試模式
- -r 自動重載文件
- -R 不自動重載文件
- 還有--threaded, --processes, --passthrough等選項。
例如要在服務器上運行Flask程序,應該這樣:
$ python hello.py runserver -h 0.0.0.0 -p 80