0%

flask学习笔记(五)

cookie与session[使用方法+源码剖析]

cookie
HTTP是无状态(stateless)协议,也就是说,在一次请求响应结束后,服务器不会留下任何关于对方状态的信息。但是对于某些Web程序来说,客户端的某些信息又必须被记住,比如用户的登录状态。可以通过Cookie技术在请求和响应报文中添加Cookie数据来保存客户端的信息。

如下例所示(可以顺便复习下(四)中请求与响应最后一部分内容):
1.当以游客身份访问主页时不需要保存状态信息,如index路由函数所示。
2.当访问登入页面时服务端会返回一个Cookie来作为已登入状态的凭据。[Cookie存放在浏览器中]
3.再次请求主页时发现请求中已经带了Cookie,此时身份为注册用户。
4.访问登出页面后Cookie被删除,再次访问主页,请求中已无Cookie,此时身份变为游客。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.route('/index')
def index():
res = make_response('主页')
return res

@app.route('/login')
def login():
res = make_response('登入')
res.set_cookie('username', 'lockcy')
return res

@app.route('/logout')
def logout():
res = make_response('登出')
res.delete_cookie('username')
return res

set_cookie()方法的参数

请求方式 详细介绍
key cookie的键(名称)
value cookie的值
max_age cookie被保存的时间数,单位为秒;默认在用户会话结束(即关闭浏览器)时过期
expires 具体的过期时间,一个datetime对象或UNIX时间戳
path 限制cookie只在给定的路径可用,默认为整个域名
domain 设置cookie可用的域名
secure 如果设为True,只有通过HTTPS才可以使用
httponly 如果设为True,禁止客户端JavaScript获取cookie

set_cookie和delete_cookie里的源码逻辑较为简单,即将设置的cookie放入响应头中,将响应头中的cookie清空,源码就不展示了

session
cookie可以存储用户的认证信息,但之前的例子中cookie是以明文形式保存的,恶意用户可以通过伪造cookie来获取他人账户权限,因此需要对包含敏感信息的cookie进行加密,flask中提供了session来加密存储信息。session是加密的,在使用session时需要设置密钥secret_key。
下例关于session的流程和cookie类似,就不赘述了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
app.secret_key = os.urandom(66)

@app.route('/index')
def index():
if session.get('name'):
return '首页,你好%s'%session.get('name')
return '首页,你好游客'

@app.route('/login')
def login():
session['name'] = 'lockcy'
return '登入'

@app.route('/logout')
def logout():
session.pop('name')
return '登出'

设置session:session[‘key’] = value
获取session:session.get(‘key’)
删除session:session.pop(‘key’)
之前在学习JAVAEE SSH框架时也接触过保存信息的session,那里的session就是一个Map来存键值对,猜想flask中的session也类似,跟进源码分析分析。
下列源码及导图转载自 https://www.cnblogs.com/liuxiangpy/p/10129618.html
1、用户请求进来后会执行app.__call__方法:

1
2
3
4
def __call__(self, environ, start_response):
#environ 是请求相关的所有数据(由wsgi做了初步的封装)
#start_respone 是用于设置响应相关的所有数据
return self.wsgi_app(environ, start_response)

2、执行def wsgi_app(self, environ, start_response)这个函数:

1
2
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)

2.1此时执行ctx = self.request_context(environ)
这是对请求相关的数据进行再次封装.
3、执行def request_context(self, environ):
这个函数里面 return RequestContext(self, environ) #返回了一个RequestContext实例化的对象
4、执行class RequestContext(object):
对请求数据进行一些列的封装,并且initrequest方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class RequestContext(object):
def __init__(self, app, environ, request=None):
"""
environ:是请求传过来的数据
"""
self.app = app
if request is None:
#如果request是空则把数据复制给request
request = app.request_class(environ)
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
#设置session为None
self.session = None

5、执行def wsgi_app函数下的ctx.push()
1)这里面的操作就是将ctx放到一个存放数据的地方
2)执行SecureCookieSessionInterface.open_session,去cookie中获取值并给ctx.session重新赋值
6、class RequestContext(object):类下的 def push(self):函数

1
2
3
4
5
6
7
8
9
10
11
#首先判断session是否为None,如果成立则
if self.session is None:
#在self.app.session_interface 等于是SecureCookieSessionInterface()实例化的对象
session_interface = self.app.session_interface
#然后通过通过session_interface调用SecureCookieSessionInterface下的open_session函数(方法)读取到session
self.session = session_interface.open_session(
self.app, self.request
)

if self.session is None:
self.session = session_interface.make_null_session(self.app)

7、执行类class SecureCookieSessionInterface(SessionInterface):
这个类里面包含了对session coooki的操作。def open_session(self, app, request):这个函数是读取session
这个函数def save_session(self, app, session, response):是保存session
7.1执行def open_session(self,app,request):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def open_session(self, app, request):
s = self.get_signing_serializer(app)
if s is None:
return None
#读取val这个val就是session的值,session_cookie_name这个就是配置文件的session_cookie_name
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()

8、执行def wsgi_app(self, environ, start_response):下的函数response = self.full_dispatch_request()
这段代码执行的是视图函数
9、执行def full_dispatch_request(self):

1
2
3
4
5
6
7
8
9
10
11
12
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
request_started.send(self)
#
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request() #调用视图函数
except Exception as e:
rv = self.handle_user_exception(e)
#视图函数执行完毕后,进行善后工作
return self.finalize_request(rv)

10、def finalize_request(self, rv, from_error_handler=False):函数

1
2
3
4
5
6
7
8
9
10
11
12
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv)
try:
#执行process_response
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

11、执行def process_response(self, response):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def process_response(self, response):
ctx = _request_ctx_stack.top
bp = ctx.request.blueprint
funcs = ctx._after_request_functions
if bp is not None and bp in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
if None in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs:
response = handler(response)
if not self.session_interface.is_null_session(ctx.session):
#这里执行了save_session
self.session_interface.save_session(self, ctx.session, response)
return response

12、执行class SecureCookieSessionInterface(SessionInterface):类下的def save_session(self, app, session, response):函数

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
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)
#对session进行加密,然后dumps编程字符串
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
)

流程
实例化Flask对象
设置路由,app里面有一个app.url_map
启动socket服务端
请求到来回执行app.__call__方法
然后执行wsgi_app方法
1.获取environ并对其进行再次封装
2.从environ中获取名称为session的cookie,解密,反序列化。
3.放在一个存数据的地方
4.执行视图函数
5.获取某个地方的session加密 =>写入cookie
6.存放在某个神奇的位置的相对应的session把它清空

思维导图

参考:https://www.cnblogs.com/liuxiangpy/p/10129618.html