返回文章列表

Requests 與 Werkzeug 技術解析

本文深入解析 Python 的 Requests 函式庫中 Cookie 建立、狀態碼處理機制,以及 Werkzeug 的 WSGI 工具箱的設計與實作。涵蓋 Cookie 建立的靈活性、狀態碼查詢字典的應用、Werkzeug 的路由系統、WSGI 應用程式建立與測試等導向,並搭配程式碼範例說明,有助於理解

Web 開發 Python

Python 的 Requests 函式庫簡化了 HTTP 請求的處理流程,其靈活的 Cookie 建立機制允許開發者透過關鍵字引數自定義 Cookie 屬性。狀態碼查詢字典則提供可讀性更高的狀態碼存取方式,提升程式碼維護性。Werkzeug 作為 WSGI 工具箱,提供 URL 路由、HTTP 標頭處理、請求/回應物件等功能,簡化 WSGI 應用程式開發。其路由系統根據 MapRule 兩個核心類別,能有效管理和匹配路由規則。此外,Werkzeug 還支援自定義轉換器,可處理特定格式的 URL 引數,並透過 Tox 進行跨 Python 版本的測試,確保程式碼的相容性。

在 Requests 函式庫中,create_cookie() 函式負責建立 Cookie 物件。該函式透過 **kwargs 允許使用者提供任意或不提供關鍵字選項來建立 Cookie。以下為具體實作:

result = dict(
    version=0,
    name=name,
    value=value,
    port=None,
    domain='',
    path='/',
    secure=False,
    expires=None,
    discard=True,
    comment=None,
    comment_url=None,
    rest={'HttpOnly': None},
    rfc2109=False,
)

badargs = set(kwargs) - set(result)
if badargs:
    err = 'create_cookie() got unexpected keyword arguments: %s'
    raise TypeError(err % list(badargs))

result.update(kwargs)
result['port_specified'] = bool(result['port'])
result['domain_specified'] = bool(result['domain'])
result['domain_initial_dot'] = result['domain'].startswith('.')
result['path_specified'] = bool(result['path'])

return cookielib.Cookie(**result)

內容解密:

  1. result 字典初始化:預設建立一個包含 Cookie 屬性的字典,涵蓋大部分常見的 Cookie 屬性。
  2. 錯誤處理:檢查 kwargs 中是否有未預期的關鍵字引數,若有則丟出 TypeError 例外。
  3. result.update(kwargs):將使用者提供的額外引數更新至 result 字典中,既可替換現有屬性,也可新增屬性。
  4. 布林值轉換:將特定屬性(如 portdomainpath)轉換為布林值,以表示是否已指定這些屬性。
  5. 建立 cookielib.Cookie 物件:利用更新後的 result 字典,以關鍵字引數的形式建立並傳回 cookielib.Cookie 物件。

狀態碼查詢字典的設計與應用

Requests 函式庫中的 status_codes.py 檔案定義了一個狀態碼查詢字典 _codes,用於將 HTTP 狀態碼對映到可讀的屬性名稱。以下是範例程式碼:

_codes = {
    # 資訊回應
    100: ('continue',),
    101: ('switching_protocols',),
    # ...
    200: ('ok', 'okay', 'all_ok', 'all_okay', 'all_good', '\\o/', ''),
    # ...
}

codes = LookupDict(name='status_codes')
for code, titles in _codes.items():
    for title in titles:
        setattr(codes, title, code)
        if not title.startswith('\\'):
            setattr(codes, title.upper(), code)

內容解密:

  1. _codes 字典定義:將 HTTP 狀態碼對映到多個屬性名稱,例如將 200 對映到 ('ok', 'okay', ...)
  2. LookupDict 物件建立:建立一個名為 codesLookupDict 物件,用於儲存狀態碼及其對應的屬性名稱。
  3. 動態屬性設定:透過迴圈遍歷 _codes,為 codes 物件動態設定屬性,使得狀態碼可以透過可讀的屬性名稱存取。
  4. 大小寫支援:除了設定原始屬性名稱外,還為不包含特殊字元的屬性名稱設定大寫版本,以增強靈活性。

狀態碼在重定向處理中的應用

sessions.py 中,狀態碼查詢字典被用於處理 HTTP 重定向。範例如下:

from .status_codes import codes

class SessionRedirectMixin(object):
    def resolve_redirects(self, resp, req, stream=False, timeout=None,
                          verify=True, cert=None, proxies=None,
                          **adapter_kwargs):
        # ...
        if resp.status_code == codes.see_other and method != 'HEAD':
            method = 'GET'
        if resp.status_code == codes.found and method != 'HEAD':
            method = 'GET'
        if resp.status_code == codes.moved and method == 'POST':
            method = 'GET'
        # ...

內容解密:

  1. 狀態碼查詢:使用 codes.see_othercodes.foundcodes.moved 等可讀屬性名稱來檢查 HTTP 狀態碼,使程式碼更具可讀性。
  2. 重定向邏輯處理:根據不同的狀態碼和請求方法,調整請求方法(如將 POST 改為 GET),以符合瀏覽器的行為標準。
  3. 程式碼可讀性提升:相較於直接使用數值狀態碼,使用具描述性的屬性名稱(如 codes.see_other 代表 303)能大幅提升程式碼的可讀性和維護性。

Werkzeug:WSGI工具箱的設計與實作

Werkzeug是一個用於建立WSGI應用程式和中介軟體元件的Python函式庫。它提供了許多實用的工具和類別,使得開發WSGI應用程式變得更加容易。

WSGI簡介

WSGI(Web Server Gateway Interface)是Python應用程式與Web伺服器之間的介面標準。它定義了伺服器如何呼叫應用程式,以及應用程式如何回傳回應給伺服器。WSGI標準在PEP 333中定義,並在PEP 3333中更新以支援Python 3。

WSGI的主要特點

  1. 伺服器呼叫應用程式:伺服器會為每個HTTP請求呼叫一次應用程式。
  2. 應用程式回傳回應:應用程式會回傳一個可迭代的位元組字串,伺服器會使用它來回應HTTP請求。
  3. 應用程式引數:應用程式會接收兩個引數:environstart_responseenviron包含了請求的相關資料,而start_response是一個可呼叫的物件,用於傳送標頭和狀態資訊給伺服器。

Werkzeug的設計與實作

Werkzeug由Armin Ronacher在2007年釋出,旨在滿足對WSGI函式庫的需求。Werkzeug提供了許多實用的工具和類別,包括:

  • WSGI 1.0實作:Werkzeug實作了WSGI 1.0標準(PEP 333)。
  • URL路由系統:Werkzeug提供了URL路由系統,使得處理HTTP請求變得更加容易。
  • HTTP標頭解析和轉儲:Werkzeug可以解析和轉儲HTTP標頭。
  • HTTP請求和回應物件:Werkzeug提供了代表HTTP請求和回應的物件。
  • 工作階段和Cookie支援:Werkzeug支援工作階段和Cookie。
  • 檔案上傳:Werkzeug支援檔案上傳。

使用Werkzeug

要使用Werkzeug,我們可以從一個簡單的WSGI應用程式開始:

def wsgi_app(environ, start_response):
    headers = [('Content-type', 'text/plain'), ('charset', 'utf-8')]
    start_response('200 OK', headers)
    yield 'Hello world.'

內容解密:

此段程式碼定義了一個簡單的WSGI應用程式。它接收environstart_response兩個引數,並使用start_response傳送標頭和狀態資訊給伺服器。然後,它回傳一個可迭代的位元組字串,包含字串"Hello world."。

我們可以使用Werkzeug的Response類別來簡化這個過程:

response_app = werkzeug.Response('Hello world!')

內容解密:

此段程式碼使用Werkzeug的Response類別建立了一個WSGI應用程式。它等同於前面的例子,但更加簡潔。

Werkzeug還提供了Client類別,用於測試WSGI應用程式:

client = werkzeug.Client(wsgi_app, response_wrapper=werkzeug.Response)
resp = client.get("?answer=42")
print(resp.data.decode())

內容解密:

此段程式碼使用Werkzeug的Client類別建立了一個測試客戶端。它呼叫了前面定義的WSGI應用程式,並列印出回應的內容。

Werkzeug的優勢

Werkzeug提供了許多實用的工具和類別,使得開發WSGI應用程式變得更加容易。它的優勢包括:

  • 簡化WSGI應用程式開發:Werkzeug提供了許多實用的工具和類別,簡化了WSGI應用程式的開發。
  • 提高開發效率:Werkzeug的工具和類別可以提高開發效率,減少開發時間。

Werkzeug 路由系統深度解析

Werkzeug 是 Python 中一個強大的 Web 開發工具函式庫,其路由系統是其核心功能之一。本文將探討 Werkzeug 的路由機制,並透過例項演示其用法。

路由基礎

Werkzeug 的路由系統根據 MapRule 兩個主要類別。Map 用於管理多個路由規則,而 Rule 則定義了單一路由規則。

from werkzeug.routing import Map, Rule

url_map = Map([
    Rule('/', endpoint='index'),
    Rule('/<any("Robin","Galahad","Arthur"):person>', endpoint='ask'),
    Rule('/<other>', endpoint='other')
])

內容解密:

  1. Map 類別用於建立路由表,管理多個 Rule 例項。
  2. Rule 類別定義單一路由規則,包括路徑模式和對應的端點(endpoint)。
  3. <any("Robin","Galahad","Arthur"):person> 是一種特殊的路徑引數,限制 person 只能是 “Robin”、“Galahad” 或 “Arthur” 三者之一。
  4. endpoint 是一個字串,用於標識路由對應的處理函式或檢視。

路由匹配

當請求到達時,Werkzeug 會使用 Map 例項來匹配對應的路由規則。

env = werkzeug.create_environ(path='/Galahad')
urls = url_map.bind_to_environ(env)
endpoint, kwargs = urls.match()

內容解密:

  1. create_environ 函式建立一個模擬的 WSGI 環境。
  2. bind_to_environ 方法將 Map 例項繫結到特定的 WSGI 環境,建立一個 MapAdapter 例項。
  3. match 方法嘗試匹配路由規則,如果成功傳回 endpoint 和引數字典;否則丟擲 NotFound 異常。

路由應用

將路由機制應用於實際的 Web 應用中,需要將請求分派到對應的端點處理函式。

@werkzeug.Request.application
def send_to_endpoint(request):
    urls = url_map.bind_to_environ(request.environ)
    try:
        endpoint, kwargs = urls.match()
        if endpoint == 'index':
            return werkzeug.Response("You got the index.")
        elif endpoint == 'ask':
            questions = {
                'Galahad': 'What is your favorite color?',
                'Robin': 'What is the capital of Assyria?',
                'Arthur': 'What is the air-speed velocity of an unladen swallow?'
            }
            return werkzeug.Response(questions[kwargs['person']])
        else:
            return werkzeug.Response(f"Other: {kwargs['other']}")
    except Exception as e:
        return werkzeug.Response('You may not have gone where you intended to go,\nbut I think you have ended up where you needed to be.', status=404)

內容解密:

  1. 使用 @werkzeug.Request.application 裝飾器將 send_to_endpoint 函式轉換為 WSGI 應用。
  2. 在函式內部,將請求環境繫結到 url_map,並嘗試匹配路由。
  3. 根據匹配結果,分派到不同的處理邏輯,並傳回相應的回應。
  4. 使用 try-except 塊捕捉異常,傳回自定義的 404 回應。

測試路由應用

使用 werkzeug.Client 可以方便地測試 WSGI 應用。

client = werkzeug.Client(send_to_endpoint, response_wrapper=werkzeug.Response)
print(client.get("/").data.decode())
print(client.get("/Arthur").data.decode())
print(client.get("/42").data.decode())

內容解密:

  1. werkzeug.Client 用於模擬客戶端請求。
  2. get 方法傳送 GET 請求到指定的路徑。
  3. data.decode() 用於取得回應內容並解碼為字串。

Werkzeug 原始碼閱讀與技術解析

Werkzeug 是一個功能強大的 Python 函式庫,主要用於 Web 應用程式的開發。透過閱讀其原始碼,可以深入瞭解其內部實作機制和設計理念。

測試案例與功能展示

Werkzeug 的測試案例展示了其豐富的功能和靈活性。例如,在 test_wrappers.py 中,測試了 Request 物件的各種功能,包括 cookies、編碼、認證、安全性和快取逾時等。

def test_modified_url_encoding():
    class ModifiedRequest(wrappers.Request):
        url_charset = 'euc-kr'
    req = ModifiedRequest.from_values(u'/?foo=정상처리'.encode('euc-kr'))
    strict_eq(req.args['foo'], u'정상처리')

內容解密:

此測試案例展示瞭如何自定義 Request 物件的 URL 編碼方式。在這個例子中,將 url_charset 設定為 ’euc-kr’,並測試了是否能夠正確解析韓文字元。測試結果驗證了 Werkzeug 在處理不同編碼時的靈活性。

路由模組的測試

Werkzeug 的路由模組提供了強大的 URL 路由功能。測試案例展示瞭如何使用非 ASCII 字元進行路由匹配。

def test_environ_nonascii_pathinfo():
    environ = create_environ(u'/лошадь')
    m = r.Map([
        r.Rule(u'/', endpoint='index'),
        r.Rule(u'/лошадь', endpoint='horse')
    ])
    a = m.bind_to_environ(environ)
    strict_eq(a.match(u'/'), ('index', {}))
    strict_eq(a.match(u'/лошадь'), ('horse', {}))
    pytest.raises(r.NotFound, a.match, u'/барсук')

內容解密:

此測試案例驗證了 Werkzeug 在處理非 ASCII 字元的 URL 時的正確性。首先,建立了一個包含非 ASCII 字元的 WSGI 環境變數。然後,定義了一個路由對映,包含兩個路由規則。接著,將路由對映繫結到環境變數,並進行路由匹配測試。測試結果表明,Werkzeug 能夠正確匹配包含非 ASCII 字元的 URL。

自定義轉換器

Werkzeug 允許開發者自定義轉換器,以處理特定的 URL 引數。

class TwoValueConverter(r.BaseConverter):
    def __init__(self, *args, **kwargs):
        super(TwoValueConverter, self).__init__(*args, **kwargs)
        self.regex = r'(\w\w+)/(\w\w+)'
    def to_python(self, two_values):
        one, two = two_values.split('/')
        return one, two
    def to_url(self, values):
        return "%s/%s" % (values[0], values[1])

內容解密:

此自定義轉換器 TwoValueConverter 用於處理形如 ‘xx/xx’ 的 URL 引數。它繼承自 r.BaseConverter,並重寫了 __init__to_pythonto_url 方法。在 __init__ 方法中,設定了正規表示式以匹配 ‘xx/xx’ 的格式。在 to_python 方法中,將匹配到的字串分割成兩個部分,並傳回一個元組。在 to_url 方法中,將輸入的元組格式化成 ‘xx/xx’ 的字串。

Tox 組態檔案解析

Werkzeug 使用 Tox 進行跨多個 Python 版本的測試。其 tox.ini 組態檔案定義了測試環境和依賴項。

[tox]
envlist = py{26,27,py,33,34,35}-normal, py{26,27,33,34,35}-uwsgi
[testenv]
passenv = LANG
deps=
    # General
    pyopenssl
    greenlet
    pytest
    pytest-xprocess
    redis
    requests
    watchdog
    uwsgi: uwsgi

內容解密:

此組態檔案指定了測試環境的列表,包括多個 Python 版本。passenv 用於傳遞環境變數 LANG 到測試環境中。deps 部分列出了測試所需的依賴項,包括通用依賴和特定 Python 版本的依賴。這些依賴項確保了測試能夠在不同的環境中正確執行。