Flask之旅: 快速上手

小諾哥發表於2019-01-20

路由

Flask只有route()裝飾器把檢視函式繫結到url上面。

@app.route('/user')
def hello_user():
    return 'Hello, user!'
複製程式碼

另外我們也可以指定動態url。

通過把 URL 的一部分標記為 <variable_name> 就可以在 URL 中新增變數。標記的 部分會作為關鍵字引數傳遞給函式。

# 比如獲取一個使用者詳情的url

@app.route('/user/<id>/')
def hello_user(id):
    return 'Hello, userid: {0}!'.format(id)
複製程式碼

<variable_name>預設是字串, 如果我們需要指定引數型別可以通過:

<converter:variable_name>
複製程式碼

Flask執行的轉換器型別:

轉換器

唯一Url

Flask的URL規則基於Werkzeug的路由模組, 主張保證優雅且唯一的URL。

以上述的例子為例如果你訪問

http://0.0.0.0:9999/user/2
複製程式碼

我們定義的路由是'/user//', 如果訪問一個結尾不帶/的URL會被重定向到帶斜槓的URL上去,這樣可以保持 URL 唯一,並幫助 搜尋引擎避免重複索引同一頁面。

URL唯一性

構造URL

使用url_for()構建url, 它接受函式名字作為第一個引數, 也接受對應URL規則的變數部分的命名引數, 未知的變數部分會新增到URL末尾作為查詢引數。

from flask import Flask, url_for
app = Flask(__name__)

app.config['DEBUG'] = True

@app.route('/user/<int:id>')
def user(id):
    pass

#  test_request_context() 告訴 Flask 正在處理一個請求
with app.test_request_context():
    print(url_for('user', id=2))
    print(url_for('user', id=3, type='doctor'))

if __name__ == '__main__':
    app.run(host='0.0.0.0', port='9999')
複製程式碼

輸出:

/user/2
/user/3?type=doctor
複製程式碼

為什麼不在把 URL 寫死在模板中,而要使用反轉函式 url_for() 動態構建?

1. 反轉通常比硬編碼URL的描述性更好。
2. 未來更過URL, 你可以只在一個地方改變 URL,而不用到處替換。
3. URL建立會為你處理特殊字元的轉義和Unicode資料,比較直觀。
4. 生產的路徑總是絕對路徑,可以避免相對路徑產生副作用。
5. 如果你的應用是放在URL根路徑之外的地方(如在 /myapplication 中,不在 / 中), url_for() 會為你妥善處理。
複製程式碼

跳轉與重定向

跳轉(301)多用於舊網址在廢棄之前轉向新網址保證使用者的訪問, 有頁面被永久性易走的概念。

重定向(302)表示頁面只是暫時的轉移, 不建議經常性使用重定向。

redirect(location) #預設是302
redirect(location, code=301) # 可以指定code
複製程式碼
from flask import Flask, url_for, render_template, redirect
app = Flask(__name__)

app.config['DEBUG'] = True

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

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

if __name__ == '__main__':
    app.run(host='0.0.0.0', port='9999')
    
複製程式碼

HTTP方法

Web 應用使用不同的 HTTP 方法處理 URL,預設情況下,一個路由只回應 GET 請求。可以使用 route() 裝飾器的 methods 引數來處理不同的 HTTP 方法:

@app.route('/login', methods=['GET', 'POST'])
複製程式碼

簡單介紹下常見的HTTP方法與使用場景:

- GET: 獲取資源, GET操作應該是冪等的

- HEAD: 想要獲取資訊, 但是隻關心訊息頭, 應該可以把它當作GET來處理, 但是不返回內容。具有冪等性

- POST: 建立一個資源, 非冪等方法

- PUT: 完整的替換資源或者建立資源, 是冪等方法

- DELETE: 刪除資源, 是冪等方法

- OPTIONS:獲取資源所支援的所有HTTP方法

- PATCH: 區域性更新, 非冪等方法
複製程式碼

HTTP冪等方法,是指無論呼叫多少次都不會有不同結果的 HTTP 方法。不管你呼叫一次,還是呼叫一百次,一千次,結果都是相同的。

關於如何理解 RESTful 的冪等性

在檢視函式裡面我們可以使用如下方式判斷HTTP請求方法

from flask import Flask, request

request.method # 獲取當前請求的方法
複製程式碼

靜態檔案

動態的 web 應用也需要靜態檔案,一般是 CSS 和 JavaScript 檔案。理想情況下你的 伺服器已經配置好了為你的提供靜態檔案的服務。但是在開發過程中, Flask 也能做好 這項工作。只要在你的包或模組旁邊建立一個名為 static 的資料夾就行了。 靜態檔案位於應用的 /static 中。

在例項化Flask時候如果你檢視原始碼你會發現
app = Flask(__name__)


def __init__(
        self,
        import_name,
        static_url_path=None,
        static_folder='static',
        static_host=None,
        host_matching=False,
        subdomain_matching=False,
        template_folder='templates',
        instance_path=None,
        instance_relative_config=False,
        root_path=None
    ):
複製程式碼

static為預設的靜態檔案的目錄。

# 這樣是可以直接訪問到靜態檔案的

http://0.0.0.0:9999/static/app.css
複製程式碼

我們不建議在模板裡面直接寫死靜態檔案路徑, 應該使用url_for()生成路徑

url_for('static', filename='app.css')
複製程式碼

當然我們也可以定製靜態檔案的真實目錄

app = Flask(__name__, static_folder='/tmp')
複製程式碼

渲染模板

Flask預設提供的是Jinja2 模板引擎。

使用 render_template() 方法可以渲染模板,你只要提供模板名稱和需要 作為引數傳遞給模板的變數就行了。下面是一個簡單的模板渲染例子:

templates是Flask預設的目錄名字。

@app.route('/')
def index():
    return render_template('index.html', title='首頁')
複製程式碼
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{title}}</title>
</head>
<body>
</body>
</html>
複製程式碼

在模板內部可以和訪問 get_flashed_messages() 函式一樣訪問 request 、 session 和 g物件[g儲存的是當前請求的全域性變數,不同的請求會有不同的全域性變數]。

Jinja2文件這裡就不細說了, 後面有空單獨寫一篇, 現在前後分離, 已經比較少使用模板了。

操作request物件

在Flask 中由全域性物件request來提供請求資訊。如果你有一些 Python 基礎,那麼 可能 會奇怪:既然這個物件是全域性的,怎麼還能保持執行緒安全?

from flask import request
複製程式碼

某些物件在 Flask 中是全域性物件,但不是通常意義下的全域性物件。這些物件實際上是 特定環境下本地物件的代理。真拗口!但還是很容易理解的。

從一個 Flask App 讀入配置並啟動開始,就進入了 App Context,在其中我們可以訪問配置檔案、開啟資原始檔、通過路由規則反向構造 URL。當一個請求進入開始被處理時,就進入了 Request Context,在其中我們可以訪問請求攜帶的資訊,比如 HTTP Method、表單域等。

request雖然是全域性變數但是隻是一個代理, 想深挖細節的可以看看這個文章Flask的Context機制

理解Flask的request上下文

- 通過使用 method 屬性可以操作當前請求方法

- 通過使用 form 屬性處理表單資料(在 POST 或者 PUT 請求 中傳輸的資料), 當 form 屬性中不存在這個鍵時會發生什麼?會引發一個 KeyError 。 
如果你不像捕捉一個標準錯誤一樣捕捉 KeyError ,那麼會顯示一個 HTTP 400 Bad Request 錯誤頁面。

- 要操作 URL (如 ?key=value )中提交的引數可以使用 args 屬性:
searchword = request.args.get('key', '')
複製程式碼

響應

檢視函式的返回值會被轉為一個響應物件。

比如:

# 你會發現頁面裡面沒有文字顯示, 只有h3標籤
@app.route('/')
def index():
    return '<h3></h3>'

複製程式碼

你檢視app.route()原始碼會發現

# route裝飾器只是一個語法糖, 實際執行的還是add_url_rule()
def decorator(f):
    endpoint = options.pop('endpoint', None)
    self.add_url_rule(rule, endpoint, f, **options)
    return f
return decorator
複製程式碼

轉化邏輯如下:

1. 如果返回的是一個合法的響應物件, 它會從檢視直接返回
2. 如果返回的是一個字串, 會用字串資料和預設引數來建立以字串為主體, 狀態碼為200,MIME型別的text.html的werkzeug.wrappers.Response響應物件。

3. 如果返回的是一個元組, 且元組的元素可以提供額外的資訊, 這樣的元組格式為(response, status, headers)。它們會覆蓋預設的配置。

4. 如果上述條件都不對, Flask會假設返回值是一個合法的WSGI應用程式, 並通過Response.force_type(rv, request.environ)轉化為請求物件
複製程式碼

例項:

@app.route('/')
def index():
    return render_template('index.html'), 400
複製程式碼

我們也可以顯示使用make_response方法

from flask import Flask, url_for, render_template, redirect, request, make_response
app = Flask(__name__)

app.config['DEBUG'] = True

@app.route('/')
def index():
    response = make_response(render_template('index.html'), 400)
    return response

複製程式碼

使用make_response方法我們可以設定cookie, 頭資訊等。

檢視make_response方法原始碼其實也很好理解。

def make_response(*args):
    # 根據args傳參情況進行不同處理
    if not args:
        return current_app.response_class()
    if len(args) == 1:
        args = args[0]
    return current_app.make_response(args)
複製程式碼
def make_response(self, rv):
    status = headers = None
    # unpack tuple returns
    # 傳入的是元組
    if isinstance(rv, tuple):
        len_rv = len(rv)
        # 格式(response, status, headers)
        if len_rv == 3:
            rv, status, headers = rv
        # decide if a 2-tuple has status or headers
        elif len_rv == 2:
            if isinstance(rv[1], (Headers, dict, tuple, list)):
                rv, headers = rv
            else:
                rv, status = rv
        # other sized tuples are not allowed
        else:
            raise TypeError(
                'The view function did not return a valid response tuple.'
                ' The tuple must have the form (body, status, headers),'
                ' (body, status), or (body, headers).'
            )

    # the body must not be None
    if rv is None:
        raise TypeError(
            'The view function did not return a valid response. The'
            ' function either returned None or ended without a return'
            ' statement.'
        )

    # make sure the body is an instance of the response class
    if not isinstance(rv, self.response_class):
        if isinstance(rv, (text_type, bytes, bytearray)):
            # let the response class set the status and headers instead of
            # waiting to do it manually, so that the class can handle any
            # special logic
            rv = self.response_class(rv, status=status, headers=headers)
            status = headers = None
        else:
            # evaluate a WSGI callable, or coerce a different response
            # class to the correct type
            try:
                rv = self.response_class.force_type(rv, request.environ)
            except TypeError as e:
                new_error = TypeError(
                    '{e}\nThe view function did not return a valid'
                    ' response. The return type must be a string, tuple,'
                    ' Response instance, or WSGI callable, but it was a'
                    ' {rv.__class__.__name__}.'.format(e=e, rv=rv)
                )
                reraise(TypeError, new_error, sys.exc_info()[2])

    # prefer the status if it was provided
    if status is not None:
        if isinstance(status, (text_type, bytes, bytearray)):
            rv.status = status
        else:
            rv.status_code = status

    # extend existing headers with provided headers
    if headers:
        rv.headers.extend(headers)

    return rv
複製程式碼

相關文章