flask源码解析之session

内容回顾

cookie与session的区别:
1. session 是保存在服务端的键值对
2. cookie 只能保存4096个字节的数据,但是session不受限制
3. cookie保存在浏览器,安全性差,但是session的安全性高
4. 通过cookie 识别浏览器,获取cookie中的随机序列从session中获取该用户的相关数据,解决了http无状态的缺点

源码流程

1. session的"生成"

在 RequestContext 类中

    def __init__(self, app, environ, request=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = app.create_url_adapter(self.request)
        self.flashes = None
        self.session = None

2. 在 app.wsgi_app()函数中

  2.1  ctx.push() 

  2.2 将生成的 SecureCookieSession类实例赋值个app.session 

    def push(self):
        """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  == ctx
        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.
        """
            _request_ctx_stack 和 _app_ctx_stack 都是 Local 类的实例
        """
        # 获取 应用上下文的栈顶元素,得到 app_ctx
        app_ctx = _app_ctx_stack.top
        if app_ctx is None or app_ctx.app != self.app:
            # self.app == Fask()
            # 得到 一个 AppContext类的实例对象,得到一个 应用上下文对象 app_ctx,此时 app_ctx拥有以下属性: app_ctx.app = app, app_ctx.g = app.app_ctx_globals_class()
            app_ctx = self.app.app_context()
            # 将 app_ctx 入栈,应用上下文入栈
            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()

        # self 指的是 ctx,即将ctx入栈,即 _request_ctx_stack._local.stack = [ctx]。请求上下文入栈
        _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.
        # 由于每次请求都会初始化创建你ctx,因此session都为None
        if self.session is None:
            # SecureCookieSessionInterface()
            # session_interface = SecureCookieSessionInterface(),即session_interface就是一个SecureCookieSessionInterface类的实例对象
            session_interface = self.app.session_interface
            # 第一次访问:生成一个 字典(容器) 返回至 self.session
            self.session = session_interface.open_session(
                self.app, self.request
            )
            if self.session is None:
                self.session = session_interface.make_null_session(self.app)
# 创建 SecureCookieSessionInterface类的实例对象
session_interface = SecureCookieSessionInterface()
    def open_session(self, app, request):
        # 获取 app的 secret_key
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        # 从 cookie 中获取 session 的value
        val = request.cookies.get(app.session_cookie_name)
        if not val:
            # 生成一个 SecureCookieSession类的实例对象,用于保存用户的session数据。 SecureCookieSession类其实就是一个特殊的字典
            return self.session_class()
     # 第二次访问 max_age
= total_seconds(app.permanent_session_lifetime) try: # 将 获取到的cookie进行反序列化 data = s.loads(val, max_age=max_age) # 返回一个含有 cookie信息的 SecureCookieSession 类实例 return self.session_class(data) except BadSignature: return self.session_class()
session_class = SecureCookieSession

  2.4 在  response = self.full_dispatch_request() 中

    def full_dispatch_request(self):
        """Dispatches the request and on top of that performs request
        pre and postprocessing as well as HTTP exception catching and
        error handling.

        .. versionadded:: 0.7
        """
        #  将 _got_first_request =  True,依次执行定义的 before_first_request 函数
        self.try_trigger_before_first_request_functions()
        try:
            # 触发 request_started 信号
            request_started.send(self)
            #  执行钩子函数:before_request,before_first_request
            rv = self.preprocess_request()
            # 如果执行的before_request,before_first_request函数没有返回值,则继续执行视图函数。若有返回值,则不执行视图函数
            if rv is None:
                # 执行此url对应的别名的视图函数并执行该函数,返回视图函数的返回值,得到相应信息
                rv = self.dispatch_request()
        except Exception as e:
            # 如果发生错误,则将异常信息作为返回值进行返回
            rv = self.handle_user_exception(e)
        # 封装返回信息并返回,包括 session
        return self.finalize_request(rv)
    def finalize_request(self, rv, from_error_handler=False):
        """Given the return value from a view function this finalizes
        the request by converting it into a response and invoking the
        postprocessing functions.  This is invoked for both normal
        request dispatching as well as error handlers.

        Because this means that it might be called as a result of a
        failure a special safe mode is available which can be enabled
        with the `from_error_handler` flag.  If enabled, failures in
        response processing will be logged and otherwise ignored.

        :internal:
        """
        response = self.make_response(rv)
        try:
            response = self.process_response(response)
            # 触发 request_finished 信号
            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
    def process_response(self, response):
        """Can be overridden in order to modify the response object
        before it's sent to the WSGI server.  By default this will
        call all the :meth:`after_request` decorated functions.

        .. versionchanged:: 0.5
           As of Flask 0.5 the functions registered for after request
           execution are called in reverse order of registration.

        :param response: a :attr:`response_class` object.
        :return: a new response object or the same, has to be an
                 instance of :attr:`response_class`.
        """
        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 这个字典序列化,并返回,写入到客户端浏览器的cookie中
            self.session_interface.save_session(self, ctx.session, response)
        return response

3. 在视图函数中对app.session赋值

  3.1  session["user"] = 1213 

  3.2 向  app.session(实际上是向SecureCookieSession类实例中)写值

# 获取 ctx.session
session = LocalProxy(partial(_lookup_req_object, 'session'))
# 偏函数
def _lookup_req_object(name):
    # top == ctx
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    # 返回 ctx.request 或者 ctx.session
    return getattr(top, name)
    def __init__(self, local, name=None):
        # local 指的是 偏函数的内存地址
        object.__setattr__(self, '_LocalProxy__local', local)
        object.__setattr__(self, '__name__', name)
        if callable(local) and not hasattr(local, '__release_local__'):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, '__wrapped__', local)
    def __setitem__(self, key, value):
        # 1.obj = self._get_current_object()
        # 2. obj[key] = value
        self._get_current_object()[key] = value
    def _get_current_object(self):
        """Return the current object.  This is useful if you want the real
        object behind the proxy at a time for performance reasons or because
        you want to pass the object into a different context.
        """
        if not hasattr(self.__local, '__release_local__'):
            # 执行偏函数,返回session 或者 request
            return self.__local()
        try:
            return getattr(self.__local, self.__name__)
        except AttributeError:
            raise RuntimeError('no object bound to %s' % self.__name__)

对于session的写值操作实际上还是调用的dict类的__setitem__方法

注意:

  在flask默认的session中,没有生成随机字符串和进行session数据持久化的操作,而只是单穿把用户赋值给session的数据加密再反序列化之后返回给浏览器。在进行判断的时候也只是单纯的获取cookie中有没有"session"对应的value,可以说在很大程度上造成了数据的不安全。

原生session组件的扩展---flaks_session组件

from flask import Flask,session


app = Flask(__name__)
app.secret_key = 'suijksdfsd'

# 方式一:
from redis import Redis
from flask_session import RedisSessionInterface
conn = Redis()
app.session_interface = RedisSessionInterface(conn,key_prefix='__',use_signer=False,permanent=True)


# 方式二:
# from redis import Redis
# from flask.ext.session import Session
# app.config['SESSION_TYPE'] = 'redis'
# app.config['SESSION_REDIS'] = Redis(host='192.168.0.94',port='6379')
# Session(app)



@app.route('/')
def index():
    session['xxx'] = 123
    return 'Index'


if __name__ == '__main__':

    app.run()

 

posted on 2018-09-25 20:50  云烟||成雨  阅读(371)  评论(0编辑  收藏  举报