目录
flask 框架源码浅析结构(二)重点 WSGI 源代码分析
上一节,我们已经了解到了flask是个什么东西,以及里面的一些名词,我知道,对于大部分人来说,当然也包括我,刚开始接触这个东西就感觉到压力山大,但是,其实只要用心去走他的每一层 ,去抓每一个return,将其做成图,就能更好的便于我们理解与剖析flask框架的源码(直接切入正题)
Werkzeug(点这里到官方网站)
Werkzeug是什么:
官方网站对于werkzeug的定义的翻译了解它里面有什么,才能更好的用
一种交互式调试器,允许在浏览器中使用交互式解释器检查堆栈跟踪和源代码,以查看堆栈中的任何帧。
一个功能齐全的请求对象,包含与头、查询参数、表单数据、文件和cookie交互的对象。
可以包装其他wsgi应用程序并处理流数据的响应对象。
一种路由系统,用于将url与端点匹配并生成端点的url,具有一个可扩展的系统,用于从url捕获变量。
用于处理实体标记、缓存控制、日期、用户代理、cookie、文件等的http实用程序。
在本地开发应用程序时使用的线程化wsgi服务器。
一种测试客户端,用于在测试期间模拟http请求,而无需运行服务器。
Werkzeug支持Unicode,不强制任何依赖项。由开发人员来选择模板引擎、数据库适配器,甚至如何处理请求。它可以用于构建各种最终用户应用程序,如博客、wiki或公告板。
我截取了官方网站用来做测试的一段代码
#代码片段一
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response("Hello, World!")
if __name__ == "__main__":
from werkzeug.serving import run_simple
run_simple("localhost", 5000, application)
运行结果
如果我们在浏览器上访问 http://127.0.0.1:5000/上可以看到“hello,world"内容
然而我们要研究的,就是 这个用户访问http://127.0.0.1:5000/后浏览“Hello World"这个过程Flask的工作原理及代码框架
拆applicattion函数
- 首先我们发现在这个代码区中有个函数 application(request)来 那我们就打开它(其中一些我已经进行了翻译)
def application(environ, start_response):
response_body = 'The request method was %s' % environ['REQUEST_METHOD']
# HTTP response code and message
status = '200 OK'
# 应答的头部是一个列表,每对键值都必须是一个 tuple。
response_headers = [('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))]
# 调用服务器程序提供的 start_response,填入两个参数
start_response(status, response_headers)
# 返回必须是 iterable
return [response_body]
#2. 可调用对象是一个类实例
class AppClass:
"""这里的可调用对象就是 AppClass 的实例,使用方法类似于:
app = AppClass()
for result in app(environ, start_response):
do_somthing(result)
"""
def __init__(self):
pass
def __call__(self, environ, start_response):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield "Hello world!\n"
WSGI接口的作用是确保HTTP请求能够转化成python应用的一个功能调用,这也就是Gateway的意义所在,网关的作用就是在协议之前进行转换。
WSGI接口中有一个非常明确的标准,每个Python Web应用必须是可调用callable的对象且返回一个iterator,并实现了app(environ, start_response) 的接口,server 会调用 application,并传给它两个参数:environ 包含了请求的所有信息,start_response 是 application 处理完之后需要调用的函数,参数是状态码、响应头部还有错误信息
拆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:
# reset the first request information if the development server
# reset normally. This makes it possible to restart the server
# without reloader and that stuff from an interactive shell.
self._got_first_request = False
拆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)
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
我们拆开来看 四个步骤
-
ctx = self.request_context(environ)创建请求上下文,并把它推送到栈中,在上一篇有提及
-
response = self.full_dispatch_request()处理请求,通过flask的路由寻找对应的视图函数进行处理(上一篇应该有印象)
-
通过try…except封装处理步骤2的处理函数,如果有问题,抛出500错误。
-
ctx.auto_pop(error)当前请求退栈。
-
下来拆full_dispatch_request()
拆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)
来 我们拆开看
1.self.try_trigger_before_first_request_functions()触发第一次请求之前需要处理的函数,只会执行一次。
2. self.preprocess_request()触发用户设置的在请求处理之前需要执行的函数,这个可以通过@app.before_request来设置
3. rv = self.dispatch_request() 核心的处理函数,包括了路由的匹配,下面会展开来讲
4. rv = self.handle_user_exception(e) 处理异常
5.return self.finalize_request(rv),将返回的结果转换成Response对象并返回。
6. 接下来我们看dispatch_request函数:
拆dispatch_request
def dispatch_request(self):
"""此处省略翻译500字
"""
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
# if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically
if (
getattr(rule, "provide_automatic_options", False)
and req.method == "OPTIONS"
):
return self.make_default_options_response()
# otherwise dispatch to the handler for that endpoint
return self.view_functions[rule.endpoint](**req.view_args)
处理的逻辑如下:
- req = _request_ctx_stack.top.request获得请求对象,并检查有效性。
- 对于请求的方法进行判断,如果HTTP请求时OPTIONS类型且用户未设置provide_automatic_options=False,则进入默认的OPTIONS请求回应,否则请求endpoint匹配的函数执行,并返回内容。
在上述的处理逻辑中,Flask从请求上下文中获得匹配的rule,这是如何实现的呢,请看上节“上下文”。
歇会明天拆
附一张不完整的解码图