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()
感谢您的阅读,如果您觉得阅读本文对您有帮助,请点一下"推荐"按钮,本文欢迎各位转载,但是转载文章之后必须在文章页面中给出作者和原文连接,谢谢。