-- coding: utf-8 --
"""
Flask-Origin
~~~~~~~~~~~~~
Flask 0.1版本源碼注解。
:copyright: (c) 2018 Grey Li
:license: MIT, see LICENSE for more details.
"""
from future import with_statement
import os
import sys
from jinja2 import Environment, PackageLoader, FileSystemLoader
from werkzeug import Request as RequestBase, Response as ResponseBase,
LocalStack, LocalProxy, create_environ, SharedDataMiddleware
from werkzeug.routing import Map, Rule
from werkzeug.exceptions import HTTPException
from werkzeug.contrib.securecookie import SecureCookie
這些從Werkzeug和Jinja2導(dǎo)入的輔助函數(shù)(utilities)沒有在
模塊內(nèi)使用,而是直接作為外部接口開放
from werkzeug import abort, redirect
from jinja2 import Markup, escape
優(yōu)先使用pkg_resource哗蜈,如果無法工作則使用cwd闻牡。
try:
import pkg_resources
pkg_resources.resource_stream
except (ImportError, AttributeError):
pkg_resources = None
class Request(RequestBase):
"""Flask默認(rèn)使用的請(qǐng)求對(duì)象孵稽,用來記住匹配的端點(diǎn)值(endpoint)和視圖參數(shù)(view arguments)嗡善。
這就是最終的flask.request對(duì)象篡腌。如果你想替換掉這個(gè)請(qǐng)求對(duì)象碱璃,可以子類化這個(gè)
類弄痹,然后將你的子類賦值給flask.Flask.request_class。
"""
def __init__(self, environ):
RequestBase.__init__(self, environ)
self.endpoint = None # 當(dāng)前請(qǐng)求的端點(diǎn)
self.view_args = None # 當(dāng)前請(qǐng)求的視圖參數(shù)嵌器,會(huì)作為關(guān)鍵字參數(shù)傳入視圖函數(shù)
class Response(ResponseBase):
"""Flask默認(rèn)使用的響應(yīng)對(duì)象肛真。除了將MIME類型默認(rèn)設(shè)置為HTML外,和Werkzeug提供的響應(yīng)對(duì)象
完全相同爽航。通常情況下蚓让,你不需要自己創(chuàng)建這個(gè)對(duì)象,因?yàn)閒lask.Flask.make_response
會(huì)負(fù)責(zé)這個(gè)工作讥珍。
如果你想替換這個(gè)響應(yīng)對(duì)象历极,你可以子類化這個(gè)類,然后將你的子類賦值給flask.Flask.response_class串述。
"""
default_mimetype = 'text/html'
class _RequestGlobals(object):
pass
class _RequestContext(object):
"""請(qǐng)求上下文(request context)包含所有請(qǐng)求相關(guān)的信息执解。它會(huì)在請(qǐng)求進(jìn)入時(shí)被創(chuàng)建,
然后被推送到_request_ctx_stack,在請(qǐng)求結(jié)束時(shí)會(huì)被相應(yīng)的移除衰腌。它會(huì)為提供的
WSGI環(huán)境創(chuàng)建URL適配器(adapter)和請(qǐng)求對(duì)象新蟆。
"""
# 會(huì)在flask.Flask.request_context和flask.Flask.test_requset_context方法中
# 調(diào)用,以便生成請(qǐng)求上下文右蕊。
def init(self, app, environ):
self.app = app
self.url_adapter = app.url_map.bind_to_environ(environ) # 綁定了當(dāng)前環(huán)境信息琼稻,用于構(gòu)建URL,在url_for函數(shù)中使用
self.request = app.request_class(environ) # 創(chuàng)建請(qǐng)求對(duì)象饶囚,包含請(qǐng)求信息
self.session = app.open_session(self.request) # 創(chuàng)建session對(duì)象帕翻,用于存儲(chǔ)用戶會(huì)話數(shù)據(jù)到cookie中
self.g = _RequestGlobals() # 創(chuàng)建g對(duì)象,用于在當(dāng)前請(qǐng)求存儲(chǔ)全局變量
self.flashes = None # 存儲(chǔ)當(dāng)前請(qǐng)求的通過flash函數(shù)發(fā)送的消息
def __enter__(self):
_request_ctx_stack.push(self) # 將當(dāng)前請(qǐng)求上下文對(duì)象推送到_request_ctx_stack堆棧萝风,這個(gè)堆棧在最后定義
def __exit__(self, exc_type, exc_value, tb):
# 在調(diào)試模式(debug mode)而且有異常發(fā)生時(shí)嘀掸,不要移除(pop)請(qǐng)求堆棧。
# 這將允許調(diào)試器(debugger)在交互式shell中仍然可以獲取請(qǐng)求對(duì)象规惰。
if tb is None or not self.app.debug:
_request_ctx_stack.pop()
def url_for(endpoint, **values):
"""根據(jù)給定的端點(diǎn)和提供的方法生成一個(gè)URL睬塌。
對(duì)于目標(biāo)端點(diǎn)未知的變量參數(shù),將會(huì)作為查詢參數(shù)附加在URL后面(生成查詢字符串)歇万。
:param endpoint: URL的端點(diǎn)值(函數(shù)名)揩晴。
:param values: URL規(guī)則的變量參數(shù)。
"""
return _request_ctx_stack.top.url_adapter.build(endpoint, values) # 這里堆棧的棧頂(top)即上面的請(qǐng)求上下文對(duì)象實(shí)例
def flash(message):
"""閃現(xiàn)(flash)一個(gè)消息到下一個(gè)請(qǐng)求贪磺。為了從session中移除閃現(xiàn)過的消息
并將其顯示給用戶硫兰,你必須在模板中調(diào)用get_flashed_messages。
:param message: 被閃現(xiàn)的消息寒锚。
"""
session['_flashes'] = (session.get('_flashes', [])) + [message]
def get_flashed_messages():
"""從session里拉冉儆场(pull)所有要閃現(xiàn)的消息并返回它們。在同一個(gè)請(qǐng)求中對(duì)這個(gè)函數(shù)的
進(jìn)一步調(diào)用會(huì)返回同樣的消息刹前。
"""
flashes = _request_ctx_stack.top.flashes
if flashes is None:
_request_ctx_stack.top.flashes = flashes =
session.pop('_flashes', [])
return flashes
def render_template(template_name, **context):
"""使用給定的上下文從模板(template)文件夾渲染一個(gè)模板苏研。
:param template_name: 要被渲染的模板文件名。
:param context: 在模板上下文中應(yīng)該可用(available)的變量腮郊。
"""
current_app.update_template_context(context)
return current_app.jinja_env.get_template(template_name).render(context)
def render_template_string(source, **context):
"""使用給定的模板源代碼字符串(source string)和上下文渲染一個(gè)模板。
:param template_name: 要被渲染的模板源代碼筹燕。
:param context: 在模板上下文中應(yīng)該可用的變量轧飞。
"""
current_app.update_template_context(context)
return current_app.jinja_env.from_string(source).render(context)
def _default_template_ctx_processor():
"""默認(rèn)的模板上下文處理器(processor)。注入request撒踪、session和g过咬。"""
# 把request、session和g注入到模板上下文制妄,以便可以直接在模板中使用這些變量掸绞。
reqctx = _request_ctx_stack.top
return dict(
request=reqctx.request,
session=reqctx.session,
g=reqctx.g
)
def _get_package_path(name):
"""返回包的路徑,如果找不到則返回當(dāng)前工作目錄(cwd)。"""
try:
return os.path.abspath(os.path.dirname(sys.modules[name].file))
except (KeyError, AttributeError):
return os.getcwd()
class Flask(object):
"""這個(gè)flask對(duì)象實(shí)現(xiàn)了WSGI程序并作為中心對(duì)象存在衔掸。傳入的參數(shù)(package_name)為
程序所在的模塊或包的名稱烫幕。一旦這個(gè)對(duì)象被創(chuàng)建,它將作為一個(gè)中心注冊處敞映,所有的視圖
函數(shù)较曼、URL規(guī)則、模板配置等等都將注冊到這里振愿。
包的名稱被用來從包的內(nèi)部或模塊所在的文件夾解析資源捷犹,具體的位置取決于傳入的包名稱
參數(shù)(package_name)指向一個(gè)真實(shí)的Python包(包含init.py文件的文件夾)
還是一個(gè)標(biāo)準(zhǔn)的模塊(.py文件)。
關(guān)于資源加載的更多信息冕末,參見open_resource萍歉。
通常,你會(huì)在你的主腳本或包中的init.py文件里使用下面的方式創(chuàng)建一個(gè)Flask實(shí)例:
from flask import Flask
app = Flask(name)
"""
#: 用作請(qǐng)求對(duì)象的類档桃。更多信息參見flask.Request枪孩。
request_class = Request
#: 用作響應(yīng)對(duì)象的類蜂桶。更多信息參見flask.Response昂儒。
response_class = Response
#: 靜態(tài)文件的路徑友扰。如果你不想使用靜態(tài)文件腮出,可以將這個(gè)值設(shè)為None用僧,這樣不會(huì)添加
#: 相應(yīng)的URL規(guī)則贿堰,而且開發(fā)服務(wù)器將不再提供(serve)任何靜態(tài)文件愚战。
static_path = '/static'
#: 如果設(shè)置了密鑰(secret key)宗兼,加密組件可以使用它來為
#: cookies或其他東西簽名抚垄。比如蜕窿,當(dāng)你想使用安全的cookie時(shí),把它設(shè)為一個(gè)復(fù)雜的隨機(jī)值呆馁。
secret_key = None
#: 安全cookie使用這個(gè)值作為session cookie的名稱桐经。
session_cookie_name = 'session' # 存儲(chǔ)session對(duì)象數(shù)據(jù)的cookie名稱
#: 直接傳入Jinja2環(huán)境的選項(xiàng)。
jinja_options = dict(
autoescape=True, # 默認(rèn)開啟自動(dòng)轉(zhuǎn)義浙滤,即轉(zhuǎn)義不安全字符為HTML實(shí)體阴挣,比如“>”、“<”等纺腊。
extensions=['jinja2.ext.autoescape', 'jinja2.ext.with_']
)
def __init__(self, package_name):
#: 調(diào)試標(biāo)志畔咧。將它設(shè)為True來開啟調(diào)試模式。在調(diào)試模式下揖膜,當(dāng)一個(gè)未捕捉
#: 的異常觸發(fā)時(shí)誓沸,調(diào)試器會(huì)啟動(dòng);而且壹粟,當(dāng)代碼中的變動(dòng)被探測到時(shí)拜隧,開發(fā)
#: 服務(wù)器會(huì)自動(dòng)重載程序。
self.debug = False
#: 包或模塊的名稱。一旦它通過構(gòu)造器設(shè)置后洪添,就不要更改這個(gè)值垦页。
self.package_name = package_name
#: 定位程序的根目錄。
self.root_path = _get_package_path(self.package_name)
###################################
# 下面是幾個(gè)存儲(chǔ)回調(diào)函數(shù)的字典或列表
###################################
#: 一個(gè)儲(chǔ)存所有已注冊的視圖函數(shù)的字典薇组。字典的鍵將是函數(shù)的名稱外臂,這些名稱
#: 也被用來生成URL;字典的值是函數(shù)對(duì)象本身律胀。
#: 要注冊一個(gè)視圖函數(shù)宋光,使用route裝飾器(decorator)。
self.view_functions = {}
#: 一個(gè)儲(chǔ)存所有已注冊的錯(cuò)誤處理器的字典炭菌。字段的鍵是整型(integer)類型的
#: 錯(cuò)誤碼罪佳,字典的值是處理對(duì)應(yīng)錯(cuò)誤的函數(shù)。
#: 要注冊一個(gè)錯(cuò)誤處理器黑低,使用errorhandler裝飾器赘艳。
self.error_handlers = {}
#: 一個(gè)應(yīng)該在請(qǐng)求開始進(jìn)入時(shí)、請(qǐng)求分發(fā)開始前調(diào)用的函數(shù)列表克握。舉例來說蕾管,
#: 這可以用來打開數(shù)據(jù)庫連接或獲取當(dāng)前登錄的用戶。
#: 要注冊一個(gè)函數(shù)到這里菩暗,使用before_request裝飾器掰曾。
self.before_request_funcs = []
#: 一個(gè)應(yīng)該在請(qǐng)求處理結(jié)束時(shí)調(diào)用的函數(shù)列表。這些函數(shù)會(huì)被傳入當(dāng)前的響應(yīng)
#: 對(duì)象停团,你可以在函數(shù)內(nèi)修改或替換它旷坦。
#: 要注冊一個(gè)函數(shù)到這里,使用after_request裝飾器佑稠。
self.after_request_funcs = []
#: 一個(gè)將被無參數(shù)調(diào)用以生成模板上下文的的函數(shù)列表秒梅。每一個(gè)函數(shù)應(yīng)返回一個(gè)
#: 用于更新模板上下文的字典。
#: 要注冊一個(gè)函數(shù)到這里舌胶,使用context_processor裝飾器捆蜀。
self.template_context_processors = [_default_template_ctx_processor] # 默認(rèn)的處理器用來注入session、request和g
self.url_map = Map()
if self.static_path is not None:
self.url_map.add(Rule(self.static_path + '/<filename>',
build_only=True, endpoint='static'))
if pkg_resources is not None:
target = (self.package_name, 'static')
else:
target = os.path.join(self.root_path, 'static')
self.wsgi_app = SharedDataMiddleware(self.wsgi_app, { # SharedDataMiddleware中間件用來為程序添加處理靜態(tài)文件的能力
self.static_path: target # URL路徑和實(shí)際文件目錄(static文件夾)的映射
})
#: Jinja2環(huán)境幔嫂。它通過jinja_options創(chuàng)建漱办,加載器(loader)通過
#: create_jinja_loader函數(shù)返回。
self.jinja_env = Environment(loader=self.create_jinja_loader(),
**self.jinja_options)
self.jinja_env.globals.update( # 將url_for和get_flashed_messages函數(shù)作為全局對(duì)象注入到模板上下文婉烟,以便在模板中調(diào)用
url_for=url_for,
get_flashed_messages=get_flashed_messages
)
def create_jinja_loader(self):
"""創(chuàng)建Jinja加載器。默認(rèn)只是返回一個(gè)對(duì)應(yīng)配置好的包的包加載器暇屋,它會(huì)從
templates文件夾中尋找模板似袁。要添加其他加載器,可以重載這個(gè)方法。
"""
if pkg_resources is None:
return FileSystemLoader(os.path.join(self.root_path, 'templates'))
return PackageLoader(self.package_name)
def update_template_context(self, context):
"""使用常用的變量更新模板上下文昙衅。這會(huì)注入request扬霜、session和g到模板上下文中。
:param context: 包含額外添加的變量的字典而涉,用來更新上下文著瓶。
"""
reqctx = _request_ctx_stack.top
for func in self.template_context_processors: # 調(diào)用所有使用context_processor裝飾器注冊的模板上下文處理函數(shù),更新模板上下文
context.update(func())
def run(self, host='localhost', port=5000, **options):
"""在本地開發(fā)服務(wù)器上運(yùn)行程序啼县。如果debug標(biāo)志被設(shè)置材原,這個(gè)服務(wù)器
會(huì)在代碼更改時(shí)自動(dòng)重載,并會(huì)在異常發(fā)生時(shí)顯示一個(gè)調(diào)試器季眷。
:param host: 監(jiān)聽的主機(jī)名余蟹。設(shè)為'0.0.0.0'可以讓服務(wù)器外部可見。
:param port: 服務(wù)器的端口子刮。
:param options: 這些選項(xiàng)將被轉(zhuǎn)發(fā)給底層的Werkzeug服務(wù)器威酒。更多信息
參見werkzeug.run_simple。
"""
from werkzeug import run_simple
if 'debug' in options:
self.debug = options.pop('debug')
options.setdefault('use_reloader', self.debug) # 如果debug為True挺峡,開啟重載器(reloader)
options.setdefault('use_debugger', self.debug) # 如果debug為True葵孤,開啟調(diào)試器(debugger)
return run_simple(host, port, self, **options)
def test_client(self):
"""為這個(gè)程序創(chuàng)建一個(gè)測試客戶端。"""
from werkzeug import Client
return Client(self, self.response_class, use_cookies=True)
def open_resource(self, resource):
"""從程序的資源文件夾打開一個(gè)資源橱赠。至于它是如何工作的尤仍,考慮下面的文件
目錄:
/myapplication.py
/schemal.sql
/static
/style.css
/templates
/layout.html
/index.html
如果你想打開schema.sql文件,可以這樣做:
with app.open_resource('schema.sql') as f:
contents = f.read()
do_something_with(contents)
:param resource: 資源文件的名稱病线。要獲取子文件夾中的資源吓著,使用斜線作為分界符。
"""
if pkg_resources is None:
return open(os.path.join(self.root_path, resource), 'rb')
return pkg_resources.resource_stream(self.package_name, resource)
def open_session(self, request):
"""創(chuàng)建或打開一個(gè)新的session送挑。默認(rèn)的實(shí)現(xiàn)是存儲(chǔ)所有的用戶會(huì)話(session)
數(shù)據(jù)到一個(gè)簽名的cookie中绑莺。這需要secret_key屬性被設(shè)置。
:param request: request_class的實(shí)例惕耕。
"""
key = self.secret_key
if key is not None:
return SecureCookie.load_cookie(request, self.session_cookie_name,
secret_key=key)
def save_session(self, session, response):
"""如果需要更新纺裁,保存session。默認(rèn)實(shí)現(xiàn)參見open_session司澎。
:param session: 要被保存的session
(一個(gè)werkzeug.contrib.securecookie.SecureCookie對(duì)象)
:param response: 一個(gè)response_class實(shí)例欺缘。
"""
if session is not None:
session.save_cookie(response, self.session_cookie_name)
def add_url_rule(self, rule, endpoint, **options):
"""連接一個(gè)URL規(guī)則。效果和route裝飾器完全相同挤安,不過不會(huì)為端點(diǎn)注冊視圖函數(shù)谚殊。
基本示例:
@app.route('/')
def index():
pass
和下面相同:
def index():
pass
app.add_url_rule('/', 'index')
app.view_functions['index'] = index
:param rule: 字符串形式的URL規(guī)則。
:param endpoint: 對(duì)應(yīng)被注冊的URL規(guī)則的端點(diǎn)蛤铜。Flask默認(rèn)將視圖函數(shù)名作為端點(diǎn)嫩絮。
:param options: 轉(zhuǎn)發(fā)給底層的werkzeug.routing.Rule對(duì)象的選項(xiàng)丛肢。
"""
options['endpoint'] = endpoint
options.setdefault('methods', ('GET',)) # 默認(rèn)監(jiān)聽GET方法
self.url_map.add(Rule(rule, **options))
################################################################################
# 下面是幾個(gè)用于注冊各類回調(diào)函數(shù)的裝飾器,函數(shù)對(duì)象存儲(chǔ)到上面創(chuàng)建的幾個(gè)字典和列表屬性中
################################################################################
def route(self, rule, **options):
"""一個(gè)用于為給定的URL規(guī)則注冊視圖函數(shù)的裝飾器剿干。示例:
@app.route('/')
def index():
return 'Hello World'
路由中的變量部分可以使用尖括號(hào)來指定(/user/<username>)蜂怎。默認(rèn)情況下,
URL中的變量部分接受任意不包含斜線的字符串置尔,你也可以使用<converter:name>
的形式來指定一個(gè)不同的轉(zhuǎn)換器杠步。
變量部分將被作為關(guān)鍵字參數(shù)傳入視圖函數(shù)。
可用的轉(zhuǎn)換器如下所示:
========= =======================================
int 接受整型
float 類似int榜轿,但是接受浮點(diǎn)數(shù)(floating point)
path 類似默認(rèn)值幽歼,但接受斜線
========= =======================================
下面是一些示例:
@app.route('/')
def index():
pass
@app.route('/<username>')
def show_user(username):
pass
@app.route('/post/<int:post_id>')
def show_post(post_id):
pass
一個(gè)重要的細(xì)節(jié)是留意Flask是如何處理斜線的。為了讓每一個(gè)URL獨(dú)一無二差导,
下面的規(guī)則被應(yīng)用:
1. 如果一個(gè)規(guī)則以一個(gè)斜線結(jié)尾而用戶請(qǐng)求的地址不包含斜線试躏,那么該用戶
會(huì)被重定向到相同的頁面并附加一個(gè)結(jié)尾斜線。
2. 如果一個(gè)規(guī)則沒有以斜線結(jié)尾而用戶請(qǐng)求的頁面包含了一個(gè)結(jié)尾斜線设褐,
會(huì)拋出一個(gè)404錯(cuò)誤颠蕴。
這和Web服務(wù)器處理靜態(tài)文件的方式相一致。這也可以讓你安全的使用相對(duì)鏈接目標(biāo)助析。
這個(gè)route裝飾器也接受一系列參數(shù):
:param rule: 字符串形式的URL規(guī)則
:param methods: 一個(gè)方法列表犀被,可用的值限定為(GET、POST等)外冀。默認(rèn)一個(gè)
規(guī)則僅監(jiān)聽GET(以及隱式的HEAD)
:param subdomain: 當(dāng)子域名匹配使用時(shí)寡键,為規(guī)則指定子域。
:param strict_slashes: 可以用來為這個(gè)規(guī)則關(guān)閉嚴(yán)格的斜線設(shè)置雪隧,見上西轩。
:param options: 轉(zhuǎn)發(fā)到底層的werkzeug.routing.Rule對(duì)象的其他選項(xiàng)。
"""
def decorator(f):
self.add_url_rule(rule, f.__name__, **options)
self.view_functions[f.__name__] = f # 將端點(diǎn)(默認(rèn)使用函數(shù)名脑沿,即f.__name__)和函數(shù)對(duì)象的映射存儲(chǔ)到view_functions字典
return f
return decorator
def errorhandler(self, code):
"""一個(gè)用于為給定的錯(cuò)誤碼注冊函數(shù)的裝飾器藕畔。示例:
@app.errorhandler(404)
def page_not_found(error):
return 'This page does not exist', 404
你也可以不使用errorhandler注冊一個(gè)函數(shù)作為錯(cuò)誤處理器。下面的例子同上:
def page_not_found(error):
return 'This page does not exist', 404
app.error_handlers[404] = page_not_found
:param code: 對(duì)應(yīng)處理器的整型類型的錯(cuò)誤代碼庄拇。
"""
def decorator(f):
self.error_handlers[code] = f # 將錯(cuò)誤碼和函數(shù)對(duì)象的映射存儲(chǔ)到error_handlers字典
return f
return decorator
def before_request(self, f):
"""注冊一個(gè)函數(shù)注服,則每一個(gè)請(qǐng)求處理前調(diào)用。"""
self.before_request_funcs.append(f)
return f
def after_request(self, f):
"""注冊一個(gè)函數(shù)措近,在每一個(gè)請(qǐng)求處理后調(diào)用溶弟。"""
self.after_request_funcs.append(f)
return f
def context_processor(self, f):
"""注冊一個(gè)模板上下文處理函數(shù)。"""
self.template_context_processors.append(f)
return f
#################################
# 下面的幾個(gè)方法用于處理請(qǐng)求和響應(yīng)
#################################
def match_request(self):
"""基于URL映射(map)匹配當(dāng)前請(qǐng)求瞭郑。如果匹配成功辜御,同時(shí)也存儲(chǔ)端點(diǎn)和
視圖參數(shù),否則存儲(chǔ)異常屈张。
"""
rv = _request_ctx_stack.top.url_adapter.match()
request.endpoint, request.view_args = rv
return rv
def dispatch_request(self):
"""附注請(qǐng)求分發(fā)工作擒权。匹配URL苇本,返回視圖函數(shù)或錯(cuò)誤處理器的返回值。這個(gè)返回值
不一定得是響應(yīng)對(duì)象菜拓。為了將返回值返回值轉(zhuǎn)換成合適的想要對(duì)象,調(diào)用make_response笛厦。
"""
try:
endpoint, values = self.match_request()
return self.view_functions[endpoint](**values) # 根據(jù)端點(diǎn)在view_functions字典內(nèi)獲取對(duì)應(yīng)的視圖函數(shù)并調(diào)用纳鼎,傳入視圖參數(shù)
except HTTPException, e:
handler = self.error_handlers.get(e.code)
if handler is None:
return e
return handler(e)
except Exception, e:
handler = self.error_handlers.get(500)
if self.debug or handler is None:
raise
return handler(e)
def make_response(self, rv):
"""將視圖函數(shù)的返回值轉(zhuǎn)換成一個(gè)真正的響應(yīng)對(duì)象,即response_class實(shí)例裳凸。
rv允許的類型如下所示:
======================= ===============================================
response_class 這個(gè)對(duì)象將被直接返回
str 使用這個(gè)字符串作為主體創(chuàng)建一個(gè)請(qǐng)求對(duì)象
unicode 將這個(gè)字符串進(jìn)行utf-8編碼后作為主體創(chuàng)建一個(gè)請(qǐng)求對(duì)象
tuple 使用這個(gè)元組的內(nèi)容作為參數(shù)創(chuàng)建一個(gè)請(qǐng)求對(duì)象
a WSGI function 這個(gè)函數(shù)將作為WSGI程序調(diào)用并緩存為響應(yīng)對(duì)象
======================= ===============================================
:param rv: 視圖函數(shù)返回值
"""
if isinstance(rv, self.response_class):
return rv
if isinstance(rv, basestring):
return self.response_class(rv)
if isinstance(rv, tuple):
return self.response_class(*rv)
return self.response_class.force_type(rv, request.environ)
def preprocess_request(self):
"""在實(shí)際的請(qǐng)求分發(fā)之前調(diào)用贱鄙,而且將會(huì)調(diào)用每一個(gè)使用before_request
裝飾的函數(shù)。如果其中某一個(gè)函數(shù)返回一個(gè)值姨谷,這個(gè)值將會(huì)作為視圖返回值
處理并停止進(jìn)一步的請(qǐng)求處理逗宁。
"""
for func in self.before_request_funcs:
rv = func()
if rv is not None:
return rv
def process_response(self, response):
"""為了在發(fā)送給WSGI服務(wù)器前修改響應(yīng)對(duì)象,可以重寫這個(gè)方法梦湘。 默認(rèn)
這會(huì)調(diào)用所有使用after_request裝飾的函數(shù)瞎颗。
:param response: 一個(gè)response_class對(duì)象。
:return: 一個(gè)新的響應(yīng)對(duì)象或原對(duì)象捌议,必須是response_class實(shí)例哼拔。
"""
session = _request_ctx_stack.top.session
if session is not None:
self.save_session(session, response)
for handler in self.after_request_funcs:
response = handler(response)
return response
#########################################################################
# WSGI規(guī)定的可調(diào)用對(duì)象,從請(qǐng)求進(jìn)入瓣颅,到生成響應(yīng)并返回的整個(gè)處理流程都發(fā)生在這里
#########################################################################
def wsgi_app(self, environ, start_response):
"""實(shí)際的WSGI程序倦逐。它沒有通過__call__實(shí)現(xiàn),因此可以附加中間件:
app.wsgi_app = MyMiddleware(app.wsgi_app)
:param environ: 一個(gè)WSGI環(huán)境宫补。
:param start_response: 一個(gè)接受狀態(tài)碼的可調(diào)用對(duì)象檬姥,一個(gè)包含首部
的列表以及一個(gè)可選的用于啟動(dòng)響應(yīng)的異常上下文。
"""
# 在with語句下執(zhí)行相關(guān)操作粉怕,會(huì)觸發(fā)_RequestContext中的__enter__方法健民,從而推送請(qǐng)求上下文到堆棧中
with self.request_context(environ):
rv = self.preprocess_request() # 預(yù)處理請(qǐng)求,調(diào)用所有使用了before_request鉤子的函數(shù)
if rv is None:
rv = self.dispatch_request() # 請(qǐng)求分發(fā)斋荞,獲得視圖函數(shù)返回值(或是錯(cuò)誤處理器的返回值)
response = self.make_response(rv) # 生成響應(yīng)荞雏,把上面的返回值轉(zhuǎn)換成響應(yīng)對(duì)象
response = self.process_response(response) # 響應(yīng)處理,調(diào)用所有使用了after_request鉤子的函數(shù)
return response(environ, start_response)
def request_context(self, environ):
"""從給定的環(huán)境創(chuàng)建一個(gè)請(qǐng)求上下文平酿,并將其綁定到當(dāng)前上下文凤优。這必須搭配with
語句使用,因?yàn)檎?qǐng)求僅綁定在with塊中的當(dāng)前上下文里蜈彼。
用法示例:
with app.request_context(environ):
do_something_with(request)
:param environ: 一個(gè)WSGI環(huán)境筑辨。
"""
return _RequestContext(self, environ)
def test_request_context(self, *args, **kwargs):
"""從給定的值創(chuàng)建一個(gè)WSGI環(huán)境(更多信息請(qǐng)參見werkzeug.create_environ,
這個(gè)函數(shù)接受相同的參數(shù))幸逆。
"""
return self.request_context(create_environ(*args, **kwargs))
def __call__(self, environ, start_response):
"""wsgi_app的快捷方式棍辕。"""
return self.wsgi_app(environ, start_response)
本地上下文
請(qǐng)求上下文堆棧(_request_ctx_stack)棧頂(_request_ctx_stack.top)的對(duì)象即請(qǐng)求上下文對(duì)象(_RequestContext)實(shí)例
通過這里的調(diào)用可以獲取當(dāng)前請(qǐng)求上下文中保存的request暮现、session等對(duì)象
請(qǐng)求上下文在wsgi_app方法中通過with語句調(diào)用request_context方法創(chuàng)建并推入堆棧
本地上下文相關(guān)的本地線程、本地堆棧和本地代理的實(shí)現(xiàn)這里不再展開楚昭,你需要先了解堆棧和代碼在Python中的實(shí)現(xiàn)栖袋,
然后再通過閱讀Werkzeug的文檔或源碼了解具體實(shí)現(xiàn)
另外,你也可以閱讀《Flask Web開發(fā)實(shí)戰(zhàn)》(helloflask.com/book)第16章16.4.3小節(jié)抚太,這一小節(jié)首先介紹了本地線程和Werkzeug中實(shí)現(xiàn)的Local塘幅,
然后從堆棧和代理在Python中的基本實(shí)現(xiàn)開始,逐漸過渡到本地堆棧和本地代理的實(shí)現(xiàn)
_request_ctx_stack = LocalStack()
current_app = LocalProxy(lambda: _request_ctx_stack.top.app)
request = LocalProxy(lambda: _request_ctx_stack.top.request)
session = LocalProxy(lambda: _request_ctx_stack.top.session)
g = LocalProxy(lambda: _request_ctx_stack.top.g)