flask上下文管理机制
flask中的上下文分為請求上寫文和應用上下文,接下來,從以下三個大的方面分別探討flask的兩大上下文管理機制。
先來一個最簡單的flask版的Hello World
from flask import Flaskapp = Flask(__name__)@app.route('/') def index():return "Hello World"if __name__ == '__main__':app.run()Flask版Hello World flask版的Hello World啟動一個flask項目時,會先執行app.run()方法,這是整個項目的入口,執行run方法時,接著執行werkzeug模塊中的run_simple
此時的self指的是我們實例化的Flask對象,即app,當請求到來時,會觸發調用Flask的__call__方法。
一、請求進來時
觸發執行__call__方法,__call__方法的邏輯很簡單,直接執行wsgi_app方法,將包含所有請求相關數據和一個響應函數傳進去。
?此時的self指的是Flask的實例化對象app,接下來執行app的wsgi_app方法。
此時的self仍然指的是Flask的實例化對象app,首先執行app這個對象的request_context的方法,將environ傳進去,最后返回一個RequestContext類的對象,對象中封裝了request和session等實例變量。ctx=RequestContext()
RequestContext類在實例化過程中執行__init__方法,賦了兩個非常有用的屬性,一個是request,一個是session
這兩個屬性中request是一個Request()對象,這個對象就是我們在flask中使用的request對象,為我們提供了很多便捷的屬性和方法,比如:request.method、request.form、request.args等等,另一個屬性是session,初始為None。
緊接著執行ctx.push()方法,此時的self為ctx
這個方法中,在執行請求上下文對象ctx之前先執行的app的app_context()方法,返回值為AppContext()的實例化對象,即app_ctx = AppContext(),先執了AppContext()的push方法,然后才執行_request_ctx_stack對象中的top和_request_ctx_stack.push(self),最后對ctx中的session進行處理。
所以,flask中的應用上下文發生在請求上下文之前。
但是我們先說請求上下文,在處理完應用上下文的push方法后,緊接著執行了_request_ctx_stack對象的兩個方法。
而這個_request_ctx_stack是LocalStack這個類的對象。_request_ctx_stack = LocalStack()
?
LocalStack有沒有很眼熟,沒錯,flask內部使用的機制就是類似于我們上文中自定義的LocalStack的機制,實例化過程中使用了面向對象中的組合概念,self._local = Local(),然后在自身又實現了push、pop、top方法,這三個方法中都是通過反射從Local類的實例化對象中對一個stack屬性進行append、pop、[-1]的操作,所以,Local對象中的stack屬性對應的值一定是一個類似于列表的東西。通過對列表的操作,實現一個類似于棧的存取。
?接下來我們開始執行LocalStack()對象的push方法
在此方法中執行了self._local.stack = rv =[ ],self._local為Local類的實例化對象,Local類在實例化的過程中,會對每個對象生成__storage__和__indent_func__兩個實例變量,__storage__對應的是一個空字典{},__ident_func__對應的一個獲取當前線程ID的一個可執行函數get_ident。
?
我們翻遍整個Local類的源碼,發現內部并沒有實現一個叫stack的方法或者屬性,但是上面我們提到了LocalStack對象會對Local對象中的一個叫stack的東西進行一系列操作。找不到不會報錯嗎?
?這就是flask的巧妙之處,通過類的一些魔法方法巧妙的實現了相應的處理。如果對象中沒有某個屬性,取值時會執行類中的__getattr__方法,賦值時會執行類中的__setattr__方法。
?
?
處理完_request_ctx_stack后,就該處理session了。
在flask中,處理session時,非常的巧妙,完美的遵循了開閉原則,會先執行session_interface對象的open_session方法,在這個方法中,會先從用戶請求的cookie中獲取sessionid,獲取該用戶之前設置的session值,然后將值賦值到ctx.session中。
處理完session后,ctx.push方法就執行完了,返回到最開始的app.wsgi_app方法中,執行完push方法后,接著執行full_dispatch_request方法,從這個名字中我們也能猜到,這個方法只要是負責請求的分發。
def full_dispatch_request(self):self.try_trigger_before_first_request_functions()try:request_started.send(self)rv = self.preprocess_request()if rv is None:rv = self.dispatch_request()except Exception as e:rv = self.handle_user_exception(e)return self.finalize_request(rv) full_dispath_request方法 full_dispatch_request在full_dispatch_request方法中先執行preprocess_request方法,這個方法,會先執行所有被before_request裝飾器裝飾的函數,然后就通過路由的分發執行視圖函數了(dispatch_request)
?二、執行視圖函數時
在執行視圖函數之前,先執行了before_request,在執行我們的視圖函數。
視圖函數主要處理業務邏輯。在視圖函數中可以調用request對象,進行取值,也可以調用session對象對session的存取。
在整個request的請求生命周期中,獲取請求的數據直接調用request即可,對session進行操作直接調用session即可。request和session都是LocalProxy對象,借助偏函數的概念將對應的值傳入_lookup_req_object函數。先從_request_ctx_stack(LocalStack)對象中獲取ctx(請求上下文對象),再通過反射分別獲取request和session屬性。整個過程中LocalStack扮演了一個全局倉庫的角色,請求進來將數據存取,需要時即去即用。所以,flask實現了在整個請求的生命周期中哪兒需要就直接調用的特色。
request = LocalProxy(partial(_lookup_req_object, 'request')) session = LocalProxy(partial(_lookup_req_object, 'session'))三、請求結束前
視圖函數執行完后,dispatch_request執行結束,執行full_dispatch_request方法的返回值finalize_request方法。這個方法中,同樣的,在返回響應之前,先執行所有被after_request裝飾器裝飾的函數。
---->finalize_request ------> process_response?
def process_response(self, response):ctx = _request_ctx_stack.topbp = ctx.request.blueprintfuncs = ctx._after_request_functionsif bp is not None and bp in self.after_request_funcs:funcs = chain(funcs, reversed(self.after_request_funcs[bp]))if None in self.after_request_funcs:funcs = chain(funcs, reversed(self.after_request_funcs[None]))for handler in funcs:response = handler(response)if not self.session_interface.is_null_session(ctx.session):self.session_interface.save_session(self, ctx.session, response)return responseprocess_response執行process_response過程中,執行完after_request后,然后,執行session的save_session方法。將內存中保存在ctx.session的值取到后,json.dumps()序列化后,寫入響應的cookie中(set_cookie),最后返回響應。
?
def save_session(self, app, session, response):domain = self.get_cookie_domain(app)path = self.get_cookie_path(app)# If the session is modified to be empty, remove the cookie.# If the session is empty, return without setting the cookie.if not session:if session.modified:response.delete_cookie(app.session_cookie_name,domain=domain,path=path)return# Add a "Vary: Cookie" header if the session was accessed at all.if session.accessed:response.vary.add('Cookie')if not self.should_set_cookie(app, session):returnhttponly = self.get_cookie_httponly(app)secure = self.get_cookie_secure(app)samesite = self.get_cookie_samesite(app)expires = self.get_expiration_time(app, session)val = self.get_signing_serializer(app).dumps(dict(session))# set_cookie將session寫入響應的cookie中 response.set_cookie(app.session_cookie_name,val,expires=expires,httponly=httponly,domain=domain,path=path,secure=secure,samesite=samesite)save_session返回響應后,自動的調用ctx.auto_pop(error),將Local中存儲的ctx對象pop掉,整個請求結束。
?四、應用上下文
與請求上下文類似,當請求進來時,先實例化一個AppContext對象app_ctx,在實例化的過程中,提供了兩個有用的屬性,一個是app,一個是g。self.app就是傳入的全局的app對象,self.g是一個全局的存儲值的對象。接著將這個app_ctx存放到LocalStack()。
class AppContext(object):def __init__(self, app):self.app = appself.url_adapter = app.create_url_adapter(None)self.g = app.app_ctx_globals_class()視圖函數中,我們就可以調用app對象和g對象,如果我們使用藍圖構建我們的項目時,在每一個直接引用app就會造成循環引用的異常,這時,應用上下文就會顯得非常有用,我們可以直接調用current_app就可以在整個生命周期中使用我們的app對象了。比如使用我們的配置項:current_app.config
current_app = LocalProxy(_find_app)最后,當視圖函數執行結束后,從storage中pop掉app_ctx對象。
總結:
流程:請求到來:- 請求到來之后wsgi會觸發__call__方法,由__call__方法再次調用wsgi_app方法,將request和session相關封裝到ctx = RequestContext對象中。將app和g封裝到app_ctx = AppContext對象中。再通過LocalStack對象將ctx、app_ctx封裝到Local對象中。獲取數據:通過LocalProxy對象+偏函數,調用LocalStack去Local中獲取響應ctx、app_ctx中封裝的值。請求結束:調用LocalStack的pop方法,將ctx和app_ctx移除。?
轉載于:https://www.cnblogs.com/fengchong/p/10256552.html
總結
以上是生活随笔為你收集整理的flask上下文管理机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: @bzoj - 3238@ [Ahoi2
- 下一篇: CTF python沙箱逃逸进阶题目