Flask中Session的实现,关于session和cookie的原理读者可查阅其他资料,我的理解如下:
服务器会为每个新到来的连接创建一个sessionID,以此识别各个连接,另外服务器可以根据sessionID来找到保存在服务器端的这个连接之前保存的数据。这个功能有两个好处:将数据保存在服务器端更安全,相比客户端而言更不容易被攻击;二是由于http协议是无状态协议,即后一个连接不会带有前一个连接的任何信息。但如果有session,后面的连接可以在被处理之前,找到存放在服务器中的数据。那么每个连接的sessionID如何保存,它被保存在客户端的cookie中。cookie中存放的相当于一把钥匙,这把钥匙可以打开存放在服务器的东西。
在flask中,请求环境入栈的时候,有下面的代码
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)
如果请求环境的session为空,会根据请求和app中的信息创建session对象,如果创建失败会创建一个空的session
class SecureCookieSessionInterface(SessionInterface): """The default session interface that stores sessions in signed cookies through 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 sha1 digest_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_serializer session_class = SecureCookieSession def get_signing_serializer(self, app): if not app.secret_key: return None signer_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 None val = 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) # 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) 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 )
看上面代码中open_session方法,先根据app创建一个序列化对象s,然后查看request.cookies.get(app.session_cookie_name)是否存在,这个值就类似我们前面说的sessionID,如果值存在且通过s.loads方法反序列化成功,则return self.session_class(data),返回用data初始化的session实例。如果值不存在或者反序列化失败,都是return self.session_class()返回没有初始化的session实例。
注意flask此处储存用户session数据的方法简化了,前面说的服务器会储存用户的session数据,一般是用数据库存储。但在flask中,数据序列化后直接存储在用户端的cookie中,不过序列化数据能提供一定的保密性。
self.session_class(data)用到的session类是SecureCookieSession
class SecureCookieSession(CallbackDict, SessionMixin): """Base class for sessions based on signed cookies.""" def __init__(self, initial=None): def on_update(self): self.modified = True self.accessed = True super(SecureCookieSession, self).__init__(initial, on_update) self.modified = False self.accessed = False def __getitem__(self, key): self.accessed = True return super(SecureCookieSession, self).__getitem__(key) def get(self, key, default=None): self.accessed = True return super(SecureCookieSession, self).get(key, default) def setdefault(self, key, default=None): self.accessed = True return super(SecureCookieSession, self).setdefault(key, default)
SecureCookieSession继承自werkzeug.datastructures模块的CallbackDict,CallbackDict实现了每次字典发生修改时,都会调用之前传入的函数的功能。这个功能的实现来自CallbackDict的父类UpdateDictMixin
class UpdateDictMixin(object): """Makes dicts call `self.on_update` on modifications. .. versionadded:: 0.5 :private: """ on_update = None def 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 rv oncall.__name__ = name return oncall def setdefault(self, key, default=None): modified = key not in self rv = super(UpdateDictMixin, self).setdefault(key, default) if modified and self.on_update is not None: self.on_update(self) return rv def pop(self, key, default=_missing): modified = key in self if 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_update
可以看到,UpdateDictMixin类中所有可以修改数据的方法,都进行改写。首先定义了calls_update装饰器,这样像__setitem__实际是经过装饰后的方法,在装饰器中会找到同名的原方法调用,并且在self.on_update不为空的情况下,调用self.on_update。
回到flask中用到的SecureCookieSession,他定义了on_update方位为修改modified和accessed属性为真。这两个属相在后面保存session的时候会用到。