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( ...
随机推荐
- log4j日志日记记录使用教程
注意:每次引入Logger的时候注意引入的jar包,因为有Logger的包太多了...... Logger必须作为类的静态变量使用.原因如下: 1 使用static修饰的属性是归这个类使用的2 也就是 ...
- 初识CPU卡、SAM卡/CPU卡简介、SAM卡简介 【转】
初识CPU卡.SAM卡/CPU卡简介.SAM卡简介 IC卡按照接口方式可分为接触式卡.非接触式卡.复合卡:按器件技术可分为非加密存储卡.加密存储卡和CPU卡. 加密存储卡是对持卡人的认证,只有输入正确 ...
- C# 清理消息管道的消息
using System;using System.Collections.Generic;using System.Linq;using System.Messaging;using System. ...
- Tpcc-MySQL对mysql数据库进行性能测试报告、分析及使用gnuplot生成图表展示
TPC-C是专门针对联机交易处理系统(OLTP系统)的规范,一般情况下我们也把这类系统称为业务处理系统. tpcc-mysql是percona基于TPC-C(下面简写成TPCC)衍生出来的产品,专用于 ...
- Expm 2_1 k-路合并操作问题
假定有k个有序数组,每个数组中含有n个元素,您的任务是将它们合并为单独的一个有序数组,该数组共有kn个元素.设计和实现 一个有效的分治算法解决k-路合并操作问题,并分析时间复杂度.
- JavaScript的类型自动转换样例集合处
1.前言 如果Javascript期望使用一个字符串,它会把给定的值转换成字符串:如果Javascript期望使用一个数字,它会把给定的值转化成数字. 2.样例 2.1.字符串拼接时有数字 windo ...
- javascript 浮点数比较
Js中两个浮点数比较,不能使用=== 必须用相减绝对值小于极小的一个数字来判断 Math.abs(1/3 - (1-2/3))<0.0001 这样的方式来判断.
- vue-router两种模式,到底什么情况下用hash,什么情况下用history模式呢?
转:https://segmentfault.com/q/1010000010340823/a-1020000010598395 为什么要有 hash 和 history 对于 Vue 这类渐进式前端 ...
- codeforce 139E
成段更新+离散化才能过,数据好强.. 单点更新挂在了test27,下次做到成段更新再来做! /* 期望=存活概率*点权值/100 ans=sum(期望) 离散化树木权值,数轴统计累加可能倒下的树木概率 ...
- 绝对定位后,position:absolute;不能使用margin: 0 auto;实现居中;
声明: web小白的笔记,欢迎大神指点!联系QQ:1522025433. 我们都知道margin: 0 auto:可也实现块状元素的水平居中:但是对于绝对顶为的元素就会失效: 请看实例: <!d ...