Perequisite:
- 什么是Cookie?
以最基本的官方文档中的示例flaskr.py中的代码片段中的session的用法为起点:
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('show_entries'))
return render_template('login.html', error=error)
首先,已经知道from flask import session中的session也是一个LocalProxy.
# globals.py
# session实际上同样是一个LocalProxy.
session = LocalProxy(partial(_lookup_req_object, 'session'))
def _lookup_req_object(name):
# session储存在RequestContext中。
top = _request_ctx_stack.top
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
那么,本质上session到底是什么呢,官方文档说,session是基于cookies的。
# Open the session at the moment that the request context is
# available. This allows a custom open_session method to use the
# request context (e.g. code that access database information
# stored on `g` instead of the appcontext).
# ----------------------------------------
# ctx.py RequestContext.push()
# 在RequestContext被推送之前,真正的session被Flask.open_session函数创建。
self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
# app.py
def open_session(self, request):
# 打开一个新的session,默认的实现是把所有session中的数据储存在一个
# cookie中,这就要求secret_key需要被设置. 我们可以通过代替session_interface
# 来自定义session.
return self.session_interface.open_session(self, request)
# app.py
# 默认情况,session_interface为SecureCookieSessionInterface()
# 可以自定义。
session_interface = SecureCookieSessionInterface()
# sessions.py//class SecureCookieSessionInterface()
def open_session(self, app, request):
# 获得签名序列化工具.
s = self.get_signing_serializer(app)
if s is None:
return None
# 查看request中是否有Cookies.
val = request.cookies.get(app.session_cookie_name)
# 如果没有Cookies, 直接返回session_class(), 默认的session是SecureCookieSession.
if not val:
return self.session_class()
# max_age被设置为31天.
max_age = total_seconds(app.permanent_session_lifetime)
try:
# 加载data.'val:aSerializedString'-->'data:aNormalString'
data = s.loads(val, max_age=max_age)
# 返回SecureCookieSession.
return self.session_class(data)
# 处理异常.
except BadSignature:
return self.session_class()
# sessions.py//class SecureCookieSessionInterface()
def get_signing_serializer(self, app):
# 必须保证app.secret_key被设置.
if not app.secret_key:
return None
# 设置选项
signer_kwargs = dict(
key_derivation=self.key_derivation,
digest_method=self.digest_method
)
# 返回一个itsdangerous中的URLSafaTimedSerializer对象.
return URLSafeTimedSerializer(app.secret_key, salt=self.salt,
serializer=self.serializer,
signer_kwargs=signer_kwargs)
# session.py
class SecureCookieSession(CallbackDict, SessionMixin):
"""Base class for sessions based on signed cookies."""
def __init__(self, initial=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.modified = False
当open_session执行完毕后,RequestContext.push()的self.session本质就真相大白也即是SecureCookieSession类.
在RequestContext.push()最后,
if self.session is None:
self.session = self.app.make_null_session()
于是self.session就被设置为NullSession.
# NullSession继承自SecureCookieSession.只不过所有的methods都被设置为抛出异常.
class NullSession(SecureCookieSession):
def _fail(self, *args, **kwargs):
raise RuntimeError('The session is unavailable because no secret '
'key was set. Set the secret_key on the '
'application to something unique and secret.')
__setitem__ = __delitem__ = clear = pop = popitem = \
update = setdefault = _fail
del _fail
至此,RequestContext push完毕,RequestContext的session储存了SecureCookieSession类实例.
Part2
接下来处理request,在处理/login对应的视图函数时,当我们写session['logged_in'] = True.就在session中设置了一对健值.
在process_response函数中:
# app.py/Flask.process_response.
def process_response(self, response):
...
# 此处完成对session的处理.
if not self.session_interface.is_null_session(ctx.session):
self.save_session(ctx.session, response)
return response
def is_null_session(self, obj):
"""Checks if a given object is a null session. Null sessions are
not asked to be saved.
This checks if the object is an instance of :attr:`null_session_class`
by default.
"""
# 检查这是不是一个Null session,Null session不会被理会.
return isinstance(obj, self.null_session_class)
接下来就是重要的save_session函数:
def save_session(self, app, session, response):
# 获取设置cookie需要的参数.
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
# 什么情况下不需要设置cookie.
if not self.should_set_cookie(app, session):
return
# 设置cookie需要的参数.
httponly = self.get_cookie_httponly(app)
secure = self.get_cookie_secure(app)
expires = self.get_expiration_time(app, session)
# 最重要的val
val = self.get_signing_serializer(app).dumps(dict(session))
# 在response中设置cookie.
response.set_cookie(app.session_cookie_name, val,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
下面结合客户端Requests库和服务端的flaskr应用来实验一下,理顺一下程序流.