1. 程式人生 > >Flask 成長之路(二)---- Flask的一個簡單示例

Flask 成長之路(二)---- Flask的一個簡單示例

上節我們已經安裝好了 Flask ,接下來我們就利用 Flask 寫一個最簡單的示例。

from flask import Flask
app = Flask(__name__)

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

這個程式做了哪些事情呢:

  1. 首先匯入了 Flask 類,這個類的例項將會成為我們的 WSGI 應用。
  2. 接下來我們建立了這個類的例項。傳入的第一個引數是應用程式的模組名或者包名。__name__ 的值會因為啟動的是應用程式(__main__)還是模組(模組名)會有所不同。Flask 需要這些資訊來知道去哪裡找到模板,靜態檔案等。
  3. 然後使用 Route() 裝飾器來告訴 Flask 什麼 URL 用來引發我們的函式。
  4. 函式名同樣用來為特定的函式產生 URLs,函式用來返回我們想要在瀏覽器中展示的資訊。

將上面的檔案儲存為 hello.py 或者其他的名字,除了 flask.py 因為這會和 Flask 自身的 flask.py 檔案衝突。

執行這個應用,可以使用 flask 命令或者 python 的 -m 轉換到 flask,在此之前,需要通過設定 FLASK_APP 將你的應用程式告訴它將要執行的終端。在 windows 中

set FLASK_APP = hello.py

接下來就可以使用 flask 命令或者 python 來啟動應用

預設處於debugger模式,此模式能夠讓使用者在電腦上執行任意的 python 程式碼,但是服務只能在本機上執行。如果要關閉 debugger 模式並讓服務公開,只需要新增 flask run --host=0.0.0.0,他會讓作業系統監聽所有的公共 IP。

如果想要 debug 支援在程式碼改變的時候伺服器自動過載而不是每次都手動重啟服務,可以在執行服務之前設定 FLASK_ENV 為development,在 windows 中

set FLASK_ENV = development
flask run

她做了幾件事:

  1. 啟用 debugger
  2. 啟用自動過載器
  3. 在 flask 應用中使能 debug 模式

也可以通過設定 FLASK_DEBUG = 1 來控制 debug 模式。

使用 route() 裝飾器把一個函式繫結到 URL 上

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

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

還可以使用 <variable_name> 將變數部分加到 URL 中,函式可以接受這個變數作為引數。可以使用轉換器來確定引數的型別,就像這樣 <converter:variable_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

轉換器一共有以下幾種型別

需要注意的是,使用 path 這個轉換器的時候,在變數名後面不加斜線,接收到的是不加斜線的引數;加斜線的時候,接收到的是加斜線的引數。但是對於不是 path 的轉換器,如果路徑中不是以斜線 '/' 結尾的話,而在搜尋 URL 的時候加了斜線,就出現 400 Not Found 錯誤。這也是 URL 的 unique 特性,避免重複搜尋。

看下面這2種寫法。一種是在末尾加了斜線,一種是沒有在末尾加上斜線。

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

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

規範的 URL 寫法後面是有斜線的。如果在搜尋 URL 中沒有加斜線,第一種寫法會讓 flask 重定向到標準的 URL,也就是會自動在搜尋 URL 的時候後面加上斜線,從而在搜尋的時候 URL 後面是否新增斜線都不會報錯。但是第二種寫法如果在搜尋 about 頁面的時候 URL 加了斜線,就會報 404 Not Found 錯誤,這也是 URL 的唯一性所決定的。

我們還可以用 url_for()函式來為某個函式建立 URL,他接受函式名作為它的第一個引數,並且可以接受任意數量的關鍵字引數,其中的每一個對應到 URL 規則的變數部分,未知的變數部分將會作為查詢引數加入到 URL 。

使用 url_for()的理由有以下幾個:

  1. 產生的路徑是絕對的,能夠避免相對路徑的異常行為。
  2. 能夠改變 URLs 而不用記住並手動改變 URLs

我們使用 test_request_context() 函式來試驗 url_for() ,它讓 Flask 在我們使用 Python shell 時像是處理一個請求一樣進行動作

Web應用在搜尋不同的 URL 使用不同的 HTTP 方法,預設的是,路徑只會響應 GET 請求,你可以使用 route() 裝飾器的 method 引數來處理不同的 HTTP 方法。

from flask import request

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        return do_the_login()
    else:
        return show_the_login_form()

動態 Web 應用同樣需要靜態檔案,通常是 JavaScript 和 CSS 檔案。要建立靜態檔案只需要在包裡面建立一個檔案並取名為 static ,就可以 /satatic 下使用。為靜態檔案建立 URL 可以使用 url_for() 函式,使用 static 這樣一個特殊的端點名稱。

url_for('static', filename='style.css')

這個檔案必須在檔案系統中儲存為 static/style.css

Flask 使用為你的應用配置一個 Jinja模板引擎。使用 render_template() 函式來渲染一個模板,你需要提供模板名稱以及傳給模板引擎作為關鍵字引數的變數

from flask import render_template

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

Flask 會在 templates 檔案中查詢模板,因此如果你的應用是一個模組,這個檔案就在模組旁邊,如果是一個包它就會在包裡

一個模板樣例如下

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

在模板裡面你同樣可以使用 request,session,和 g 物件,還有 get_flashed_messages() 函式。

request 物件記錄在 API 部分,要使用首先要從 flask 模組中匯入 request。

通過 request 的 method 屬性可以獲得當前請求的方法。通過 form 屬性可以獲得表單資料。

@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)

如果關鍵字在表單屬性中不存在,就會丟擲特殊的 KeyError 異常,可以像標準的 KeyError 異常一樣對她進行捕獲,否則頁面就會顯示 400 Bad Request。因此大多數情況不需要處理這個問題。

通過 args 屬性獲得傳遞到 URL 中的引數 (?key=value)

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

推薦使用 get 或者捕獲 KeyError 獲得 URL 引數,因為使用者可能改變 URL 或者不當的使用引發 400 Bad Request。request 物件更多的方法和屬性可以查閱 Request 文件。

Flask 也可以很容易的處理上傳的檔案,這個時候需要在 HTML 表單中設定 'enctype="multipart/form-data' ,否則瀏覽器不會為你傳送檔案。通過 request 物件中的 file 屬性獲得這些檔案,他就像標準的 python 檔案物件一樣,但是他有 save() 方法允許你把檔案儲存到伺服器的檔案系統。

from flask import request

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/uploaded_file.txt')
    ...

如果想要知道檔案在上傳到應用之前在客戶端是如何命名的,可以使用 filename 屬性。但是其實這個值是可以偽造的。如果想要用客戶端的檔名來儲存伺服器端的檔案,通過 Werkzeug 提供的 secure_filename() 函式來傳遞。

from flask import request
from werkzeug.utils import secure_filename

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        f = request.files['the_file']
        f.save('/var/www/uploads/' + secure_filename(f.filename))

關於 Cookies ,可以 通過 cookies 屬性獲得 cookies 並可以通過 response 物件的 set_cookie 方法設定 cookies。request 物件的 cookies 屬性是一個字典,包含了客戶端傳送的所有 cookies。如果想要使用 sessions,就不要直接使用 cookies 而是使用 Flask 中的 Sessions ,可以為 cookies 增加安全性。

from flask import request

@app.route('/')
def index():
    username = request.cookies.get('username')
    # use cookies.get(key) instead of cookies[key] to not get a
    # KeyError if the cookie is missing.

儲存 cookies

from flask import make_response

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

通過 redirect() 可以重定向,使用 abort 加上錯誤碼可以丟棄一個請求

from flask import abort, redirect, url_for

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

@app.route('/login')
def login():
    abort(401)
    this_is_never_executed()

可以使用 errorhandler() 裝飾器來定製錯誤頁

from flask import render_template

@app.errorhandler(404)
def page_not_found(error):
    return render_template('page_not_found.html'), 404

注意到 render_template 後面的 404 ,他會告訴 flask 那個頁面的狀態碼,404代表 Not Found。200 代表正常。

檢視函式的返回值會自動轉換為 response 物件。如果返回值是一個字串,它會轉換為字串作為響應體的 response 物件,或者是一個 200 OK 的狀態碼或者是一個 文字/HTML mimetype。Flask 轉換返回值為 response 物件的邏輯如下:

  1. 如果返回正確的 response 物件型別,就直接從檢視函式返回。
  2. 如果是一個字串,就用那個資料和預設引數建立一個 response 物件。
  3. 如果返回一個元組,並且元組中的元素能夠提供額外的資訊,這樣的元組必須放在表中(response,status,headers) 或者(response,headers) 也就是至少有一個元素放在表中。status 會重寫狀態碼,headers 可以是一個 列表 或者 字典。
  4. 如果上面情況都沒有出現,Flask 會認為返回值是一有效的 WSGI 應用,並將之轉換為一個 response 物件。

如果想要控制 view 裡面的 response 物件的結果,可以使用 make_response() 函式

@app.errorhandler(404)
def not_found(error):
    return render_template('error.html'), 404

例如上面的 view ,你只需要用 make_response()  重寫返回表示式,獲得 response 物件並修改它,然後將之返回即可。

@app.errorhandler(404)
def not_found(error):
    resp = make_response(render_template('error.html'), 404)
    resp.headers['X-Something'] = 'A value'
    return resp

除了 request 物件還有一種物件那就是 session 物件,它能夠從一個請求到另一個請求的特定使用者的資訊。他以加密的方式在 cookies 上實現,這意味著使用者能夠看到你的 cookies 的內容但是不能修改它,除非以你知道密碼。

要使用 sessions 你必須設定密碼

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

app = Flask(__name__)

# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'

@app.route('/')
def index():
    if 'username' in session:
        return 'Logged in as %s' % escape(session['username'])
    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">
            <p><input type=text name=username>
            <p><input type=submit value=Login>
        </form>
    '''

@app.route('/logout')
def logout():
    # remove the username from the session if it's there
    session.pop('username', None)
    return redirect(url_for('index'))

這裡的 escape() 是用於當你沒有使用模板引擎的時候跳出。

一個好的應用必須要處理好使用者反饋,Flask 提供了一個簡單的方式,也就是 flashing 系統,這個系統在請求最後記錄資訊,能夠且僅能夠在下一個請求獲得它,這通常是和佈局模板結合起來暴露這個資訊。通常需要使用的函式就是 flash() 以及如果想要控制這個資訊要使用的 get_flash_messages() 函式

如果想要記錄日誌資訊,可以使用 logger

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

如果想要在應用中新增 WSGI 中介軟體,你也可以覆蓋掉內建的 WSGI 應用

from werkzeug.contrib.fixers import LighttpdCGIRootFix
app.wsgi_app = LighttpdCGIRootFix(app.wsgi_app)

此外,Flask 還有許多擴充套件,這些擴充套件通常是一些包能夠讓你完成一些通用的任務。例如,Flask-SQLAlchemy 提供了 SQLAlchemy 簡單易用的 Flask支援。