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( ...
随机推荐
- Spring+SpringMVC+Mybatis整合(二)
目录结构:
- Informatic学习总结_day02_增量抽取
SELECT EMP.EMPNO, EMP.ENAME, EMP.JOB, EMP.MGR, EMP.HIREDATE, EMP.SAL, EMP.COMM, EMP.DEPTNO FROM EMP ...
- ubuntu 14.04 软件中心闪退解决方案
法一: gksudo gedit /usr/share/software-center/softwarecenter/ui/gtk3/views/lobbyview.py 注释下面这句话(注释使用#号 ...
- jQuery中对未来的元素绑定事件用bind、live or on
对未来的元素绑定事件不能用bind, 1.可以用live代替,但是要注意jquery的版本,根据官方文档,从1.7开始就不推荐live和delegate了,1.9里就去掉live了. 2.推荐用on代 ...
- IIS配置过程中的常见问题
解析Json需要设置Mime IIS6.0 1.打开IIS添加Mime项 关联扩展名:*.json内容类型(MIME):application/x-JavaScript 2.添加映射: 位置 ...
- TabCtrl使用
TabCtrl使用 0x1 新建子页面 插入三个对话框,ID分别为:IDD_PAGE_FILE.IDD_PAGE_NETWORK.IDD_PAGE_PROCESS 工具箱-[属性]-[Style]设置 ...
- CEdit使用(Edit Control控件)
CEdit使用(Edit Control控件) 编辑框只读 属性 Read Only等于 True,就有了只读效果 密码框 属性 Password 等于 True,就有了密码效果 获取值/赋予值 Up ...
- arch linux 安装指南
(如果不想折腾arch linux,推荐直接使用 manjaro: https://manjaro.org/ ) 1.安装准备 Arch Linux 能在任何内存空间不小于 512MB 的 x86_ ...
- python内存数据库pydblite
Pure-Python engine 最近由于项目开发中发现python informixDB模块对多线程的支持非常不好,当开启两个线程同时连接informix数据库的时候,数据库会报错,显示SQL ...
- RestTemplate -springwebclient
1 使用jar版本 - spring-web-4.3.8.RELEASE.jar 场景:backend,post请求远端,header中加入accessToken,用于权限控制 HttpHeaders ...