内置session原理
请求到来
当请求进来之后,先执行Flask对象的 __call__ 方法
def wsgi_app(self, environ, start_response):# 获取请求相关数据,并进行封装和加工ctx = self.request_context(environ)# 将请求消息推送到堆栈中,并执行 open_session方法 ctx.push()error = Nonetry:try:response = self.full_dispatch_request()except Exception as e:error = eresponse = self.make_response(self.handle_exception(e))return response(environ, start_response)finally:if self.should_ignore_error(error):error = Nonectx.auto_pop(error)def __call__(self, environ, start_response):"""Shortcut for :attr:`wsgi_app`."""return self.wsgi_app(environ, start_response)
def push(self):top = _request_ctx_stack.topif 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.topif 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)# 调用Flask对象的open_session方法self.session = self.app.open_session(self.request)if self.session is None:self.session = self.app.make_null_session()
def open_session(self, request):"""Creates or opens a new session. Default implementation stores allsession data in a signed cookie. This requires that the:attr:`secret_key` is set. Instead of overriding this methodwe recommend replacing the :class:`session_interface`.:param request: an instance of :attr:`request_class`."""# self指的是Flask对象,session_interface默认值为SecureCookieSessionInterface()return self.session_interface.open_session(self, request)
由以上源码发现,当接收到用户请求之后,会调用 Flask对象的 session_interface对象的open_session方法,以此来获取一个session对象。
class SecureCookieSessionInterface(SessionInterface):"""The default session interface that stores sessions in signed cookiesthrough the :mod:`itsdangerous` module."""#: the salt that should be applied on top of the secret key for the#: signing of cookie based sessions.salt = 'cookie-session'#: the hash function to use for the signature. The default is sha1digest_method = staticmethod(hashlib.sha1)#: the name of the itsdangerous supported key derivation. The default#: is hmac.key_derivation = 'hmac'#: A python serializer for the payload. The default is a compact#: JSON derived serializer with support for some extra Python types#: such as datetime objects or tuples.serializer = session_json_serializersession_class = SecureCookieSessiondef get_signing_serializer(self, app):if not app.secret_key:return Nonesigner_kwargs = dict(key_derivation=self.key_derivation,digest_method=self.digest_method)return URLSafeTimedSerializer(app.secret_key, salt=self.salt,serializer=self.serializer,signer_kwargs=signer_kwargs)def open_session(self, app, request):# 获取加密相关的类,必须设置app.secret_key,不然s就是Nones = self.get_signing_serializer(app)if s is None:return None# 去Cookie中获取 session 对应的值(该值默认是加密之后的session的值,也可以改造成随机字符串)val = request.cookies.get(app.session_cookie_name)if not val:# 未获取到值,则创建一个空字典(就是flask中用到的session)return self.session_class()max_age = total_seconds(app.permanent_session_lifetime)try:data = s.loads(val, max_age=max_age)# 如果获取到值,则将值放入字典中(就是flask中用到的session)return self.session_class(data)except BadSignature:# 解密失败,则创建一个空字典(就是flask中用到的session)return self.session_class()
上述中 self.session_class 就是创建的一个SecureCookieSession对象,这个类是继承了字典的类,其实就是一个特殊的字典。
class SessionMixin(object):"""Expands a basic dictionary with an accessors that are expectedby Flask extensions and users for the session."""def _get_permanent(self):return self.get('_permanent', False)def _set_permanent(self, value):self['_permanent'] = bool(value)#: this reflects the ``'_permanent'`` key in the dict.permanent = property(_get_permanent, _set_permanent)del _get_permanent, _set_permanent#: some session backends can tell you if a session is new, but that is#: not necessarily guaranteed. Use with caution. The default mixin#: implementation just hardcodes ``False`` in.new = False#: for some backends this will always be ``True``, but some backends will#: default this to false and detect changes in the dictionary for as#: long as changes do not happen on mutable structures in the session.#: The default mixin implementation just hardcodes ``True`` in.modified = Trueclass UpdateDictMixin(object):"""Makes dicts call `self.on_update` on modifications... versionadded:: 0.5:private:"""on_update = Nonedef calls_update(name):def oncall(self, *args, **kw):rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw)if self.on_update is not None:self.on_update(self)return rvoncall.__name__ = namereturn oncalldef setdefault(self, key, default=None):modified = key not in selfrv = super(UpdateDictMixin, self).setdefault(key, default)if modified and self.on_update is not None:self.on_update(self)return rvdef pop(self, key, default=_missing):modified = key in selfif default is _missing:rv = super(UpdateDictMixin, self).pop(key)else:rv = super(UpdateDictMixin, self).pop(key, default)if modified and self.on_update is not None:self.on_update(self)return rv__setitem__ = calls_update('__setitem__')__delitem__ = calls_update('__delitem__')clear = calls_update('clear')popitem = calls_update('popitem')update = calls_update('update')del calls_updateclass CallbackDict(UpdateDictMixin, dict):"""A dict that calls a function passed every time something is changed.The function is passed the dict instance."""def __init__(self, initial=None, on_update=None):dict.__init__(self, initial or ())self.on_update = on_updatedef __repr__(self):return '<%s %s>' % (self.__class__.__name__,dict.__repr__(self))class SecureCookieSession(CallbackDict, SessionMixin):"""Base class for sessions based on signed cookies."""def __init__(self, initial=None):def on_update(self):self.modified = TrueCallbackDict.__init__(self, initial, on_update)self.modified = False
该字典其实就是继承了字典,并在其基础上定制了一些功能,如
class MyDict(dict):def __init__(self, initial):dict.__init__(self, initial)session = MyDict({'k1': 123})print(session, type(session)) # {'k1': 123} <class '__main__.MyDict'> session['k2'] = 'v2' print(session)
所以,Flask的视图函数中在对session进行操作时,其实就是在内存中修改一个字典的数据。
class SecureCookieSessionInterface(SessionInterface):"""The default session interface that stores sessions in signed cookiesthrough the :mod:`itsdangerous` module."""#: the salt that should be applied on top of the secret key for the#: signing of cookie based sessions.salt = 'cookie-session'#: the hash function to use for the signature. The default is sha1digest_method = staticmethod(hashlib.sha1)#: the name of the itsdangerous supported key derivation. The default#: is hmac.key_derivation = 'hmac'#: A python serializer for the payload. The default is a compact#: JSON derived serializer with support for some extra Python types#: such as datetime objects or tuples.serializer = session_json_serializersession_class = SecureCookieSessiondef get_signing_serializer(self, app):if not app.secret_key:return Nonesigner_kwargs = dict(key_derivation=self.key_derivation,digest_method=self.digest_method)return URLSafeTimedSerializer(app.secret_key, salt=self.salt,serializer=self.serializer,signer_kwargs=signer_kwargs)def open_session(self, app, request):s = self.get_signing_serializer(app)if s is None:return Noneval = request.cookies.get(app.session_cookie_name)if not val:return self.session_class()max_age = total_seconds(app.permanent_session_lifetime)try:data = s.loads(val, max_age=max_age)return self.session_class(data)except BadSignature:return self.session_class()def save_session(self, app, session, response):domain = self.get_cookie_domain(app)path = self.get_cookie_path(app)# Delete case. If there is no session we bail early.# If the session was modified to be empty we remove the# whole cookie.if not session:if session.modified:response.delete_cookie(app.session_cookie_name,domain=domain, path=path)return# Modification case. There are upsides and downsides to# emitting a set-cookie header each request. The behavior# is controlled by the :meth:`should_set_cookie` method# which performs a quick check to figure out if the cookie# should be set or not. This is controlled by the# SESSION_REFRESH_EACH_REQUEST config flag as well as# the permanent flag on the session itself.if not self.should_set_cookie(app, session):returnhttponly = self.get_cookie_httponly(app)secure = self.get_cookie_secure(app)expires = self.get_expiration_time(app, session)val = self.get_signing_serializer(app).dumps(dict(session))response.set_cookie(app.session_cookie_name, val,expires=expires, httponly=httponly,domain=domain, path=path, secure=secure)
业务处理
设置session
响应内容
响应内容其实就讲数据返回给用户,并且把内容中的session重新保存
执行xxx的save_session方法,将内存中的数据保存。