? 一篇文章入門Flask

Flask本身相當于一個內核,其他幾乎所有的功能都要用到擴展(郵件擴展Flask-Mail,用戶認證Flask-Login),都需要用第三方的擴展來實現。

Flask對WSGI (路由)的實現,是采用 Werkzeug,而模板引擎(業務視圖) 則使用 Jinja2。這兩個是Flask框架的核心。

Flask核心就是作為一個Webapp框架的兩個基礎部分:

  • CGI (WSGI) 路由:是由Werkzeug實現,即將網絡請求翻譯成Python語言
  • Template 業務模版:是由Python流行的Jinja2實現

除此之外,Flask其它一切的都是由第三方插件實現:
包括:

Flask-SQLalchemy:操作數據庫; Flask-migrate:管理遷移數據庫; Flask-Mail:郵件; Flask-WTF:表單; Flask-Bable:提供國際化和本地化支持,翻譯; Flask-script:插入腳本; Flask-Login:認證用戶狀態; Flask-OpenID:認證; Flask-RESTful:開發REST API的工具; Flask-Bootstrap:集成前端Twitter Bootstrap框架; Flask-Moment:本地化日期和時間; Flask-Admin:簡單而可擴展的管理接口的框架

參考Flask擴展列表:http://flask.pocoo.org/extensions/

Flask安裝

安裝很簡單:

$ pip install flask

建議在Virtualenv虛擬環境下安裝,因為Flask需要一系列的依賴,最好給Flask生成一個專用的生產環境,并生產requirement.txt依賴列表:

# 生產虛擬環境
$ virtualenv ./flask_env
# 啟動虛擬環境
$ ./flask_env/bin/active
# 生產依賴列表
$ pip freeze > requirements.txt

Hello World

一個最簡單的Flask程序,只需要三步:

  • 生成Flask類的實例,即一個APP
  • 指定目錄的路由規則,即不同路徑可以實現不同的操作。
  • 運行app:app.run()

hello-world.py

from flask import Flask

# 生產一個Flask APP 實例,并指向當前文件(模塊)
app = Flask(__name__)

# 指定"/"根目錄的路由規則
@app.route('/')
def index():
    return 'Hello World'

# 開始運行app
app.run()

Flask中,路由的實現是用裝飾器:@app.route("/")這種方式來做到的。

Flask路由規則

頁面顯示指定內容:

@app.route('/')
def index():
    return '<h1> 歡迎訪問此頁面 </h1>'

頁面返回指定內容、狀態碼、headers等:

@app.route('/')
def index():
    body = '<h1> 歡迎訪問此頁面 </h1>'
    status_code = 200
    headers = {'Cache-Control':'no-cache', 'Connection':'keep-alive'}
    return body, status_code, headers

指定接收的請求方式

@app.route('/', method='GET')
# ...

給路由傳參數:

@app.route('/orders/<order_id>')
def hello_itheima(order_id):
    return 'The ID of order is: %d' % order_id

限制路由參數的數據類型:

@app.route('/orders/<int:order_id>')
# ...

其中int:order_id是指定參數order_id必須能轉換成int整數,否則這個請求就會自動被拒絕。
比如用戶請求http://xyz.com/orders/HAHAHAH,這就不成功。而http://xyz.com/orders/210這樣的就成功。

image
image

中斷請求:

# ...

from flask import abort

@app.route('/wrong-page')
def hello():
    # 中斷請求,并返回404狀態碼
    abort(404)

處理錯誤請求:

# ...

@app.errorhandler(404)
def handel_404_error():
    return '<h1> The page does not exist </h1>'

其中,如果路由中調用了abort(404)中斷函數,或是其它產生404錯誤的方法,這個app.errorhandler都會被自動調用。

返回“渲染”過的模版(即把動態的模版渲染成靜態的HTML):

#...
from flask import render_template

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

Flask 路由分割

如果所有路徑的路由都定義在一個文件里,會很難維護,所以必定要把各個路徑的路由分拆到不同的文件里。

這種分拆很簡單:

正常情況下,在index.py主模塊中,我們還是一樣正常的定義路由:

#...
@app.route('/')
def index():
   pass

然后我們可以把其它路由的處理函數分別放在別的文件里,比如:
register.py中定義“普通函數”:

def reg():
    pass

以及login.py中定義一個普通函數:

def signin():
    pass

然后回到主模塊index.py中,我們可以導入這些函數,并顯式的將這些函數注冊到路由上:

from flask import Flask

from register import reg
from login import signin

app = Flask(__name__)

app.route('/register')(reg)
app.route('/login')(signin)

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

然后我們可以用app.url_map獲得當前定義過的所有路由:

print( app.url_map )
image
image

Flask Blueprint 藍圖

我們手動分割路由處理函數,然后分別導入,這樣雖然也簡單,但是不不好的地方是,主模塊外定義的各個處理函數,本身很難看出來處理的是什么路由邏輯。

為此,Flask提供了另一種路由分割的方法:即Blueprint類。
而這個Blueprint類生成的對象,是在子模塊中代替了之前我們所使用的Flask類生成的app對象。
也就是說:主模塊還是用app,但是子模塊中用藍圖blueprint。

假設我們現在有一個子模塊order.py定義"/order"路徑的路由,那么文件中定義如下:

from flask import Blueprint

# 生成藍圖實例:參數中一個是藍圖名稱,一個是主模塊名稱
app_orders = Bluepint('blueprint_orders', __name__)

# 將路由添加到藍圖里
@app_orders.route('/orders')
def get_orders():
    pass

然后回到主模塊index.py中,把藍圖注冊到主路由上:

#...
from orders import app_orders

app = Flask(__name__)

app.register_blueprint( app_orders )

#...

Hooks 鉤子事件

Flask提供一個完整請求至回應的事件流,其中包括:

  • @app.before_first_request: 接受第一次請求之前執行
  • @app.before_request: 接受請求前,每次請求之前都執行。
  • @app.route(): 處理請求
  • @app.after_request: 請求之后執行,但前提是請求中沒有出現異常
    -@app.teardown_request: 關閉請求時,即每次請求是否異常都會被執行

以下是鉤子的用法:

#...

@app.before_first_request
def handle_before_first_request():
    pass

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

@app....
def ...

Flask上下文請求對象 flask.current_app

request.current_app 是Flask特有的一種request請求處理方式,不同于flask.request對象的處理方式,它是能區分多個請求的。

在我們常用的flask.request對象中,會有一個很嚴重的問題:即它是一個全局變量。也就是說,如果服務器在處理并發請求時使用的是在同一個進程里的多線程,那么不同用戶的請求也許會使用同一個flask.request對象!這時候request中的請求信息就會出現混淆!

所以Flask引入了request.current_app這個對象,即它能夠根據上下文來區分不同人的請求。
這是怎么做到的呢?其實很簡單,它只是把request變為一個局部變量而已。這樣一來,每次的request請求,都是各自獨立的局部對象。

返回響應信息 flask.make_response

除了我們自己定義返回的信息外,Flask提供了一個內置的make_response對象,便于處理返回信息。

返回全文信息:

from flask import make_response

@app.route('/')
def index():
    resp = make_response('<h1> 歡迎訪問此頁面 </h1>')
    resp.status = 200
    resp.headers['Cache-Control'] = 'no-cache'
    return resp

設置cookies:

from flask import make_response

@app.route('/')
def index():
    resp = make_response('<h1> 此頁面會設置你的cookies :) </h1>')
    resp.set_cookie('uuid', '1230sfjdlsj3uu')
    resp.set_cookie('name', 'Jason', max_age=360)
    return resp

其中,max_age是cookie的存活時間,以s秒為單位。不設置的話,默認是臨時cookies,即瀏覽器關閉后立馬失效。

刪除cookie:resp.delete_cookie('uuid')。注意,這里的刪除并不是立馬刪除瀏覽器中用戶的cookie,而只是把max_age設置為0,即瀏覽器關閉后立馬失效。

獲取請求信息 flask.request

Flask中有一個request對象,接收了一切對當前模塊的請求數據。
使用的話,直接在@app.route后面的函數中用def index(request)接收來自裝飾器的請求對象即可使用。

request參數類型:

image
image

常用的各種類型操作如下:

from flask import request 
app = Flask(__name__)

@app.route('/', method='POST')
def index(request):
    # Get uploaded file
    afile = request.files.get('pic')
    with open('./pic.jpg', 'w') as f:
        f.write( afile.read() )

    # Get a form
    form = request.from    # Dict類型
    name = form.get('name')
    age = form.get('age')

    # Get cookies
    uuid = request.cookies.get('uuid')
    name = request.cookies.get('name')

會話處理 flask.session

在登錄頁設置session,并在index頁根據session判斷是否登錄:

from flask import session
#...

app.config['SECRET_KEY'] = 'asdlkjflaj23jrsdjf任意字符串作為密鑰kaljdsl;fkja;j'

@app.route('/login')
def login():
    # 設置sessions
    session['uuid'] = '123abadsf'
    return '<p> 登錄成功 </p>'

@app.route('/')
def index():
    # 獲取sessions
    uuid = session['uuid']
    # 判別session是否存在
    if uuid:
        return '<p> 之前已登錄過 </p>'
    else:
        return '<p> 未登錄,請重新登錄 </p>'

其中,Flask默認情況下,會利用app.config['SECRET_KEY']的值作為一個密鑰,來加密你手動設置的session,然后把這個信息轉換為名叫session的cookie存在瀏覽器中。
這個是Flask特別的一點。

但是把敏感的session數據保存到誰都能訪問的cookie中,即使加密了也不是很安全。
所以一般我們還是會手動把session數據存到服務器后臺的數據庫中,而不是存到cookie中。
每次驗證再與數據庫進行對比。

表單處理 request.form

動態網頁必須要的就是Form表單。Flask中有自帶的form表單處理方法。不過我們也可以用第三方插件Flask-WTF實現。

這里我們先只講自帶的處理方式。

Flask自帶表單處理

假設我們有一個表單模版form.html

<form method="post">

    用戶名:<input type="text" name="username">
    密碼: <input type="password" name="password">
    確認密碼: <input type="password" name="password2">

    <input type="submit" value="提交"><br>

    {% for message in get_flashed_messages() %}
        {{ message }}
    {% endfor %}
    
</form>

當用戶點擊submit提交時,
整個form信息就會用POST方式提交到Flask的路由文件abc.py中。
我們進行處理如下:

from flask import Flask
from flask import render_template
from flask import request

app.secret_key = 'abc123'

@app.route('/', methods=['GET', 'POST'])
def hello():

    if request.method == 'POST':

        # 獲取參數, 并效驗參數完整性, 如果有問題就進行flash
        username = request.form.get('username')
        password = request.form.get('password')
        password2 = request.form.get('password2')

        if not all([username, password, password2]):
            flash('params error')
        elif password != password2:
            flash('password error')
        else:
            print username
            return 'success'

    return render_template('Congratulations.html')

Flask的HTTP Server

一般我們在開發調試過程中,可以用Flask自帶的WSGI和一個小HTTP Server來實現整個App正常運轉。
但是生產環境中,這兩個自帶的組件就效率很低了。所以我們需要用效率更高的獨立的CGI和獨立的HTTP Server服務器來部署真正的生產環境

一般常見的選項有:

  • HTTP Server -> 首推Nginx
  • CGI翻譯器 -> Gunicorn (Python開發,實現了WSGI翻譯)

所以,我們一般采用Nginx + Gunicorn + Flask來部署網絡應用。

Gunicorn的使用:

# 安裝
$ pip install gunicorn

# 進入Flask app的主目錄
cd ./myFlask

# 用gunicorn服務器啟動Flask app
$ gunicorn -w 4 -b 127.0.0.1:8080 main:app

這個時候,flask就在gunicorn的HTTP服務器上運行了,可以通過127.0.0.1:8080訪問到app。

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

推薦閱讀更多精彩內容