文章目录
- 1 cookie和session
- 2 flask中使用session
- 3 剖析flask内部session实现原理
- 3.1 重点分析SecureCookieSessionInterface类
- 3.2 请求进来后,开始准备响应
- 4 小结
1 cookie和session
http是无状态的,对于一些场景,比如购物网站,加购物车,付款这些过程需要记录前一个页面的状态,假如你选了一购物车的货物,准备去付款页面付款,此时你的商品信息全部都没有了,这是行不通的。为了保持客户端与服务器之间的会话状态,就有了cookie和session。
不严谨的说,cookie是存储在浏览器端的一些信息,session是存储在服务器端的信息,session存储后会生成一个sessionID,浏览器通过cookie把sessionID发送到服务器,服务器判断这个sessionID里面存储的信息,把信息给这个浏览器,如果浏览器没有sessionID,那么访问服务器后,服务器会为这个浏览器新建一个sessionID。
本文章的重点不是剖析session和cookie,而是研究一下flask的session是怎么实现的。
2 flask中使用session
from flask import Flask, session
app = Flask(__name__)
#RuntimeError: The session is unavailable because no secret key was set.
#Set the secret_key on the application to something unique and secret.
app.config['SECRET_KEY'] = 'secret key xxx'
@app.route('/set_session')
def set_session():
session['key'] = 'value'
return "set_cookie_success"
@app.route('/get_session')
def get_session():
try:
session_value = session['key']
except KeyError:
session_value = None
if session_value:
return session_value
else:
return 'session key not found'
if __name__ == '__main__':
app.run()
通过session可以在不同的请求之间共享数据,上面图片所示就是写入session和获取session的过程
带着几个问题:
- 为什么session可以像字典一样操作?
- 使用session为什么需要secret key?
- 通过开发者工具会发现session出现在了浏览器的cookie中,为什么?
3 剖析flask内部session实现原理
"""
===============================step1==================================
从from flask import session开始
"""
# globals.py中session是一个全局变量,并且也是一个本地代理对象
session: "SessionMixin" = LocalProxy( # type: ignore
partial(_lookup_req_object, "session")
)
"""
===============================step2==================================
请求刚进来,ctx.py模块中的class RequestContext:中定义了session
"""
class RequestContext:
def __init__(
self,
app: "Flask",
environ: dict,
request: t.Optional["Request"] = None,
session: t.Optional["SessionMixin"] = None,) # session被初始化为None
"""
===============================step3==================================
在RequestContext对象被push到_request_ctx_stack中后
"""
# RequestContext类的push方法如下:
def push(self)
...
# 请求刚进来的时候session就是None
if self.session is None:
# 代码执行到这,这里定义了session的接口,很巧妙,只要session_interface 类
# 执行open_session方法就可以
# 跳转到Flask类中
"""
===================step3.1==================
self.app 是Flask类的实例
在app.py模块中Flask类中有下面的代码
session_interface = SecureCookieSessionInterface()
session_interface 就是SecureCookieSessionInterface类的一个实例
"""
session_interface = self.app.session_interface
"""
===================step3.2==================
调用SecureCookieSessionInterface类的open_session方法,将app对象和request对象作为参数传递
"""
self.session = session_interface.open_session(self.app, self.request)
if self.session is None:
self.session = session_interface.make_null_session(self.app)
...
3.1 重点分析SecureCookieSessionInterface类
class SecureCookieSessionInterface(SessionInterface):
"""通过签名cookie方式存储session,依赖itsdangerous模块"""
#: 用于加密session的“盐值”
salt = "cookie-session"
#: 签名算法,默认使用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: "Flask"
) -> t.Optional[URLSafeTimedSerializer]:
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: "Flask", request: "Request"
) -> t.Optional[SecureCookieSession]:
"""
===============================step4=============================
open_session最后返回的是session_class,也就是说在请求被推进上下文的时候,创建了一个session_class
对象
session_class = SecureCookieSession
"""
s = self.get_signing_serializer(app)
if s is None:
return None
val = request.cookies.get(self.get_cookie_name(app))
if not val:
return self.session_class()
max_age = int(app.permanent_session_lifetime.total_seconds())
try:
data = s.loads(val, max_age=max_age)
return self.session_class(data)
except BadSignature:
return self.session_class()
"""
===============================step5=============================
看SecureCookieSession是个什么
class SecureCookieSession(CallbackDict, SessionMixin):
class CallbackDict(UpdateDictMixin, dict):
class UpdateDictMixin(dict):
没错SecureCookieSession就是继承了字典,所以session具有类似字典的功能
"""
def save_session(
self, app: "Flask", session: SessionMixin, response: "Response"
) -> None:
name = self.get_cookie_name(app)
domain = self.get_cookie_domain(app)
path = self.get_cookie_path(app)
secure = self.get_cookie_secure(app)
samesite = self.get_cookie_samesite(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(
name, domain=domain, path=path, secure=secure, samesite=samesite
)
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)
expires = self.get_expiration_time(app, session)
"""
这句是关键,将session转换成字段后进行序列化
"""
val = self.get_signing_serializer(app).dumps(dict(session)) # type: ignore
"""
最后通过cookie发送到客户端
"""
response.set_cookie(
name,
val, # type: ignore
expires=expires,
httponly=httponly,
domain=domain,
path=path,
secure=secure,
samesite=samesite,
)
3.2 请求进来后,开始准备响应
ctx = self.request_context(environ)
error: t.Optional[BaseException] = None
try:
try:
ctx.push()
# 主要看这句
response = self.full_dispatch_request()
"""
===============================step6=============================
response = self.full_dispatch_request()
"""
def full_dispatch_request(self) -> Response:
return self.finalize_request(rv)
def finalize_request(
self,
rv: t.Union[ResponseReturnValue, HTTPException],
from_error_handler: bool = False,
) -> Response:
response = self.make_response(rv)
try:
response = self.process_response(response)
def process_response(self, response: Response) -> Response:
if not self.session_interface.is_null_session(ctx.session):
self.session_interface.save_session(self, ctx.session, response)
"""
===============================step7=============================
又回到了save_session
"""
4 小结
简单总结一下,session和request一样,都是请求上下文对象的一个属性。
当请求进来后,session开辟一个继承自字典的对象,用于保存信息,处理请求过程中,更新session对象的内容,最后通过cookie的方式发送到浏览器,说通俗一点,flask是通过加密cookie来保存session信息的。
只要满足SessionInterface接口的类就可以用于flask的session处理,虽然我不能完全理解所有细节,但是源码写的是真的妙