Flask0.1源码阅读——请求处理和响应
用flask做了几个项目了,它的各个模块的功能也都基本了解,有时候为了解决某个bug会溯源到对应的源码里一看究竟,但一直都没有从整体上理解flask的工作流程。最近发现flask最初版本代码不过300多行,于是赶紧找出来阅读了一遍。
这篇笔记主要记录flask对于请求的处理和响应过程,忽略了关于模版渲染的内容。
数据结构
werkzeug
稍微浏览一遍源码你就会发现,werkzeug才是爸爸。flask中关键的几个模块全是从werkzeug中继承下来的。在这里就不溯源到werkzeug去分析了,只是了解werkzeug中的这些重要模块在flask中起的作用。
Request
Request类处理从environ变量中获取的变量,将其封装好供其他模块使用,我们经常使用到的Flask.request就是它的实例。
Response
Response类将响应函数的返回值包装为http响应
LocalStack
LocalStack类是一个线程隔离栈,被用于应用上下文和请求上下文。这是非常重要的一个概念,这篇文章讲的很清楚:Flask 的 Context 机制
Map、Rule
Map、Rule类将路由和视图函数及其参数绑定
Flask
class Flask(object):
request_class = Request
response_class = Response
static_path = '/static'
session_cookie_name = 'session'
secret_key = None
def __init__(self, package_name):
self.debug = False
self.package_name = package_name
self.root_path = _get_package_path(self.package_name)
self.view_functions = {}
self.error_handlers = {}
self.before_request_funcs = []
self.after_request_funcs = []
self.url_map = Map()
def open_session(self, request):
key = self.secret_key
if key is not None:
return SecureCookie.load_cookie(request, self.session_cookie_name,
secret_key=key)
def request_context(self, environ):
return _RequestContext(self, environ)
上面节选了些Flask类中定义成员变量的部分,可以看到,Flask直接利用了werkzeug的Request、Response、Map作为请求、响应、路由的成员变量。open_session将secret_key作为密钥,生成保存在cookie中的加密session。view_functions和error_handlers保存视图函数和错误处理函数与对应的路由。before_request_funcs和after_request_funcs分别保存请求预处理函数和响应的后续处理函数。
def add_url_rule(self, rule, endpoint, **options):
options['endpoint'] = endpoint
options.setdefault('methods', ('GET',))
self.url_map.add(Rule(rule, **options))
def route(self, rule, **options):
def decorator(f):
self.add_url_rule(rule, f.__name__, **options)
self.view_functions[f.__name__] = f
return f
return decorator
def errorhandler(self, code):
def decorator(f):
self.error_handlers[code] = f
return f
return decorator
上面三个函数处理视图函数与路由的绑定,add_url_rule利用Rule类将路由规则、视图函数、参数绑定到一起,存储到url_map中。
而route就是那个刚接触flask时觉得非常神器的装饰器。它的作用就是把被装饰的函数和路由规则作为参数调用了add_url_rule,并放入view_functions字典。下面两段代码效果一样。
@app.route('/')
def index():
pass
def index():
pass
app.add_url_rule('index', '/')
app.view_functions['index'] = index
_RequestContext
class _RequestContext(object):
"""The request context contains all request relevant information. It is created at the beginning of the request and pushed to the `_request_ctx_stack` and removed at the end of it. It will create the URL adapter and request object for the WSGI environment provided.
"""
def __init__(self, app, environ):
self.app = app
self.url_adapter = app.url_map.bind_to_environ(environ)
self.request = app.request_class(environ)
self.session = app.open_session(self.request)
self.g = _RequestGlobals()
self.flashes = None
def __enter__(self):
_request_ctx_stack.push(self)
def __exit__(self, exc_type, exc_value, tb):
# do not pop the request stack if we are in debug mode and an
# exception happened. This will allow the debugger to still
# access the request object in the interactive shell.
if tb is None or not self.app.debug:
_request_ctx_stack.pop()
我觉得这是一个非常重要的辅助类。它把werkzeug中的Request、Map、SecureCookie等依赖实例化为自己的成员函数,再使用python的上下文机制,将自己作为flask的请求上下文,压入LocalStack栈中。它传入的参数environ即是WSGI的environ参数,在WSGI中,environ变量包含了一个请求的所有信息,_RequestContext将environ解析请求的数据信息——request,和请求的路由信息——url_adapter,具体的解析过程都是在werkzeug中完成的。所以,在flask中,一个请求的所有信息都可以在request和url_adapter中找到。
处理流程
def wsgi_app(self, environ, start_response):
"""The actual WSGI application. This is not implemented in
`__call__` so that middlewares can be applied:
app.wsgi_app = MyMiddleware(app.wsgi_app)
:param environ: a WSGI environment
:param start_response: a callable accepting a status code,
a list of headers and an optional
exception context to start the response
"""
with self.request_context(environ): # 请求上下文
rv = self.preprocess_request() # 请求前, 预处理
if rv is None:
rv = self.dispatch_request() # 处理请求
response = self.make_response(rv) # 产生响应
response = self.process_response(response) # 返回响应前, 作清理工作
return response(environ, start_response)
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`"""
return self.wsgi_app(environ, start_response)
整个响应的处理流程在wsgi_app函数中描述的非常清除,依次来看:
请求上下文压入栈
Flask被调用时,会执行wsgi_app函数,首先把请求上下文,即之前提到的_RequestContext压入LocalStack栈。
请求预处理
def preprocess_request(self):
"""Called before the actual request dispatching and will
call every as :meth:`before_request` decorated function.
If any of these function returns a value it's handled as
if it was the return value from the view and further
request handling is stopped.
"""
for func in self.before_request_funcs:
rv = func() # 执行预处理函数
if rv is not None:
return rv
请求预处理阶段,通常是从请求中提取出一些信息放到请求上下文中,方便视图函数使用。比如从cookie中提取用户身份验证信息,得到对应的用户实例,保存在全局变量g中。
如果before_request_funcs列表中的某个函数有返回值,也就是在预处理阶段就产生了响应,那么就立即响应生成阶段。比如用户身份验证未通过,立即返回403错误,不再交给试图函数处理。
请求处理
def match_request(self):
"""Matches the current request against the URL map and also
stores the endpoint and view arguments on the request object
is successful, otherwise the exception is stored.
"""
rv = _request_ctx_stack.top.url_adapter.match()
request.endpoint, request.view_args = rv
return rv
def dispatch_request(self):
"""Does the request dispatching. Matches the URL and returns the
return value of the view or error handler. This does not have to
be a response object. In order to convert the return value to a
proper response object, call :func:`make_response`.
"""
try:
endpoint, values = self.match_request() # 请求匹配
return self.view_functions[endpoint](**values)
except HTTPException, e:
handler = self.error_handlers.get(e.code)
if handler is None:
return e
return handler(e)
except Exception, e:
handler = self.error_handlers.get(500)
if self.debug or handler is None:
raise
return handler(e)
处理请求时,首先调用match_request函数,从请求上下文的url_adapter中匹配到对应的路由规则和参数。再从view_functions字典中找出规则对应的请求函数,然后执行这个视图,并返回它的值。如果抛出了错误,先看看错误有没有在error_handlers字典中定义,有的话就调用相应的错误处理函数,没有就抛出500错误。
产生响应
def make_response(self, rv):
"""Converts the return value from a view function to a real
response object that is an instance of :attr:`response_class`.
The following types are allowd for `rv`:
======================= ===========================================
:attr:`response_class` the object is returned unchanged
:class:`str` a response object is created with the
string as body
:class:`unicode` a response object is created with the
string encoded to utf-8 as body
:class:`tuple` the response object is created with the
contents of the tuple as arguments
a WSGI function the function is called as WSGI application
and buffered as response object
======================= ===========================================
:param rv: the return value from the view function
"""
if isinstance(rv, self.response_class):
return rv
if isinstance(rv, basestring):
return self.response_class(rv)
if isinstance(rv, tuple):
return self.response_class(*rv)
return self.response_class.force_type(rv, request.environ)
make_response将请求处理阶段返回的值转换为Response对象
响应返回前的处理
def process_response(self, response):
"""Can be overridden in order to modify the response object
before it's sent to the WSGI server. By default this will
call all the :meth:`after_request` decorated functions.
:param response: a :attr:`response_class` object.
:return: a new response object or the same, has to be an
instance of :attr:`response_class`.
"""
session = _request_ctx_stack.top.session
if session is not None:
self.save_session(session, response) # 保存 session
for handler in self.after_request_funcs: # 请求结束后, 清理工作
response = handler(response)
return response
这里首先检查请求上下文中有没有session,如果有就调用save_session将session存到cookie中。然后把after_request_funcs列表中的函数取出,依次执行。
返回响应
这里的response(environ, start_response)
看接收的参数就很像WSGI标准的HTTP处理函数:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return '<h1>Hello, web!</h1>'
而response实例已经在make_response函数中,携带上了需要返回的值。