笔记-flask-原理及请求处理流程
笔记-flask-原理及请求处理流程
1. 服务器声明及运行
最基本的flask项目代码如下
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
简单来说,声明Flask对象,声明view function,运行flask对象。
看一下后台做了什么。
1.1. __init__
看一下Flask对象的__init__方法:
#: Holds the path to the instance folder.
#: .. versionadded:: 0.8
self.instance_path =
instance_path
#: The configuration
dictionary as :class:`Config`. This
behaves
#: exactly like a regular
dictionary but supports additional methods
#: to load a config from files.
self.config =
self.make_config(instance_relative_config)
#: A dictionary of
all view functions registered.
self.view_functions
= {}
#: A dictionary of
all registered error handlers.
self.error_handler_spec
= {}
#: A list of
functions that are called when:meth:`url_for`raises a
#: :exc:`~werkzeug.routing.BuildError
self.url_build_error_handlers
= []
#: A dictionary with
lists of functions that will be called at the
#: beginning of each request.
#use the :meth:`before_request`:
decorator.
self.before_request_funcs
= {}
#: A list of
functions that will be called at the beginning of the
#: first request to this
instance.
#: :meth:`before_first_request`
decorator.
self.before_first_request_funcs
= []
#: A dictionary with
lists of functions that should be called after
#: each request.
#:
:meth:`after_request` decorator.
self.after_request_funcs
= {}
#: A dictionary with
lists of functions that are called after
#: each request, even if an
exception has occurred.
#: :meth:`teardown_request`
decorator.
self.teardown_request_funcs
= {}
#: A list of
functions that are called when the application context
#: is destroyed.
self.teardown_appcontext_funcs
= []
#: A dictionary with
lists of functions that are called before the
#: :attr:`before_request_funcs`
functions.
#: :meth:`url_value_preprocessor`.
self.url_value_preprocessors
= {}
#: A dictionary with
lists of functions that can be used as URL value
#: preprocessors.
#:meth:`url_defaults`
self.url_default_functions
= {}
#: A list of shell
context processor functions that should be run
#: when a shell context is
created.
self.shell_context_processors
= []
#: all the attached
blueprints in a dictionary by name.
Blueprints
#: can be attached multiple times
so this dictionary does not tell
#: you how often they got
attached.
#:
#: .. versionadded:: 0.7
self.blueprints = {}
self._blueprint_order = []
#: The
:class:`~werkzeug.routing.Map` for this instance. You can use
#: this to change
the routing converters after the class was created
#: but before any
routes are connected.
self.url_map =
Map()
self.url_map.host_matching = host_matching
self.subdomain_matching = subdomain_matching
把路径及其它环境变量设置去掉以后,剩下的基本就这些了。
都是一些函数列表,用于在不同时机处理请求。
view_functions保存视图函数,error_handlers保存的错误处理函数
url_map保存uri到视图函数的映射。
1.1.1.
route装饰器
顺带讲一下route装饰器:
def route(self, rule, **options):
"""A decorator that is used to register a view function
for a
given URL
rule. This does the same thing as
:meth:`add_url_rule`
but is intended
for decorator usage::
@app.route('/')
def index():
return
'Hello World'
"""
def decorator(f):
endpoint =
options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
route装饰器的作用是将视图函数与url的对应联系装入self.url_map。
1.2.
app.run()
上面说到的是初始化部分,下面看服务器运行部分,当执行app.run()时:
找到run()方法,它做的事情很少,只是设定的一些参数,然后调用了run_simple方法:
def
run(self, host=None, port=None, debug=None,
load_dotenv=True, **options):
from werkzeug.serving import run_simple
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
1.3.
run_simple()
def
inner():
try:
fd =
int(os.environ['WERKZEUG_SERVER_FD'])
except
(LookupError, ValueError):
fd = None
srv =
make_server(hostname, port, application, threaded,
processes, request_handler,
passthrough_errors, ssl_context,
fd=fd)
if fd is None:
log_startup(srv.socket)
srv.serve_forever()
这里只列出了核心部分,前面有一些判断处理语句,按序做了以下工作:
- 对debug模式以及静态文件的包装;
- ShareDataMiddleware就是一个中间件,这里是起到吧文件转换为服务器可接受的Response形式的作用。
- use_reloader 用于决定当app代码改变时是否要重启服务器,若是True,则他会建立一个socket,其中的can_open_by_fd由socket中是否由fromfd特征决定,如果可以就将fd储存在环境变量中以便重启后的复用,socket开始监听,而后就调用run_with_reloader,它也接受了函数inner.
- 无论use_reloader是不是True时,都会调用函数内部的inner函数。inner函数内,在环境中WERKZEUG_SERVER_FD这个key储存了可以复用的socket,若没有就设为None,然后就调用函数make_server,这根据参数process和threads选择合适的服务器,取得服务器对象后,就调用方法run_forever,这服务器也就启动了。,werkzeug提供了多种可选的服务器,这里是一个基本的单线程单进程服务器
1.4.
make_server()
def make_server(host=None, port=None, app=None, threaded=False,
processes=1,
request_handler=None,
passthrough_errors=False,
ssl_context=None, fd=None):
"""Create a new server instance that is either threaded,
or forks
or just processes one
request after another.
"""
if threaded and
processes > 1:
raise ValueError("cannot have a
multithreaded and "
"multi process server.")
elif threaded:
return
ThreadedWSGIServer(host, port, app, request_handler,
passthrough_errors, ssl_context, fd=fd)
elif processes >
1:
return
ForkingWSGIServer(host, port, app, processes, request_handler,
passthrough_errors, ssl_context, fd=fd)
else:
return
BaseWSGIServer(host, port, app, request_handler,
passthrough_errors, ssl_context, fd=fd)
基本上就是根据服务器情况创建一个server instance。
继续,选择BaseWSGIServer()去看一下
1.5.
BaseWSGIServer
class BaseWSGIServer(HTTPServer, object):
"""Simple single-threaded, single-process WSGI
server."""
multithread = False
multiprocess = False
request_queue_size =
LISTEN_QUEUE
def __init__(self,
host, port, app, handler=None,
passthrough_errors=False, ssl_context=None, fd=None):
if handler is
None:
handler =
WSGIRequestHandler
self.address_family = select_ip_version(host, port)
if fd is not
None:
real_sock =
socket.fromfd(fd, self.address_family,
socket.SOCK_STREAM)
port = 0
HTTPServer.__init__(self,
get_sockaddr(host, int(port),
self.address_family), handler)
self.app = app
self.passthrough_errors = passthrough_errors
self.shutdown_signal = False
self.host = host
self.port =
self.socket.getsockname()[1]
# Patch in the
original socket.
if fd is not
None:
self.socket.close()
self.socket =
real_sock
self.server_address = self.socket.getsockname()
if ssl_context is
not None:
if
isinstance(ssl_context, tuple):
ssl_context = load_ssl_context(*ssl_context)
if
ssl_context == 'adhoc':
ssl_context = generate_adhoc_ssl_context()
# If we are
on Python 2 the return value from socket.fromfd
# is an
internal socket object but what we need for ssl wrap
# is the
wrapper around it :(
sock =
self.socket
if PY2 and
not isinstance(sock, socket.socket):
sock =
socket.socket(sock.family, sock.type, sock.proto, sock)
self.socket =
ssl_context.wrap_socket(sock, server_side=True)
self.ssl_context = ssl_context
else:
self.ssl_context
= None
def log(self, type,
message, *args):
_log(type,
message, *args)
def
serve_forever(self):
self.shutdown_signal = False
try:
HTTPServer.serve_forever(self)
except
KeyboardInterrupt:
pass
finally:
self.server_close()
def
handle_error(self, request, client_address):
if
self.passthrough_errors:
raise
return
HTTPServer.handle_error(self, request, client_address)
def get_request(self):
con, info =
self.socket.accept()
return con, info
2.
请求处理
上面的部分是服务器的声明及运行,下面写一下flask服务器是如何处理请求的。
WSGI部分暂且略过,具体可看:https://www.cnblogs.com/steinliber/p/5133386.html
总而言之,它通过application_iter = app(environ, start_response)将请求体传给了flask的app。
2.1.1.
wsgi_app()
接下来,当http请求从server发送过来的时候,会调用__call__()方法,最后实际是调用了wsgi_app功能并传入environ和start_response。
代码如下:
def wsgi_app(self, environ, start_response):
"""The actual WSGI application.
: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.
"""
ctx =
self.request_context(environ)
error = None
try:
try:
ctx.push()
response
= self.full_dispatch_request()
except
Exception as e:
error = e
response
= self.handle_exception(e)
except:
error =
sys.exc_info()[1]
raise
return response(environ,
start_response)
finally:
if
self.should_ignore_error(error):
error =
None
ctx.auto_pop(error)
def __call__(self,
environ, start_response):
"""The WSGI server calls the Flask application object as
the
WSGI application.
This calls :meth:`wsgi_app` which can be
wrapped to
applying middleware."""
return
self.wsgi_app(environ, start_response)
response是在这里生成的。
上下文处理语句:
ctx = self.request_context(environ)
核心语句:
response = self.full_dispatch_request()
2.1.2.
full_dispatch_request
找到full_dispatch_request:
def full_dispatch_request(self):
"""Dispatches
the request and on top of that performs request
pre and postprocessing as well as
HTTP exception catching and
error handling.
"""
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)
try_trigger_before_first_request_function()用于判断是否是第一个请求,然后是否执行
for func in
self.before_first_request_funcs:
func()
request_started是信号机制通知请求开始处理,preprocess_request会调用app中注册的请求前函数,若函数的返回值不是None,response的内容就设为该返回值。否则就调用dispatch_request来找到对应的视图函数得到返回值
preprocess_request()方法的话,主要是进行flask的hook钩子,
before_request功能的实现;
2.1.3.
dispatch_request()
下一句:
try:
request_started.send(self)
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
一个http请求到了这里,实际上已经完成了从wsgi部分的过渡,进入到了寻找响应的阶段了,一个请求通过url进来以后,app怎么知道要如何响应呢?
就是通过dispatch_request方法来进行请求判定和分发。
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`.
"""
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)
self.view_functions是通过路由模块产生的endpoint与视图函数相对应的字典。这个就能返回视图函数要返回的值。
2.1.4.
finalize_request()
接下来返回full_dispatch_request
return self.finalize_request(rv)
对响应进行处理,主要是标准化,通过make_response来将其转化为response的对象
def finalize_request(self, rv, from_error_handler=False):
"""Given the return value from a view function this
finalizes
the request by
converting it into a response and invoking the
postprocessing
functions. This is invoked for both
normal
request
dispatching as well as error handlers.
Because this
means that it might be called as a result of a
failure a special
safe mode is available which can be enabled
with the
`from_error_handler` flag. If enabled,
failures in
response
processing will be logged and otherwise ignored.
:internal:
"""
response =
self.make_response(rv)
try:
response =
self.process_response(response)
request_finished.send(self, response=response)
except Exception:
if not
from_error_handler:
raise
self.logger.exception('Request finalizing failed with an '
'error while
handling an error')
return response
3.
总结
对flask程序结构有了初步了解,理解了从请求到WSGI到flask的处理流程。
从请求到响应的流程图:
4. 参考文档
https://www.jianshu.com/p/2a2407f66438
https://blog.csdn.net/bestallen/article/details/54342120
笔记-flask-原理及请求处理流程的更多相关文章
- flask基础之请求处理核心机制(五)
前言 总结一下flask框架的请求处理流程. 系列文章 flask基础之安装和使用入门(一) flask基础之jijia2模板使用基础(二) flask基础之jijia2模板语言进阶(三) flask ...
- Flask - 请求处理流程和上下文源码分析
目录 Flask - 请求处理流程和上下文 WSGI Flask的上下文对象及源码解析 0. 请求入口 1.请求上下文对象的创建 2. 将请求上下文和应用上下文入栈 3.根据请求的URl执行响应的视图 ...
- Yii源码阅读笔记(二十一)——请求处理流程
Yii2请求处理流程: 首先:项目路径/web/index.php (new yii\web\Application($config))->run();//根据配置文件创建App实例,先实例化y ...
- ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程
从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...
- ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程
好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人>: --> 开篇:上一篇 ...
- Http 请求处理流程
引言 我查阅过不少Asp.Net的书籍,发现大多数作者都是站在一个比较高的层次上讲解Asp.Net.他们耐心.细致地告诉你如何一步步拖放控件.设置控件属性.编写CodeBehind代码,以实现某个特定 ...
- ASP.Net MVC请求处理流程
ASP.Net MVC请求处理流程 好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人& ...
- Asp.Net构架(Http请求处理流程)、(Http Handler 介绍)、(HttpModule 介绍)
Asp.Net构架(Http请求处理流程) Http请求处理流程概述 对于普通访问者来说,这就像每天太阳东边升起西边落下一样是理所当然的:对于很多程序员来说,认为这个与己无关,不过是系统管理员或者网管 ...
- Asp.Net构架(Http请求处理流程) - Part.1
引言 我查阅过不少Asp.Net的书籍,发现大多数作者都是站在一个比较高的层次上讲解Asp.Net.他们耐心.细致地告诉你如何一步步拖放控件.设置控件属性.编写CodeBehind代码,以实现某个特定 ...
随机推荐
- 浏览器下出现net::ERR_BLOCKED_BY_CLIENT的解决办法
转发网址:https://www.cnblogs.com/wenzheshen/p/7724065.html 当我们在做开发时,调试页面图片会出现部分图片无法正常显示,并且确认图片的地址正确: 按F1 ...
- bootstrapTable表格表头换行
使用bootstrapTable组件,达到表头中有一格显示两行,其他表头均为一行,效果图如下: 代码: { field : 'pay_date', title : '已还款完成时间', valign: ...
- BZOJ2836:[SHOI2012]魔法树(树链剖分)
Description Input Output Sample Input 4 0 1 1 2 2 3 4 Add 1 3 1 Query 0 Query 1 Query 2 Sample Outpu ...
- 模拟栈的回溯,完全二叉树搜索,(ZOJ1004)
题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=1004 解题报告: ①方法:完全二叉树的搜索方式,回溯法. ②代码 ...
- 【转】总结oninput、onchange与onpropertychange事件的用法和区别
经本人测试在chrome下的从历史记录中选取值的时候也户触发input事件 前端页面开发的很多情况下都需要实时监听文本框输入,比如腾讯微博编写140字的微博时输入框hu9i动态显示还可以输入的字数.过 ...
- 课堂笔记:HTML----------图片热点
HTML----------图片热点: 规划出图片上的一个区域,可以做出超链接,直接点击图片区域就可完成跳转的效果. 代码: <!DOCTYPE html PUBLIC "-//W3C ...
- 2018.12.20 Spring环境如何搭建
Spring学习 1.导入spring约束 为后续创建xml文件做铺垫 2.开始搭建Spring环境 1.创建Web项目,引入spring的开发包(根据下面的图来引入) 2.引入jar包 coreCo ...
- ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(三) 激动人心的时刻到啦,实现1v1聊天
看起来挺简单,细节还是很多的,好,接上一篇,我们已经成功连接singalR服务器了,那么剩下的内容呢,就是一步一步实现聊天功能. 我们先看看缺什么东西 点击好友弹框之后,要给服务器发消息,进入组Gro ...
- VS Code 中 HTML 文档注释 js 语句异常
今天用 VS Code 编辑 html 文档时,发现快捷键注释 js 代码显示成 “<!-- …… -->”,怀疑是不是因为安装了某个插件,随后排查出系 Jinja 所致,将其禁用之后就 ...
- Android学习笔记_54_自定义 Widget (Toast)
1.Toast控件: 通过查看源代码,发现Toast里面实现的原理是通过服务Context.LAYOUT_INFLATER_SERVICE获取一个LayoutInflater布局管理器,从而获取一个V ...