flask源码剖析
这段时间想重新写个自己的博客系统,又正好在看一些框架源码,然后就想要不顺便写个小框架吧,既然想写框架,要不再顺便写个orm吧,再写个小的异步Server吧。。事实证明饭要一口一口吃
先梳理一下flask工作的整个流程吧。
首先flask是符合wsgi协议的,那么也就是说,flask要实现一个可以callable的object给web server。
那么什么是wsgi? 简而言之,wsgi就是把框架和web Server协同工作的一个协议,规范。流程大概是:当有请求来的时候, web Server调用一个callable object(函数,类,实现__call__的实例等等), 传入(environ, start_response), 其中environ是一个字典,包含了请求相关的所有信息,比如请求路径,参数;start_response是一个函数。在这个callable object中会调用start_response,返回http状态码,headers等信息给web server, callable object本身则会返回具体的内容给web server, 返回的object必须是可迭代的。当有callable object本身返回的内容时候, 就会把start_response返回的内容和callable object返回的内容写入缓冲区并且刷新。
那么flask 给web server的callable object是什么?
Class Flask:
...
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)
可以看到,是一个实现了__call__的实例,web server 调用Flask的实例,传入environ和start_response,那么这个self.wsgi_app是什么东西?
def wsgi_app(self, environ, start_response):
"""The actual WSGI application. This is not implemented in
`__call__` so that middlewares can be applied without losing a
reference to the class. So instead of doing this:: app = MyMiddleware(app) It's a better idea to do this instead:: app.wsgi_app = MyMiddleware(app.wsgi_app) Then you still have the original application object around and
can continue to call methods on it. .. versionchanged:: 0.7
The behavior of the before and after request callbacks was changed
under error conditions and a new callback was added that will
always execute at the end of the request, independent on if an
error ocurred or not. See :ref:`callbacks-and-errors`. :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):
try:
response = self.full_dispatch_request()
except Exception, e:
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
在文档里面第一句已经说明了“The actual WSGI application...”这个才是真正的callable object。那么这个真正的callable object会做什么事情?
首先,会打开self.request_context(envion),
class RequestContext(object):
def __init__(self, app, environ):
self.app = app
self.request = app.request_class(environ)
self.url_adapter = app.create_url_adapter(self.request)
self.g = app.request_globals_class()
self.flashes = None
self.session = None
self.preserved = False
self._after_request_functions = []
self.match_request()
blueprint = self.request.blueprint
if blueprint is not None:
# better safe than sorry, we don't want to break code that
# already worked
bp = app.blueprints.get(blueprint)
if bp is not None and blueprint_is_module(bp):
self.request._is_old_module = True
def match_request(self):
try:
url_rule, self.request.view_args = \
self.url_adapter.match(return_rule=True)
self.request.url_rule = url_rule
except HTTPException, e:
self.request.routing_exception = e
def push(self):
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
app_ctx = self.app.app_context()
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)
_request_ctx_stack.push(self)
self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
def pop(self, exc=None):
app_ctx = self._implicit_app_ctx_stack.pop()
clear_request = False
if not self._implicit_app_ctx_stack:
self.preserved = False
if exc is None:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc)
clear_request = True
rv = _request_ctx_stack.pop()
assert rv is self, 'Popped wrong request context. (%r instead of %r)' \
% (rv, self)
# get rid of circular dependencies at the end of the request
# so that we don't require the GC to be active.
if clear_request:
rv.request.environ['werkzeug.request'] = None
# Get rid of the app as well if necessary.
if app_ctx is not None:
app_ctx.pop(exc)
def __enter__(self):
self.push()
return self
def __exit__(self, exc_type, exc_value, tb):
if self.request.environ.get('flask._preserve_context') or \
(tb is not None and self.app.preserve_context_on_exception):
self.preserved = True
else:
self.pop(exc_value)
从__enter__可以看到,首先会把当前请求推入栈,这个是什么栈?有什么用呢?
简而言之,这个栈就是flask根据每次请求时候,为每个请求所创建的线程/协程的id所创建的一个自己的threadlocal,在这个threadlocal中所实现的一个栈结构,会把不同的请求隔离开来,每次请求时候把当前请求所在的app,request推入栈,就可以在全局可用了。
把current_app,request推入栈之后,就根据请求的方法,路径等参数分发请求,找到用@app.route('...')注册的函数,生成response。
但是这里有个问题,就是根据wsgi协议,返回的结果应该是个可迭代的结果,而且这时候start_response还没执行,状态码和其他头部信息还都没有。这个就是make_response的事情了,
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, e:
rv = self.handle_user_exception(e)
response = self.make_response(rv)
response = self.process_response(response)
request_finished.send(self, response=response)
return response
make_response会把返回结果包装一下。
def make_response(self, rv):
if isinstance(rv, basestring):
rv = self.response_class(rv, headers=headers, status=status)
headers = status = None
else:
rv = self.response_class.force_type(rv, request.environ) if status is not None:
if isinstance(status, basestring):
rv.status = status
else:
rv.status_code = status
if headers:
rv.headers.extend(headers) return rv
make_response会利用class Response(BaseResponse)把view function返回的结果包装一下,
class Response(ResponseBase):
default_mimetype = 'text/html' class BaseResponse(object):
automatically_set_content_length = True def __init__(self, response=None, status=None, headers=None,
.... @classmethod
def force_type(cls, response, environ=None):
... @classmethod
def from_app(cls, app, environ, buffered=False):
... def _get_status_code(self):
return self._status_code def __call__(self, environ, start_response): app_iter, status, headers = self.get_wsgi_response(environ)
start_response(status, headers)
return app_iter
当执行Response的实例时候,在__call__里面会执行start_response, 同时返回一个可迭代的对象
这就是flask的执行逻辑
参考资料:
PEP 333 -- Python Web Server Gateway Interface v1.0
flask源码剖析的更多相关文章
- flask源码剖析系列(系列目录)
flask源码剖析系列(系列目录) 01 flask源码剖析之werkzurg 了解wsgi 02 flask源码剖析之flask快速使用 03 flask源码剖析之threading.local和高 ...
- 08 Flask源码剖析之flask拓展点
08 Flask源码剖析之flask拓展点 1. 信号(源码) 信号,是在flask框架中为我们预留的钩子,让我们可以进行一些自定义操作. pip3 install blinker 2. 根据flas ...
- flask 源码剖析
flask 上下文管理源码流程及涉及的部分技术点 [flask源码梳理]之一 偏函数_mro [flask源码梳理]之二 面向对象中__setattr__ [flask源码梳理]之三 Local ...
- Flask源码剖析详解
1. 前言 本文将基于flask 0.1版本(git checkout 8605cc3)来分析flask的实现,试图理清flask中的一些概念,加深读者对flask的理解,提高对flask的认识.从而 ...
- 04 flask源码剖析之LocalStack和Local对象实现栈的管理
04 LocalStack和Local对象实现栈的管理 目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于l ...
- 05 flask源码剖析之配置加载
05 Flask源码之:配置加载 目录 05 Flask源码之:配置加载 1.加载配置文件 2.app.config源码分析 3.from_object源码分析 4. 总结 1.加载配置文件 from ...
- 06 flask源码剖析之路由加载
06 Flask源码之:路由加载 目录 06 Flask源码之:路由加载 1.示例代码 2.路由加载源码分析 1.示例代码 from flask import Flask app = Flask(__ ...
- 07 flask源码剖析之用户请求过来流程
07 Flask源码之:用户请求过来流程 目录 07 Flask源码之:用户请求过来流程 1.创建ctx = RequestContext对象 2. 创建app_ctx = AppContext对象 ...
- flask源码剖析--请求流程
想了解这篇里面的内容,请先去了解我另外一篇博客Flask上下文 在了解flask之前,我们需要了解两个小知识点 偏函数 import functools def func(a1,a2): print( ...
随机推荐
- python中的%s%是什么意思
它是一个字符串格式化语法(它从C借用). 请参阅 “格式化字符串”: Python支持将值格式化为字符串.虽然这可以包括非常复杂的表达式,但最基本的用法是将值插入到%s 占位符的字符串中 . 编辑 ...
- js中获取时间new date()的用法和获取时间戳
获取时间: 1 var myDate = new Date();//获取系统当前时间 获取特定格式的时间: 1 myDate.getYear(); //获取当前年份(2位) 2 myDate.getF ...
- shiro自定义realm支持MD5算法认证(六)
1.1 散列算法 通常需要对密码 进行散列,常用的有md5.sha, 对md5密码,如果知道散列后的值可以通过穷举算法,得到md5密码对应的明文. 建议对md5进行散列时加salt(盐),进行 ...
- 关于C++中的指针、数组
C++中指针和数组基本等价的原因在于指针算术和C++内部处理数组的方式:将整数变量加一后,其值将增加1:将指针变量加一后,增加的量等于其指向的数据类型的字节数: 指针中存储的是地址,地址在形式上和整数 ...
- k64 datasheet学习笔记4---Memory Map
1.前言 本文主要介绍K64地址空间的映射 2. System Memory Map 3. K64地址映射 4. Armv7m地址映射 4.1 Armv7M.System地址段(0XE0000000~ ...
- VS "以下文件中的行尾不一致,要将行尾标准化吗?"
原文地址:http://www.cnblogs.com/yymn/p/6852857.html 这是由Windows和Unix不同的标准引起的...即“回车”和“换行”的问题... “回车”和“换行” ...
- 用命令打开本地tomcat服务器
1.点击开始菜单,搜索cmd,默认第一个结果是cmd.exe, 鼠标右键用管理员权限打开(win7及以上版本系统) 启动命令是net start tomcat8 (我电脑是tomcat8,如果是tom ...
- MyEclipse 2017 ci6 安装反编译插件(本人自己摸索的方法,亲测可行)
注: 本文来源于:Smile_Miracle 的< MyEclipse 2017 ci6 安装反编译插件(本人自己摸索的方法,亲测可行) > 第一步:关闭ME,去一下地址下载jad的反编译 ...
- python接口自动化测试三:代码发送HTTP请求
get请求: 1.get请求(无参数): 2.get请求(带参数): 接口地址:http://japi.juhe.cn/qqevaluate/qq 返回格式:json 请求方式:get post 请求 ...
- 设计模式【转自JackFrost的博客】
首先,感谢作者对知识的分享 使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性.设计模式使代码编制真正工程化,是软件工程的基石脉络,如同大厦的结构一样. 文章结构:1.单一职责原则( ...