1. 程式人生 > >77.Flask基礎知識

77.Flask基礎知識

簡介

Flask是Python的一個輕量微框架。微框架中的“微”,意味著Flask核心簡單,但這並不意味著Flask功能薄弱,相反,Flask有許多外掛支援連線資料庫、表單驗證、檔案上傳等各種功能,配合這些外掛可以實現非常強大的功能。Flask是Python在Web開發領域的一件利器。

安裝

Flask支援Python>=3.4、Python 2.7或PyPy,安裝命令:

pip install Flask

自動安裝如下依賴:

  • Werkzeug:Python的一種WSGI實現。
  • Jinja:一種模板渲染語言。
  • MarkupSafe:配合Jinja,轉義客戶端的輸入,規避跨站指令碼攻擊(Cross Site Script,CSS)。
  • ItsDangerous:利用數字簽名技術保證資料一致性,用來保護Flask的session加密。
  • Click:編寫命令列程式的框架,Flask用它支援flask命令並允許新增自定義命令。

如下依賴不會自動安裝,如果環境上存在的話,Flask會自動載入使用:

  • Blinker:支援訊號量(Signal)。
  • SimpleJSON:相容Python的json模組,速度更快。
  • python-dotenv:執行flask命令時,提供從dotenv提取環境變數的支援。
  • Watchdog:開發模式下,提供更快、更高效的裝載器。

小試牛刀

# hello.py,應用程式檔名不能是flask.py,這會和Flask本身衝突
from flask import Flask #一個Flask類的例項表示一個Web應用程式 app = Flask(__name__) #第一個引數是應用程式的模組或包名,Flask根據此引數查詢模板、靜態檔案等內容 @app.route('/') #route()裝飾器,定義URL和檢視函式的繫結關係 def hello_world(): return "Hello, World!"

啟動應用程式可以使用flask命令:

$ export FLASK_APP=hello.py
$ flask run

也可以使用Python的-m開關:

$ export FLASK_APP=
hello.py $ python -m flask run

Flask會啟動一個非常簡單的內建伺服器( 僅供除錯,不建議用於生產環境),瀏覽器訪問http://127.0.0.1:5000即可看到Hello, World的響應: 在這裡插入圖片描述 預設情況下,Flask應用程式只監聽本機loopback網路,flask run --host=0.0.0.0可以令程式監聽所有網路埠。一定要確保關閉了除錯功能,再開放監聽所有網路埠。

開發模式與除錯功能

Flask的開發模式,在程式出錯時提供除錯功能,並且自動裝載最新的程式碼,不必每次更新程式碼都重啟應用程式。flask run之前通過如下方式開啟開發模式:

export FLASK_ENV=development

開發模式的Flask開啟瞭如下功能:

  1. 開啟偵錯程式
  2. 開啟自動裝載器
  3. 在Flask應用程式上應用偵錯程式

如果想只在Flask應用程式上應用偵錯程式,使用如下命令:

export FLASK_DEBUG=1

生產環境無論如何都不要開啟開發模式,開發模式下允許應用程式的使用者在伺服器上執行任意的Python程式碼。比如如下異常場景: 在這裡插入圖片描述 滑鼠懸浮在報錯的那一層上,點選最後側的命令列圖示,即可基於該層進行除錯: 在這裡插入圖片描述

路由

Flask通過route()裝飾器繫結URL和檢視函式,例如:

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Index Page'

@app.route('/hello')
def hello():
    return 'Hello, World'

URL的可變部分用<variable_name>代替,<variable_name>的值作為關鍵字引數傳遞給繫結的函式,也可以使用<converter:variable_name>指定變數的型別。

from flask import Flask
app = Flask(__name__)

@app.route('/user/<username>')
def show_user_profile(username):
    return 'User %s' % username

@app.route('/post/<int:post_id>')
def show_post(post_id):
    return 'Post %d' % post_id

@app.route('/path/<path:subpath>')
def show_subpath(subpath):
    return 'Subpath %s' % subpath

converter支援如下型別:

  • string:除斜線(/)外任何字串。
  • int:十進位制正整數。
  • float:正浮點數。
  • path:類似string型別,但是接受斜線(/)。
  • uuid:接受uuid字串。 對於特殊字元(如#),在瀏覽器中輸入時需要URL轉碼後訪問(如%23)。

URL唯一性與URL重定向

有如下兩種格式定義的URL:

rom flask import Flask
app = Flask(__name__)

@app.route('/projects/')
def projects():
    return 'The project page'

@app.route('/about')
def about():
    return 'The about page'

訪問http://127.0.0.1:5000/projects,Flask會將請求重定向到http://127.0.0.1:5000/projects/。訪問http://127.0.0.1:5000/about/,Flask響應404 Not Found。建議使用不帶字尾斜線的URL,為資源確定唯一的URL,防止搜尋引擎為同樣的頁面建立兩次索引。

生成URL

使用url_for()函式生成某個方法的URL,url_for()的第一個引數是方法名(字串),然後是關鍵字引數,這些關鍵字引數相當於URL的可變部分<variable_name>,如果提供的關鍵字引數不存在,作為查詢引數生成URL(?key=value)。

不將URL硬編碼到模板檔案中的理由:

  1. 生成URL比硬編碼更具可讀性。
  2. 修改URL格式時,只需修改route()裝飾器定義處,無需修改所有使用了此URL的地方。
  3. 生成URL自動處理字元轉義、Unicode編碼轉換等場景。
  4. 生成URL得到的是絕對路徑,避免了瀏覽器中使用相對路徑可能出現的問題。
  5. 如果應用程式不在URL根目錄下,比如在/myapplication目錄而不是/目錄,url_for()也能正確處理。
from flask import Flask
from flask import url_for

app = Flask(__name__)

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

@app.route('/login')
def login():
    return 'login'

@app.route('/user/<username>')
def profile(username):
    return '{}\'s profile'.format(username)

with app.test_request_context(): #假裝Flask收到了一個請求,即使是通過Python命令列啟動的指令碼
    print url_for('index')
    print url_for('login')
    print url_for('login', next = '/')
    print url_for('profile', username = 'mars loo')

執行結果:

/
/login
/login?next=%2F
/user/mars%20loo

HTTP方法

Flask路由預設支援HTTP GET方法,如果要支援其他HTTP方法,在定義route時給出methods引數:

from flask import Flask
from flask import request

app = Flask(__name__)

@app.route('/login', methods = ['GET', 'POST'])
def login():
    if request.method == 'GET':
        return 'Show the login form'
    else:
        return 'Valid user'

Flask預設實現了OPTIONS方法,如果URL支援GET方法,Flask也自動支援了HEAD方法。

靜態檔案

動態Web網站也需要靜態檔案,如CSS、JavaScript檔案等。使用靜態檔案,需要在包中建立一個名為static的目錄(如果使用模組,建立與模組同級的static目錄),然後就可以通過/static的方式訪問了。獲取靜態檔案的URL路徑,可以置url_for()方法,置第一個引數名為static,第二個引數名為filename

from flask import Flask
from flask import url_for

app = Flask(__name__)

@app.route('/')
def index():
    return url_for('static', filename = 'style.css')

獲取style.css檔案的URL連結時,這個檔案不一定真的存在。

渲染模板

用Python生成HTML非常麻煩,需要考慮字元轉義、應用程式安全等問題。Flask引入了Jinja2模板引擎支援自動渲染HTML檔案。 渲染模板可以render_template()函式,函式的第一個引數是模板檔名,剩餘的關鍵字引數是要傳入模板的值,例如:

# application.py
from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name = None):
    return render_template('hello.html', name = name)

建立與application.py同級的templates目錄,放入hello.html模板檔案:

<!-- hello.html -->
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
    <h1>Hello {{ name }}!</h1>
{% else %}
    <h1>Hello, World!</h1>
{% endif %}

如果name變數包含HTML程式碼,render_template()會自動將其轉義(僅.html.htm.xml.xhtml支援自動轉義,如果模板引數是字串,預設不支援自動轉義)。如果Flask應用程式是一個包,則templates目錄包含在包的目錄下即可:

/application
    /__init__.py
    /templates
        /hello.html

在模板檔案(本例是hello.html)中,也可以使用requestsessiong等物件及get_flashed_messages()方法。 模板配合繼承功能尤其有用,比如可以讓某些元素在所有頁面都存在(頁首、導航、頁尾等)。

獲取請求資料

Web應用程式的基本場景:服務端接收客戶端請求,服務端處理請求體後,傳送響應給客戶端。Flask通過全域性的request物件訪問請求,該變數是執行緒安全的(thread local)。 使用request物件要先import:

from flask import request

請求的HTTP方法通過request物件的method屬性獲得。通過form屬性,可以獲得POSTPUT方法提交的資料。

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

app = Flask(__name__)

def valid_login(username, password):
    return False

@app.route('/login', methods = ['POST', 'GET'])
def login():
    error = None
    if request.method == 'POST':
        if valid_login(request.form['username'],
                       request.form['password']):
            return log_the_user_in(request.form['username'])
        else:
            error = 'Invalid username/password'
    return render_template('login.html', error = error)

login.html檔案內容:

<!doctype html>
<title>Login page</title>
{% if error %}
    <p>{{ error }}</p>
{% endif %}
<form action="/login" enctype="multipart/form-data" method="post">
    <label>username</label>
    <input type="text" name="username"/>
    <label>password</label>
    <input type="password" name="password" />
    <input type="submit" />
</form>

form屬性中的key不存在,會丟擲KeyError異常,可以捕獲處理該異常,如果不處理,會返回HTTP 400 Bad Request,大多數情況無需自己處理該異常。 獲取URL中的引數(?key=value),使用args屬性(dict型別):

searchword = request.args.get('key', '')

檔案上傳

HTML網頁通過form表格上傳檔案到Web服務端:

<!-- index.html -->
<!doctype html>
<title>upload file</title>
<body>
    <form method="post" enctype="multipart/form-data" action="/upload">
        <label>your file:</label>
        <input type="file" name="certi_file" />
        <input type="submit">
    </form>
</body>

上傳檔案儲存在臨時記憶體,通過request.files屬性獲取所有上傳檔案,例如:

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

app = Flask(__name__)

@app.route('/upload', methods = ['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['certi_file']
        f.save('./certi_file')
        return 'ok'

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

上傳檔案的原始檔名儲存在filename屬性,直接用這個檔名有CSS攻擊風險,可以使用Werkzeug的secure_filename()規避:

from flask import Flask
from flask import request
from flask import render_template
from werkzeug.utils import secure_filename

app = Flask(__name__)

@app.route('/upload', methods = ['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['certi_file']
        f.save(secure_filename(f.filename))
        return 'ok'

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

Cookie

request.cookie屬性儲存客戶端上傳的所有cookie。讀取cookie:

from flask import Flask
from flask import request

app = Flask(__name__)

@app.route('/')
def index():
    username = request.cookies.get('username', '')
    return username

寫入cookie:

from flask import Flask
from flask import request
from flask import make_response

app = Flask(__name__)

@app.route('/')
def index():
    resp = make_response('')
    resp.set_cookie('username', 'loo')
    return resp

@app.route('/get_cookie')
def get_cookie():
    username = request.cookies.get('username', '')
    return username

有響應物件才能寫入cookie,呼叫make_response()函式可以顯示獲取一個響應物件。

重定向和報錯

redirect()函式重定向到另一個URL節點。abort()函式異常終止處理、返回客戶端一個錯誤碼。

from flask import abort
from flask import redirect
from flask import url_for
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return redirect(url_for('login')) #預設錯誤碼302

@app.route('/login')
def login():
    abort(401)
    print "never executed"

使用errorhandler()裝飾器定製錯誤頁面的樣式:

from flask import abort
from flask import redirect
from flask import url_for
from flask import Flask
from flask import render_template

app = Flask(__name__)

@app.route('/')
def index():
    return redirect(url_for('login'))

@app.route('/login')
def login():
    abort(401)
    print "never executed"

@app.errorhandler(401)
def not_authorized(error):
    return render_template('not_authorized.html'), 401
<!-- not_authorized.html -->
<!doctype html>
<title>Not authorized</title>
<body>
    <p>You are not authorized</p>
</body>

關於響應

URL繫結的檢視函式,自動將return的值轉換為響應物件,轉換規則如下:

  1. 如果是響應物件,直接返回。
  2. 如果是字串,將字串作為響應物件的body,狀態碼為200,MIME型別為text/html
  3. 如果return的是元組,必須按照(response, status, headers)(response, headers)的格式,headers可以是列表,或者頭資訊的字典。
  4. 如果上述規則均未命中,認為返回值是一個合法的WSGI應用,並將其轉換為響應物件。

例如如果在404頁面響應中新增頭資訊,可以通過make_reponse()函式顯示獲得響應物件後新增:

from flask import Flask
from flask import render_template
from flask import make_response

app = Flask(__name__)

@app.errorhandler(404)
def page_not_found(error):
    resp = make_response(render_template('page_not_found.html'), 404)
    resp.headers['key'] = 'value'
    return resp
<!-- page_not_found.html -->
<!doctype html>
<title>page not found</title>
<body>
    <p>Page not found</p>
</body>

Session

session物件可以用於多次請求間儲存資料。Flask內建的sesison基於cookie實現,並對cookie做了簽名加密處理,使用者能夠看到cookie的值但是無法修改,除非他知道金鑰的值。Flask內建的session比cookie更安全。

from flask import Flask
from flask import request
from flask import session
from flask import redirect
from flask import url_for
from flask import escape
import os

app = Flask(__name__)

secret_key = os.urandom(16)
print "secret_key:", secret_key
app.secret_key = secret_key

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username']) #escape可以轉義特殊字元,防止CSS攻擊
    return 'You are not logged in'

@app.route('/login', methods = ['GET', 'POST'])
def login():
    if request.method == 'POST':
        session['username'] = request.form['username']
        return redirect(url_for('index'))
    return '''
        <form method="post">
            <label>username</label>
            <input type="text" name="username" />
            <input type="submit" value="login" />
        </form>
    '''

@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('index'))

預設情況下,Flask使用了這種基於客戶端cookie的session技術.如果某些session沒有在請求間傳遞,檢查瀏覽器是否開啟了cookie支援,或者cookie的大小是否超過了瀏覽器的最大限制。

Flash訊息

Flash訊息技術,在返回響應前儲存一個訊息,在下次請求(且僅下次請求)時訪問到這個訊息,該技術通常配合一個浮層模板顯示提示訊息。使用flash()函式釋出一個flash訊息,使用get_flashed_messages()獲取上一次請求中釋出的訊息(在Jinja模板中也可以使用此函式)。

日誌

Flask內建了一個logger支援記錄日誌,例如:

app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')

具體用法參考官方文件

使用Flask擴充套件

Flask擴充套件為Flask應用提供了豐富的功能支援,例如傳送郵件、連結資料庫、開發ReST API應用等。Flask擴充套件的名字通常是Flask-FooFoo-Flask,Flask開發團隊會review這些擴充套件,並批准通過與未來發布的Flask相容的擴充套件(approved extension)。