[toc]
我之前在Flask—werkzeug请求、响应源码分析中简单分析过werkzeug的请求、响应简单流程,这篇笔记就从Flask
的角度,去分析下session
的源码
首先我们先从一个用例入手
用例
from flask import Flask,session
app = Flask(__name__)
app.secret_key = 'sadfasdfasdf'
@app.route('/x1')
def index():
session['k1'] = 123
return "Index"
@app.route('/x2')
def order():
print(session.get("k2"))
return "Order"
if __name__ == '__main__':
app.run()
源码分析
首先从该行代码app.run()
入手
def run(self, host=None, port=None, debug=None, **options):
from werkzeug.serving import run_simple
if host is None:
host = '127.0.0.1'
if port is None:
server_name = self.config['SERVER_NAME']
if server_name and ':' in server_name:
port = int(server_name.rsplit(':', 1)[1])
else:
port = 5000
if debug is not None:
self.debug = bool(debug)
options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug)
try:
run_simple(host, port, self, **options)
finally:
self._got_first_request = False
首先看run
方法的参数def run(self, host=None, port=None, debug=None, **options)
主机、端口进行设置,options其他的参数(诸如processes、threaded、ssl_context等),然后去执行run_simple(host, port, self, **options)
我在文章开始贴出那篇博客,已经了解过werkzeug
的执行流程,这里就简单说下
在run_simple
方法中,通过判断去执行inner()
方法,然后就srv.serve_forever()
将服务运行起来
wsgi有两方,服务器方 和 应用程序
①服务器方:其调用应用程序,给应用程序提供(环境信息)和(回调函数), 这个回调函数是用来将应用程序设置的http header和status等信息传递给服务器方.
②应用程序:用来生成返回的header,body和status,以便返回给服务器方
我们在看下run_simple(host, port, self, **options)
代码,这个self
就是我们示例中的app
实例对象
app = Flask(__name__)
if __name__ == '__main__':
app.run()
根据wsgi
,当请求到来时,wsgi服务会去回调该对象(当然也可以是可回掉函数),我们知道在调用app
实例时,会调用该实例对象的__call__
方法,那我们就来看下该方法
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)
其调用应用程序,给应用程序提供(环境信息)和(回调函数), 这个回调函数是用来将应用程序设置的http header和status等信息传递给服务器方.
我们继续看下wsgi_app
方法
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
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)
在上述方法代码ctx = self.request_context(environ)
中,看看对请求
环境参数做了哪些处理呢?
def request_context(self, environ):
return RequestContext(self, environ)
继续跟踪方法return RequestContext(self, environ)
,我们来看下通过environ
是如何构造RequestContext
的,在__init__
中参看如下代码
request = app.request_class(environ)
然后上述一行代码定位到request_class = Request
,我们可以判断出app.request_class(environ)
所以是构造了一个Request
实例对象,将environ
参数传递过去,
接下来我们就看下Request
类,该类如下
class Request(RequestBase):
该类继承RequestBase
,当我们跟踪代码查看父类RequestBase
时,定位到如下代码,咦?奇怪为什么不是RequestBase
类,这是因为在flask\wrappers.py
模块中,做了命名处理
from werkzeug.wrappers import Request as RequestBase, Response as ResponseBase
,所以是继承了werkzeug\wrappers.py
下的Request
class Request(BaseRequest, AcceptMixin, ETagRequestMixin,
UserAgentMixin, AuthorizationMixin,
CommonRequestDescriptorsMixin):
Request是个多重继承类,以下是继承类的作用:
AcceptMixin
类:实现了请求内容协商的部分,比如请求接受的语言、编码格式、相应内容等
ETagRequestMixin
类:将实体标记和缓存描述符添加到请求对象或对象中,也提供对缓存控制头的访问
UserAgentMixin
类:向请求对象添加一个“UsRoAgent”属性,该对象包含作为请求触发浏览器的解析的用户代理。
AuthorizationMixin
类:添加属性,代表请求中的HTTP_AUTHORIZATION值
CommonRequestDescriptorsMixin
类:请求对象混合这个类将自动获得一对夫妇的描述符具有自动类型转换的HTTP报头
我们就来主要看下BaseRequest
类,以下是其初始方法,将environ
进行赋值在BaseRequest
进行封装
def __init__(self, environ, populate_request=True, shallow=False):
self.environ = environ
if populate_request and not shallow:
self.environ['werkzeug.request'] = self
self.shallow = shallow
回过头来我们继续看下__call__
方法中的wsgi_app
方法
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
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)
我们就分析完毕ctx = self.request_context(environ)
,然后我们看下面一行代码
ctx.push()
def push(self):
"省略一些代码"
self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
在上述代码self.app.open_session(self.request)
,跟踪代码,进入到Flask
类如下方法
def open_session(self, request):
return self.session_interface.open_session(self, request)
我们e是session_interface = SecureCookieSessionInterface()
然后session_interface.open_session(self, request)
,我们先看session_interface
类做了什么操作?
class SecureCookieSessionInterface(SessionInterface):
salt = 'cookie-session'
digest_method = staticmethod(hashlib.sha1)
key_derivation = 'hmac'
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 not session:
if session.modified:
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
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
方法
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()
在open_session
方法中,通过 s = self.get_signing_serializer(app)
获取一个<itsdangerous.URLSafeTimedSerializer object at 0x05DC9490>
对象用来进行序列化操作的
然后继续执行val = request.cookies.get(app.session_cookie_name)
代码,用来获取如下的键值对(我本机测试)
session=eyJrMSI6MTIzfQ.DcHu9w.Ko6qHGq3iopv0dWG5YDGixnKtU4
然后通过data = s.loads(val, max_age=max_age)
获取我们设置的session
值(我自己本机测试格式如下)
{'k1': 123}
然后通过self.session_class(data)
封装成如下的对象
<SecureCookieSession {'k1': 123}>
如果请求没有带来session,会返回self.session_class()
对象,
session_class = SecureCookieSession
我们继续回过头来看下wsgi_app
方法,以上的简单梳理就是执行了如下的2行代码
ctx = self.request_context(environ)
ctx.push()
然后看wsgi_app
方法try
内部代码段
try:
try:
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)
在response = self.full_dispatch_request()
中
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)
Flask将调用full_dispatch_request函数进行请求的分发,之所以不用给参数,是因为我们可以通过request对象获得这次请求的信息。full_dispatch_request将根据请求的url找到对应的蓝本里面的视图函数,并生成一个response对象
是如何生成response呢?
通过rv = self.dispatch_request()
获取要进行转发的当前的内容,然后通过self.finalize_request(rv)
生成最终对象,并且返回
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
在finalize_request
方法中,通过response = self.make_response(rv)
构造response对象
response_class = Response
class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin,
CommonResponseDescriptorsMixin,
WWWAuthenticateMixin):
最后在wsgi_app中return response(environ, start_response)
返回即可
然后会去执行werkzeug
下的__call__
方法
def __call__(self, environ, start_response):
app_iter, status, headers = self.get_wsgi_response(environ)
start_response(status, headers)
return app_iter
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
if headers_sent:
reraise(*exc_info)
finally:
exc_info = None
elif headers_set:
raise AssertionError('Headers already set')
headers_set[:] = [status, response_headers]
return write
start_response返回write方法,然后跟踪该方法,最终写回浏览器
以上就是简单的流程