Falsk框架session請求流程
from?flask?import?Flask
# 1. 實例化Flask對象
app?=?Flask(__name__)# 2. 設置路由
@app.route('/index')
def?index():
????return?"index"if?__name__?==?'__main__':
????# 3. 啟動socket服務端
????app.run()
????# 4. 用戶請求到來
????app.__call__
????app.wsgi_app
????app.request_class
????app.session_interface
請求進來之后走run,run最后執行的是run_simple(host, port, self, **options)
####### app.py 文件下的 class Flask(_PackageBoundObject) 下的 def?run ######
### 最后執行了run_simple(host, port, self, **options)
def run(self, host=None, port=None, debug=None,
? ? ? ? load_dotenv=True, **options):
? ??
? ? # Change this into a no-op if the server is invoked from the
? ? # command line. Have a look at cli.py for more information.
? ? if os.environ.get('FLASK_RUN_FROM_CLI') == 'true':
? ? ? ? from .debughelpers import explain_ignored_app_run
? ? ? ? explain_ignored_app_run()
? ? ? ? return? ? if get_load_dotenv(load_dotenv):
? ? ? ? cli.load_dotenv()? ? ? ? # if set, let env vars override previous values
? ? ? ? if 'FLASK_ENV' in os.environ:
? ? ? ? ? ? self.env = get_env()
? ? ? ? ? ? self.debug = get_debug_flag()
? ? ? ? elif 'FLASK_DEBUG' in os.environ:
? ? ? ? ? ? self.debug = get_debug_flag()? ? # debug passed to method overrides all other sources
? ? if debug is not None:
? ? ? ? self.debug = bool(debug)? ? _host = '127.0.0.1'
? ? _port = 5000
? ? server_name = self.config.get('SERVER_NAME')
? ? sn_host, sn_port = None, None? ? if server_name:
? ? ? ? sn_host, _, sn_port = server_name.partition(':')? ? host = host or sn_host or _host
? ? port = int(port or sn_port or _port)? ? options.setdefault('use_reloader', self.debug)
? ? options.setdefault('use_debugger', self.debug)
? ? options.setdefault('threaded', True)? ? cli.show_server_banner(self.env, self.debug, self.name, False)
? ? from werkzeug.serving import run_simple
? ? try:
? ? ? ? run_simple(host, port, self, **options)
? ? finally:
? ? ? ? # reset the first request information if the development server
? ? ? ? # reset normally. ?This makes it possible to restart the server
? ? ? ? # without reloader and that stuff from an interactive shell.
? ? ? ? self._got_first_request = False
werkzeug源碼講到,執行run_simple 方法,其實就是 當請求來時 最后會調用第三個參數加括號執行,即執行self的 __call__ 方法
參考:https://blog.csdn.net/fenglepeng/article/details/104676817
請求到來
當請求到來,執行__call__方法。看一下源碼。
####### app.py 文件下的 class Flask(_PackageBoundObject) 下的 ?def?__call__ ######
## 最后執行self.wsgi_app(environ, start_response)
def?__call__(self, environ, start_response):
????"""The WSGI server calls the Flask application object as the
????WSGI application. This calls :meth:`wsgi_app` which can be
????wrapped to applying middleware."""
????
? ? # environ:請求相關的所有數據,wsgi將原生的請求做第一步處理,把字符串分割。(wsgi做了初步封裝)
????# start_response:用于設置響應相關的所有數據。
????return?self.wsgi_app(environ, start_response)
點開?wsgi_app
####### app.py 文件下的 class Flask(_PackageBoundObject) 下的 def?wsgi_app ######
# 這個文件是Flask的整個執行流程的入口
def?wsgi_app(self, environ, start_response):
??????'''
??????1、獲取environ并對其進行再次封裝。就成了我們要的request;并獲取session值,此時為空,獲取self,封裝成app
? ? ? ? ? ?兩個東西app_ctx,request_ctx?放到“某個神奇”的地方。ctx.request = Request(environ) ?ctx.session = None ctx.app=app
??????'''
??????ctx?=?self.request_context(environ)??# 實際執行ctx = RequestContext(self, environ)
??????error?=?None
??????try:
??????????try:
? ? ? ? ? ? ? # 2、把app_ctx,request_ctx?放到“某個神奇”的地方。對于session來說,執行SecureCookieSessionInterface.open_session(),去cookie中獲取session的值,反序列化解密之后給ctx.session重新賦值(默認session的值存在cookie中)。
??????????????ctx.push()??????????????# 3、執行視圖函數,然后去‘某個神奇’的地方獲取session,加密,序列化,寫入cookie
??????????????response?=?self.full_dispatch_request()
???????????except?Exception as e:
??????????????error?=?e
??????????????response?=?self.handle_exception(e)
??????????except:
??????????????error?=?sys.exc_info()[1]
??????????????raise
??????????return?response(environ, start_response)
??????finally:
??????????if?self.should_ignore_error(error):
??????????????error?=?None??????????# 4、“某個神奇”的地方位置清空 (請求結束)
??????????ctx.auto_pop(error)
1 、首先查看 request_context 函數
####### app.py 文件下的 class Flask(_PackageBoundObject) 下的 def request_context ######
def request_context(self, environ): ?# self 是app,即Flask的實例化
? ? return RequestContext(self, environ)
RequestContext把我們的對象和請求封裝到了一個類。我們對這個類進行實例化,看一下做了什么事?
####### ctx.py 文件下的 class RequestContext(Object) 下的 def __init__ ######
class RequestContext(object):
? ? """The request context contains all request relevant information. ?It is
? ? created at the beginning of the request and pushed to the
? ? `_request_ctx_stack` and removed at the end of it. ?It will create the
? ? URL adapter and request object for the WSGI environment provided.
? ? """? ? def __init__(self, app, environ, request=None):
? ? ? ? self.app = app? # 對app進行封裝
? ? ? ? if request is None:??# 對environ進行第二次封裝,封裝成一個Request對象
? ? ? ? ? ? request = app.request_class(environ) ?#?request_class = Request ?實際執行為 request = Request(environ)
? ? ? ? self.request = request
? ? ? ? self.url_adapter = app.create_url_adapter(self.request)
? ? ????self.flashes = None
???? ? ?self.session = None?? ?# 為session 賦值 None
???? ? ?self._implicit_app_ctx_stack = []
???? ? ?self.preserved = False
???? ? ?self._preserved_exc = None
???? ? ?self._after_request_functions = []
???? ? ?self.match_request()? ? def match_request(self):
? ? ? ? """Can be overridden by a subclass to hook into the matching
? ? ? ? of the request.
? ? ? ? """
? ? ? ? try:
? ? ? ? ? ? url_rule, self.request.view_args = \
? ? ? ? ? ? ? ? self.url_adapter.match(return_rule=True)
? ? ? ? ? ? self.request.url_rule = url_rule
? ? ? ? except HTTPException as e:
? ? ? ? ? ? self.request.routing_exception = e? ? def push(self):? ? # 點開ctx.push(),實際執行這里
? ? ? ? """Binds the request context to the current context."""
? ? ? ? # If an exception occurs in debug mode or if context preservation is
? ? ? ? # activated under exception situations exactly one context stays
? ? ? ? # on the stack. ?The rationale is that you want to access that
? ? ? ? # information under debug situations. ?However if someone forgets to
? ? ? ? # pop that context again we want to make sure that on the next push
? ? ? ? # it's invalidated, otherwise we run at risk that something leaks
? ? ? ? # memory. ?This is usually only a problem in test suite since this
? ? ? ? # functionality is not active in production environments.
? ? ? ? top = _request_ctx_stack.top
? ? ? ? if top is not None and top.preserved:
? ? ? ? ? ? top.pop(top._preserved_exc)? ? ? ? # Before we push the request context we have to ensure that there
? ? ? ? # is an application context.
? ? ? ? app_ctx = _app_ctx_stack.top
? ? ? ? if app_ctx is None or app_ctx.app != self.app:
? ? ? ? ? ? app_ctx = self.app.app_context()
? ? ? ? ? ? app_ctx.push()
? ? ? ? ? ? self._implicit_app_ctx_stack.append(app_ctx)
? ? ? ? else:
? ? ? ? ? ? self._implicit_app_ctx_stack.append(None)? ? ? ? if hasattr(sys, "exc_clear"):
? ? ? ? ? ? sys.exc_clear()? ? ? ? _request_ctx_stack.push(self)
? ? ? ? # Open the session at the moment that the request context is available.
? ? ? ? # This allows a custom open_session method to use the request context.
? ? ? ? # Only open a new session if this is the first time the request was
? ? ? ? # pushed, otherwise stream_with_context loses the session.
? ? ? ? # 這里這里
? ? ? ? # 當請求進來時,session 肯定為空,因為上面設置的是空。Flask的session加密,序列化之后保存在cookie中
? ? ? ? if self.session is None:
? ? ? ? ? ? session_interface = self.app.session_interface
? ? ? ? ? ? self.session = session_interface.open_session(self.app, self.request)? ? ? ? ? ? if self.session is None:
? ? ? ? ? ? ? ? self.session = session_interface.make_null_session(self.app)? ? ? ? if self.url_adapter is not None:
? ? ? ? ? ? self.match_request()? ? def pop(self, exc=_sentinel):
? ? ? ? """Pops the request context and unbinds it by doing that. ?This will
? ? ? ? also trigger the execution of functions registered by the
? ? ? ? :meth:`~flask.Flask.teardown_request` decorator.? ? ? ? .. versionchanged:: 0.9
? ? ? ? ? ?Added the `exc` argument.
? ? ? ? """
? ? ? ? app_ctx = self._implicit_app_ctx_stack.pop()? ? ? ? try:
? ? ? ? ? ? clear_request = False
? ? ? ? ? ? if not self._implicit_app_ctx_stack:
? ? ? ? ? ? ? ? self.preserved = False
? ? ? ? ? ? ? ? self._preserved_exc = None
? ? ? ? ? ? ? ? if exc is _sentinel:
? ? ? ? ? ? ? ? ? ? exc = sys.exc_info()[1]
? ? ? ? ? ? ? ? self.app.do_teardown_request(exc)? ? ? ? ? ? ? ? # If this interpreter supports clearing the exception information
? ? ? ? ? ? ? ? # we do that now. ?This will only go into effect on Python 2.x,
? ? ? ? ? ? ? ? # on 3.x it disappears automatically at the end of the exception
? ? ? ? ? ? ? ? # stack.
? ? ? ? ? ? ? ? if hasattr(sys, "exc_clear"):
? ? ? ? ? ? ? ? ? ? sys.exc_clear()? ? ? ? ? ? ? ? request_close = getattr(self.request, "close", None)
? ? ? ? ? ? ? ? if request_close is not None:
? ? ? ? ? ? ? ? ? ? request_close()
? ? ? ? ? ? ? ? clear_request = True
? ? ? ? finally:
? ? ? ? ? ? rv = _request_ctx_stack.pop()? ? ? ? ? ? # get rid of circular dependencies at the end of the request
? ? ? ? ? ? # so that we don't require the GC to be active.
? ? ? ? ? ? if clear_request:
? ? ? ? ? ? ? ? rv.request.environ["werkzeug.request"] = None? ? ? ? ? ? # Get rid of the app as well if necessary.
? ? ? ? ? ? if app_ctx is not None:
? ? ? ? ? ? ? ? app_ctx.pop(exc)? ? ? ? ? ? assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
? ? ? ? ? ? ? ? rv,
? ? ? ? ? ? ? ? self,
? ? ? ? ? ? )? ? def auto_pop(self, exc):
? ? ? ? if self.request.environ.get("flask._preserve_context") or (
? ? ? ? ? ? exc is not None and self.app.preserve_context_on_exception
? ? ? ? ):
? ? ? ? ? ? self.preserved = True
? ? ? ? ? ? self._preserved_exc = exc
? ? ? ? else:
? ? ? ? ? ? self.pop(exc)? ? def __enter__(self):
? ? ? ? self.push()
? ? ? ? return self? ? def __exit__(self, exc_type, exc_value, tb):
? ? ? ? # do not pop the request stack if we are in debug mode and an
? ? ? ? # exception happened. ?This will allow the debugger to still
? ? ? ? # access the request object in the interactive shell. ?Furthermore
? ? ? ? # the context can be force kept alive for the test client.
? ? ? ? # See flask.testing for how this works.
? ? ? ? self.auto_pop(exc_value)? ? ? ? if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
? ? ? ? ? ? reraise(exc_type, exc_value, tb)? ? def __repr__(self):
? ? ? ? return "<%s '%s' [%s] of %s>" % (
? ? ? ? ? ? self.__class__.__name__,
? ? ? ? ? ? self.request.url,
? ? ? ? ? ? self.request.method,
? ? ? ? ? ? self.app.name,
? ? ? ? )
回到wsgi_app類中,我們會得到:
- ctx.request=Request(environ)。environ是一個原始的請求對象,但是現在被Request包裹,就不是原始的了。我們就可以執行".args",".form",".method"等。會自動幫我們去原始的數據解析。
- ctx.session=None.
- ctx.app=app
點擊進入 session_interface
####### app.py 文件下的 class Flask(_PackageBoundObject) 下的 def request_context ######
class Flask(_PackageBoundObject)
? ? session_interface = SecureCookieSessionInterface()
?查看open_session
## sessions.py 文件下的 class SecureCookieSessionInterface(SessionInterface) 下的 def?open_session ######
# 在cookie中取出session的key,然后獲取對應的session值,并返回
def?open_session(self, app, request):
????s?=?self.get_signing_serializer(app)??# 加密
????if?s?is?None:
????????return?None
????val?=?request.cookies.get(app.session_cookie_name)??# 去cookie中取值
????if?not?val:??# 第一次訪問為空執行
????????return?self.session_class()??# 返回{}
????max_age?=?total_seconds(app.permanent_session_lifetime)
????try:
????????data?=?s.loads(val, max_age=max_age)?# loads:反序列化 val:原來的值
????????return?self.session_class(data)?# {"k1":123}
????except?BadSignature:
????????return?self.session_class()
open_session返回啥self.session中就是啥。
現在回到我們的wsgi_app類中,ctx.push() 對于session的作用:執行SecureCookieSessionInterface.open_session(),去cookie中獲取值,并反序列化解密之后給ctx.session重新賦值。
3、現在才開始真正走視圖函數full_dispatch_request
######################## app.py 文件下的 class Flask 下的 full_dispatch_request ####################
def full_dispatch_request(self):
? ? self.try_trigger_before_first_request_functions()
? ? try:
? ? ? ? request_started.send(self)
? ? ? ? rv = self.preprocess_request()? # 獲取request
? ? ? ? if rv is None:
? ? ? ? ? ? rv = self.dispatch_request() ? # 調用視圖函數
? ? except Exception as e:
? ? ? ? rv = self.handle_user_exception(e)?
? ? return self.finalize_request(rv) ? ? ? # 視圖函數執行完畢的善后工作
?點擊進入 finalize_request
######################## app.py 文件下的 class Flask 下的 finalize_request ####################
def finalize_request(self, rv, from_error_handler=False):? ? response = self.make_response(rv)
? ? try:
? ? ? ? response = self.process_response(response) ?# 觸發函數
? ? ? ? request_finished.send(self, response=response)
? ? except Exception:
? ? ? ? if not from_error_handler:
? ? ? ? ? ? raise
? ? ? ? self.logger.exception('Request finalizing failed with an '
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 'error while handling an error')
? ? return response
點擊進入 process_response
######################## app.py 文件下的 class Flask 下的 process_response ####################
def process_response(self, response):
? ? ctx = _request_ctx_stack.top
? ? bp = ctx.request.blueprint
? ? funcs = ctx._after_request_functions
? ? if 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) ?# 保存session
? ? return response
看一下save_session:
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):
? ? ? ? return? ? httponly = 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)) ?# 加密序列化成字符串
? ? response.set_cookie( ? ? ? ?# 設置cookie
? ? ? ? app.session_cookie_name,
? ? ? ? val,
? ? ? ? expires=expires,
? ? ? ? httponly=httponly,
? ? ? ? domain=domain,
? ? ? ? path=path,
? ? ? ? secure=secure,
? ? ? ? samesite=samesite
? ? )
最后執行?ctx.auto_pop(error)
這就是Flask框架中sesion的請求流程。說了這么多,其實真正實現咱們想要的功能的就是兩個方法:open_session,save_session.請求進來執行open_session,請求走的時候執行save_session。
默認都是調用app.session_interface。
流程:請求到來:請求到來之后wsgi會觸發__call__方法,由__call__方法再次調用wsgi_app方法,將請求和session相關封裝到ctx = RequestContext對象中,此時session為空,request二次封裝,可以使用將app和g封裝到app_ctx = AppContext對象中。再通過LocalStack對象將ctx、app_ctx封裝到Local對象中。獲取數據:通過LocalProxy對象+偏函數,調用LocalStack去Local中獲取響應ctx、app_ctx中封裝的值。請求結束:調用LocalStack的pop方法,將ctx和app_ctx移除。