Falsk session 源码解析

14 篇文章 1 订阅

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类中,我们会得到:

  1. ctx.request=Request(environ)。environ是一个原始的请求对象,但是现在被Request包裹,就不是原始的了。我们就可以执行".args",".form",".method"等。会自动帮我们去原始的数据解析。
  2. ctx.session=None.
  3. 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移除。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值