一、简介
flask中session组件可分为内置的session组件还有第三方flask-session组件,内置的session组件功能单一,而第三方的flask-sessoin可支持redis、memcached、文件等进行session的存储。以下将介绍内置session以及第三方session组件的使用方法以及处理机制。
二、内置session处理机制
Cookie与Session
Cookie:
Cookie意为“甜饼”,是由W3C组织提出,最早由Netscape社区发展的一种机制。目前Cookie已经成为标准,所有的主流浏览器如IE、Netscape、Firefox、Opera等都支持Cookie。由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证吧,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了。这就是Cookie的工作原理。
Cookie实际上是一小段的文本信息。客户端请求服务器,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态。服务器还可以根据需要修改Cookie的内容
Session:
Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了,实质上session就是保存在服务器端的键值对。
如果说Cookie机制是通过检查客户身上的“通行证”来确定客户身份的话,那么Session机制就是通过检查服务器上的“客户明细表”来确认客户身份。Session相当于程序在服务器上建立的一份客户档案,客户来访的时候只需要查询客户档案表就可以了。
第一次请求,session的创建过程
在flask上下文中介绍了,请求到flask框架会执行wsgi_app方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def wsgi_app( self , environ, start_response):
ctx = self .request_context(environ) #实例化生成RequestContext对象
error = None
try :
try :
ctx.push() #push上下文到LocalStack中
response = self .full_dispatch_request() #执行视图函数过程
except Exception as e:
error = e
response = self .handle_exception(e) #处理异常
except :
error = sys.exc_info()[ 1 ]
raise
return response(environ, start_response)
finally :
if self .should_ignore_error(error):
error = None
ctx.auto_pop(error) # 删除LocalStack中的数据
|
在改方法中会生成一个ctx也就是RequestContext对象:
1
2
3
4
5
6
7
8
9
|
class RequestContext( object ):
def __init__( self , app, environ, request = None ):
self .app = app # app对象
if request is None :
request = app.request_class(environ)
self .request = request # 封装request
self .url_adapter = app.create_url_adapter( self .request)
self .flashes = None
self .session = None #一开始的session
|
在这个对象中封装了session,最初为None。接着在wsgi_app中执行ctx.push:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def push( self ):
app_ctx = _app_ctx_stack.top # 获取app上下文
if app_ctx is None or app_ctx.app ! = self .app:
app_ctx = self .app.app_context() #将app上下文push到app_ctx对于的LocalStack中
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 )
if self .session is None : # 判断session是否为None,一开始为None
session_interface = self .app.session_interface # 获取操作session的对象
self .session = session_interface.open_session( #调用open_session 创建session
self .app, self .request
)
if self .session is None :
self .session = session_interface.make_null_session( self .app)
|
这里我们主要关注session,前面的代码在上下文中已经进行了相关说明,这里有个判断session是否为None,刚开始RequestContext中的session为None,所以条件成立,此时执行以下语句:
1
2
3
4
5
6
|
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_interface = self.app.session_interface,self.app.session_interface就是app中的session_interface属性:
1
|
session_interface = SecureCookieSessionInterface()
|
默认是一个SecureCookieSessionInterface()对象,该对象的内部主要实现了open_session和save_session用于使用和保存session。接着self.session被重新赋值为session_interface.open_session(self.app, self.request)方法返回的值,以下为open_session源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def open_session( self , app, request):
s = self .get_signing_serializer(app) #根据app.secret_key获取签名算法
if s is None :
return None
# 根据配置中的session_cookie_name获取session对于的值
val = request.cookies.get(app.session_cookie_name) #如果request.cookies为空,val为空
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()
|
该方法返回self.session_class(),当请求第一次来时,request.cookies为None,所以val也为None,返回self.session_class(),而session_class又是SecureCookieSession:
1
|
session_class = SecureCookieSession
|
所以我们继续看SecureCookieSession:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class SecureCookieSession(CallbackDict, SessionMixin):
accessed = False
def __init__( self , initial = None ):
def on_update( self ):
self .modified = True
self .accessed = True
super (SecureCookieSession, self ).__init__(initial, on_update)
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)
|
该类继承了CallbackDict, SessionMixin我们继续来看看CallbackDict:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class 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_update
def __repr__( self ):
return '<%s %s>' % (
self .__class__.__name__,
dict .__repr__( self )
)
|
也就是说SecureCookieSession继承了CallbackDict而CallbackDict继承了原生的dict,所以我们可以认为SecureCookieSession是一个特殊的字典,是调用了SecureCookieSessionInterface类中open_session返回的特殊字典,经过进一步分析self.session此时就是这个字典,这也意味着session在执行open_session方法时候被创建了,并保存在ctx中,也就是在RequestContext对象中,当我们使用session时候是通过全局变量session = LocalProxy(partial(_lookup_req_object, 'session'))由LocalProxy对象从ctx中获取到session。
第二次请求
开始我们知道session第一次请求来的时候是在open_session方法之后被创建,当第二次请求时,此时在open_session方法中,val已经不在是None,此时获取cookie的有效时长,如果cookie依然有效,通过与写入时同样的签名算法将cookie中的值解密出来并写入字典并返回,若cookie已经失效,则仍然返回'空字典',这样以来在第二次请求中就能获取到之前保存的session数据。
session生命周期
我们介绍了session创建时候是在ctx.push时候开始创建,也就是说在这之后我们就可以使用session,对它进行操作了,那么session什么时候保存呢?我们接下来继续看wsgi_app:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def wsgi_app( self , environ, start_response):
ctx = self .request_context(environ)
error = None
try :
try :
ctx.push()
response = self .full_dispatch_request()
except Exception as e:
error = e
response = self .handle_exception(e)
except :
error = sys.exc_info()[ 1 ]
raise
return response(environ, start_response)
finally :
if self .should_ignore_error(error):
error = None
ctx.auto_pop(error)
|
生成session后,接着执行self.full_dispatch_request():
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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
"""
self .try_trigger_before_first_request_functions() #执行app.before_first_reques钩子函数
try :
request_started.send( self ) # 触发request_started信号
rv = self .preprocess_request() # 执行before_request钩子函数
if rv is None :
rv = self .dispatch_request() # 执行视图函数
except Exception as e:
rv = self .handle_user_exception(e)
return self .finalize_request(rv)
|
这一部分先执行钩子app.before_first_reques在触发request_started信号,再执行before_request钩子函数,然后在执行视图函数,rv是执行完视图函数的返回值,最后执行finalize_request,这里的session保存就发生在这里:
1
2
3
4
5
6
7
8
9
10
11
|
def finalize_request( self , rv, from_error_handler = False ):
response = self .make_response(rv)
try :
response = self .process_response(response)
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
|
注意这里的在最后会session判断是否为空,会执行save_session方法,也就是SecureCookieSessionInterface的save_session方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
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)
samesite = self .get_cookie_samesite(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,
samesite = samesite
)
|
该方法最后保存的session调用的response.set_cookie,其实是将数据保存在cookie中,也就是在客户端的浏览器中,并非在服务端进行数据的保存,当请求完毕后会执行ctx.auto_pop(error)这时候会从上下文中将session和request删除,到此,session的生命周期结束。
视图函数使用session
在介绍flask的上下文中就已经对session进行过介绍,其本质也是通过LocalProxy操作上下文从而设置session,我们以session['username']='wd'作为列子,首先根据
1
|
session = LocalProxy(partial(_lookup_req_object, 'session' ))
|
session是一个LocalProxy对象,执行session['username']=‘wd'则执行LocalProxy对象的__setitem__方法,而__setitem__方法中则是调用_get_current_object获取ctx中的session对象,而其对象本质是一个特殊的字典,相当于在字典中加一对key,value。
小结
flask内置session本质上依靠上下文,当请求到来时,调用session_interface中的open_session方法解密获取session的字典,并保存在RequestContext.session中,也就是上下文中,然后在视图函数执行完毕后调用session_interface的save_session方法,将session以加密的方式写入response的cookie中,浏览器再保存数据。而第三方的session组件原理就是基于是open_session方法和save方法,从而实现session更多的session保存方案。
三、第三方组件flask-session
flask-session支持多种数据库session保存方案如:redis、memchached、mongodb甚至文件系统等。官方文档: https://pythonhosted.org/Flask-Session/
安装:
1
|
pip3 install flask - session
|
redis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
import redis
from flask import Flask, session
from flask_session import Session
from datetime import timedelta
app = Flask(__name__)
app.debug = True
app.secret_key = 'adavafa'
app.config[ 'SESSION_TYPE' ] = 'redis' # session类型为redis
app.config[ 'SESSION_PERMANENT' ] = True # 如果设置为True,则关闭浏览器session就失效。
app.config[ 'PERMANENT_SESSION_LIFETIME' ] = timedelta(seconds = 20 )
#一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
app.config[ 'SESSION_USE_SIGNER' ] = False # 是否对发送到浏览器上session的cookie值进行加密,默认False
app.config[ 'SESSION_KEY_PREFIX' ] = 'flask-session' # 保存到redis中的key的前缀
app.config[ 'SESSION_COOKIE_NAME' ] = 'session_id' # 保存在浏览器的cookie名称
app.config[ 'SESSION_REDIS' ] = redis.Redis(host = '10.1.210.33' , port = ‘ 6379 ',password=‘123123' ) # 用于连接redis的配置
#其他配置,不经常使用
app.config[ 'SESSION_COOKIE_DOMAIN' ] = '127.0.0.1' # 设置cookie的域名,不建议设置默认为server_name
app.config[ 'SESSION_COOKIE_PATH' ] = '/' # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
app.config[ 'SESSION_COOKIE_HTTPONLY' ] = True # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
Session(app)
@app .route( '/login' )
def index():
session[ "username" ] = "jack"
return 'login'
if __name__ = = '__main__' :
app.run()
|
Memchached
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import memcache
from flask import Flask, session
from flask_session import Session
from datetime import timedelta
app = Flask(__name__)
app.debug = True
app.secret_key = 'adavafa'
app.config[ 'SESSION_TYPE' ] = ‘memcached' # session类型为memcached
app.config[ 'SESSION_PERMANENT' ] = True # 如果设置为True,则关闭浏览器session就失效。
app.config[ 'PERMANENT_SESSION_LIFETIME' ] = timedelta(seconds = 20 )
#一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
app.config[ 'SESSION_USE_SIGNER' ] = False # 是否对发送到浏览器上session的cookie值进行加密,默认False
app.config[ 'SESSION_KEY_PREFIX' ] = 'flask-session' # 保存到缓存中的key的前缀
app.config[ 'SESSION_COOKIE_NAME' ] = 'session_id' # 保存在浏览器的cookie名称
app.config[ 'SESSION_MEMCACHED' ] = memcache.Client([ '10.1.210.33:12000' ]) #连接
#其他配置,不经常使用
app.config[ 'SESSION_COOKIE_DOMAIN' ] = '127.0.0.1' # 设置cookie的域名,不建议设置默认为server_name
app.config[ 'SESSION_COOKIE_PATH' ] = '/' # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
app.config[ 'SESSION_COOKIE_HTTPONLY' ] = True # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
Session(app)
@app .route( '/login' )
def index():
session[ "username" ] = "jack"
return 'login'
if __name__ = = '__main__' :
app.run()
|
Filesystem
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
from flask import Flask, session
from flask_session import Session
from datetime import timedelta
app = Flask(__name__)
app.debug = True
app.secret_key = 'adavafa'
app.config[ 'SESSION_TYPE' ] = 'filesystem' # session类型为filesystem
app.config[ 'SESSION_FILE_DIR' ] = '/opt/db' #文件保存目录
app.config[ 'SESSION_FILE_THRESHOLD' ] = 300 # 存储session的个数如果大于这个值时,开始删除
app.config[ 'SESSION_PERMANENT' ] = True # 如果设置为True,则关闭浏览器session就失效。
app.config[ 'PERMANENT_SESSION_LIFETIME' ] = timedelta(seconds = 20 )
#一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
app.config[ 'SESSION_USE_SIGNER' ] = False # 是否对发送到浏览器上session的cookie值进行加密,默认False
app.config[ 'SESSION_KEY_PREFIX' ] = 'flask-session' # 保存到文件中的key的前缀
app.config[ 'SESSION_COOKIE_NAME' ] = 'session_id' # 保存在浏览器的cookie名称
#其他配置,不经常使用
app.config[ 'SESSION_COOKIE_DOMAIN' ] = '127.0.0.1' # 设置cookie的域名,不建议设置默认为server_name
app.config[ 'SESSION_COOKIE_PATH' ] = '/' # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
app.config[ 'SESSION_COOKIE_HTTPONLY' ] = True # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
Session(app)
@app .route( '/login' )
def index():
session[ "username" ] = "jack"
return 'login'
if __name__ = = '__main__' :
app.run()
|
mongodb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
import pymongo
from flask import Flask, session
from flask_session import Session
from datetime import timedelta
app = Flask(__name__)
app.debug = True
app.secret_key = 'adavafa'
app.config[ 'SESSION_TYPE' ] = 'mongodb' # session类型为mongodb
app.config[ 'SESSION_MONGODB' ] = pymongo.MongoClient( 'localhost' , 27017 )
app.config[ 'SESSION_MONGODB_DB' ] = '数据库名称'
app.config[ 'SESSION_MONGODB_COLLECT' ] = '表名称'
app.config[ 'SESSION_PERMANENT' ] = True # 如果设置为True,则关闭浏览器session就失效。
app.config[ 'PERMANENT_SESSION_LIFETIME' ] = timedelta(seconds = 20 )
#一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
app.config[ 'SESSION_USE_SIGNER' ] = False # 是否对发送到浏览器上session的cookie值进行加密,默认False
app.config[ 'SESSION_KEY_PREFIX' ] = 'flask-session' # 保存的session的key的前缀
app.config[ 'SESSION_COOKIE_NAME' ] = 'session_id' # 保存在浏览器的cookie名称
#其他配置,不经常使用
app.config[ 'SESSION_COOKIE_DOMAIN' ] = '127.0.0.1' # 设置cookie的域名,不建议设置默认为server_name
app.config[ 'SESSION_COOKIE_PATH' ] = '/' # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
app.config[ 'SESSION_COOKIE_HTTPONLY' ] = True # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
Session(app)
@app .route( '/login' )
def index():
session[ "username" ] = "jack"
return 'login'
if __name__ = = '__main__' :
app.run()
|
sqlalchemy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
import redis
from flask import Flask, session
from flask_session import Session
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.debug = True
app.secret_key = 'adavafa'
# 设置数据库链接
app.config[ 'SQLALCHEMY_DATABASE_URI' ] = 'mysql+pymysql://root:dev@127.0.0.1:3306/devops?charset=utf8'
app.config[ 'SQLALCHEMY_TRACK_MODIFICATIONS' ] = True
# 实例化SQLAlchemy
db = SQLAlchemy(app)
app.config[ 'SESSION_TYPE' ] = 'sqlalchemy' # session类型为sqlalchemy
app.config[ 'SESSION_SQLALCHEMY' ] = db # SQLAlchemy对象
app.config[ 'SESSION_SQLALCHEMY_TABLE' ] = '表名' # session要保存的表名称
app.config[ 'SESSION_PERMANENT' ] = True # 如果设置为True,则关闭浏览器session就失效。
app.config[ 'PERMANENT_SESSION_LIFETIME' ] = timedelta(seconds = 20 )
#一个持久化的会话的生存时间,是一个datetime.timedelta对象,也可以用一个整数来表示秒,前提设置了PERMANENT_SESSION_LIFETIME为True
app.config[ 'SESSION_USE_SIGNER' ] = False # 是否对发送到浏览器上session的cookie值进行加密,默认False
app.config[ 'SESSION_KEY_PREFIX' ] = 'flask-session' # 保存的session的key的前缀
app.config[ 'SESSION_COOKIE_NAME' ] = 'session_id' # 保存在浏览器的cookie名称
#其他配置,不经常使用
app.config[ 'SESSION_COOKIE_DOMAIN' ] = '127.0.0.1' # 设置cookie的域名,不建议设置默认为server_name
app.config[ 'SESSION_COOKIE_PATH' ] = '/' # 会话cookie的路径。 如果未设置,则cookie将对所有url有效,默认为'/'
app.config[ 'SESSION_COOKIE_HTTPONLY' ] = True # 是否启动httponly,默认为true,为了防止xss脚本访问cookie
Session(app)
@app .route( '/login' )
def index():
session[ "username" ] = "jack"
return 'login'
if __name__ = = '__main__' :
app.run()
###使用SQLAlchemy时候先确保数据库和表都存在
在命令行中创建表
#>>> from app import db
#>>> db.create_all()
|
原理
这里以redis作为session存储方案做分析,以下是RedisSessionInterface源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
class RedisSessionInterface(SessionInterface):
serializer = pickle
session_class = RedisSession
def __init__( self , redis, key_prefix, use_signer = False , permanent = True ):
if redis is None :
from redis import Redis
redis = Redis()
self .redis = redis
self .key_prefix = key_prefix
self .use_signer = use_signer
self .permanent = permanent
def open_session( self , app, request):
sid = request.cookies.get(app.session_cookie_name)
if not sid:
sid = self ._generate_sid()
return self .session_class(sid = sid, permanent = self .permanent)
if self .use_signer:
signer = self ._get_signer(app)
if signer is None :
return None
try :
sid_as_bytes = signer.unsign(sid)
sid = sid_as_bytes.decode()
except BadSignature:
sid = self ._generate_sid()
return self .session_class(sid = sid, permanent = self .permanent)
if not PY2 and not isinstance (sid, text_type):
sid = sid.decode( 'utf-8' , 'strict' )
val = self .redis.get( self .key_prefix + sid)
if val is not None :
try :
data = self .serializer.loads(val)
return self .session_class(data, sid = sid)
except :
return self .session_class(sid = sid, permanent = self .permanent)
return self .session_class(sid = sid, permanent = self .permanent)
def save_session( self , app, session, response):
domain = self .get_cookie_domain(app)
path = self .get_cookie_path(app)
if not session:
if session.modified:
self .redis.delete( self .key_prefix + session.sid)
response.delete_cookie(app.session_cookie_name,
domain = domain, path = path)
return
httponly = self .get_cookie_httponly(app)
secure = self .get_cookie_secure(app)
expires = self .get_expiration_time(app, session)
val = self .serializer.dumps( dict (session))
self .redis.setex(name = self .key_prefix + session.sid, value = val,
time = total_seconds(app.permanent_session_lifetime))
if self .use_signer:
session_id = self ._get_signer(app).sign(want_bytes(session.sid))
else :
session_id = session.sid
response.set_cookie(app.session_cookie_name, session_id,
expires = expires, httponly = httponly,
domain = domain, path = path, secure = secure)
|
分析:RedisSessionInterface继承了SessionInterface
1
2
3
4
5
6
7
8
9
10
|
class SessionInterface(FlaskSessionInterface):
def _generate_sid( self ):
return str (uuid4())
def _get_signer( self , app):
if not app.secret_key:
return None
return Signer(app.secret_key, salt = 'flask-session' ,
key_derivation = 'hmac' )
|
而SessionInterface又继承了FlaskSessionInterface,而FlaskSessionInterface又继承了flask内置的SessionInterface,并且RedisSessionInterface重写了内置session的open_session和save_session.
首先是RedisSessionInterface实例化用于初始化配置,例如redis的连接、签名配置、过期配置、前缀配置等。
接下来看两个核心方法:open_session方法和save_session方法。
open_session:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
def open_session( self , app, request):
sid = request.cookies.get(app.session_cookie_name) #获取session id
if not sid: #判断session id是否为空,为空表示第一次请求
sid = self ._generate_sid() # 返回使用uuid4随机字符串
return self .session_class(sid = sid, permanent = self .permanent)
if self .use_signer: # 判断签名配置
signer = self ._get_signer(app)
if signer is None :
return None
try :
sid_as_bytes = signer.unsign(sid) # 对session id 进行加密签名
sid = sid_as_bytes.decode()
except BadSignature:
sid = self ._generate_sid()
return self .session_class(sid = sid, permanent = self .permanent)
if not PY2 and not isinstance (sid, text_type):
sid = sid.decode( 'utf-8' , 'strict' )
val = self .redis.get( self .key_prefix + sid) # 获取seession数据
if val is not None :
try :
data = self .serializer.loads(val) # 反序列化数据
return self .session_class(data, sid = sid) #返回
except :
return self .session_class(sid = sid, permanent = self .permanent)
return self .session_class(sid = sid, permanent = self .permanent)
|
改方法先从cookie中获取session id,然后对session id判断是否为空,为空表示第一次请求,则通过self._generate_sid()返回随机字符串,作为返回给浏览器的sessionid
1
2
|
def _generate_sid( self ):
return str (uuid4())
|
接着判断签名判断是否为true,然后对session 进行签名,这里和内置session不同的是获取session的时候通过self.redis.get(self.key_prefix + sid)在redis中进行获取。
save_session:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
def save_session( self , app, session, response):
domain = self .get_cookie_domain(app) # 获取cookie中的域名
path = self .get_cookie_path(app) # 获取cookie 中path
if not session: # 判断有误session对象
if session.modified: #没有但是被修改了,表示已经被删除了
self .redis.delete( self .key_prefix + session.sid) #清空session
response.delete_cookie(app.session_cookie_name,
domain = domain, path = path)
return
httponly = self .get_cookie_httponly(app)
secure = self .get_cookie_secure(app)
expires = self .get_expiration_time(app, session)
val = self .serializer.dumps( dict (session))
self .redis.setex(name = self .key_prefix + session.sid, value = val,
time = total_seconds(app.permanent_session_lifetime)) #保存session
if self .use_signer:
session_id = self ._get_signer(app).sign(want_bytes(session.sid))
else :
session_id = session.sid
response.set_cookie(app.session_cookie_name, session_id, # 设置cookie
expires = expires, httponly = httponly,
domain = domain, path = path, secure = secure)
|
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://www.cnblogs.com/wdliu/p/10173548.html