Falsk session 源码解析

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值,此时为空
         两个东西放到“某个神奇”的地方。ctx.request = Request(environ)  ctx.session = None
      '''
      ctx = self.request_context(environ)  # 实际执行ctx = RequestContext(self, environ)

      error = None
      try:
          try:
              # 2、执行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__ ######
def __init__(self, app, environ, request=None):
    self.app = app
    if request is None:
        # 对environ进行第二次封装,封装成一个Request对象
        request = app.request_class(environ)  # request_class = Request  实际执行为 request = Request(environ)

    # 把 request 返回
    self.request = request
    self.url_adapter = app.create_url_adapter(self.request)
    self.flashes = None
    # 为session 赋值 None
    self.session = None

    self._implicit_app_ctx_stack = []
    self.preserved = False
    self._preserved_exc = None
    self._after_request_functions = []
    self.match_request()

回到wsgi_app类中,我们会得到:

  1. ctx.request=Request(environ)。environ是一个原始的请求对象,但是现在被Request包裹,就不是原始的了。我们就可以执行".args",".form",".method"等。会自动帮我们去原始的数据解析。
  2. ctx.session=None.

现在ctx已经封装了两个值了,一个是请求相关的数据,一个是session,把它放到“某个神奇的地方”。

视图函数:

2、点开ctx.push()。上面的可以先省略

####### ctx.py 文件下的 class RequestContext(Object) 下的 def  push ######
### 前面先不看,直接看if self.session is None:
def push(self):
    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)

    # 这里这里
    # 当请求进来时,session 肯定为空,因为上面设置的是空。Flask的session加密,序列化之后保存在cookie中

    if self.session is None:
        # session_interface.open_session 相当于执行 SecureCookieSessionInterface.open_session
        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)

点击进入 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() 的作用:执行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移除。
发布了33 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/fenglepeng/article/details/104674845