笔记-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()

这里只列出了核心部分,前面有一些判断处理语句,按序做了以下工作:

  1. 对debug模式以及静态文件的包装;
  2. ShareDataMiddleware就是一个中间件,这里是起到吧文件转换为服务器可接受的Response形式的作用。
  3. use_reloader 用于决定当app代码改变时是否要重启服务器,若是True,则他会建立一个socket,其中的can_open_by_fd由socket中是否由fromfd特征决定,如果可以就将fd储存在环境变量中以便重启后的复用,socket开始监听,而后就调用run_with_reloader,它也接受了函数inner.
  4. 无论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-原理及请求处理流程的更多相关文章

  1. flask基础之请求处理核心机制(五)

    前言 总结一下flask框架的请求处理流程. 系列文章 flask基础之安装和使用入门(一) flask基础之jijia2模板使用基础(二) flask基础之jijia2模板语言进阶(三) flask ...

  2. Flask - 请求处理流程和上下文源码分析

    目录 Flask - 请求处理流程和上下文 WSGI Flask的上下文对象及源码解析 0. 请求入口 1.请求上下文对象的创建 2. 将请求上下文和应用上下文入栈 3.根据请求的URl执行响应的视图 ...

  3. Yii源码阅读笔记(二十一)——请求处理流程

    Yii2请求处理流程: 首先:项目路径/web/index.php (new yii\web\Application($config))->run();//根据配置文件创建App实例,先实例化y ...

  4. ASP.NET Core管道深度剖析(2):创建一个“迷你版”的管道来模拟真实管道请求处理流程

    从<ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求>我们知道ASP.NET Core请求处理管道由一个服务器和一组有序的中间件组成,所以从总体设计来讲是非常简单的,但 ...

  5. ASP.Net请求处理机制初步探索之旅 - Part 5 ASP.Net MVC请求处理流程

    好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人>: --> 开篇:上一篇 ...

  6. Http 请求处理流程

    引言 我查阅过不少Asp.Net的书籍,发现大多数作者都是站在一个比较高的层次上讲解Asp.Net.他们耐心.细致地告诉你如何一步步拖放控件.设置控件属性.编写CodeBehind代码,以实现某个特定 ...

  7. ASP.Net MVC请求处理流程

    ASP.Net MVC请求处理流程 好听的歌 我一直觉得看一篇文章再听一首好听的歌,真是种享受.于是,我在这里嵌入一首好听的歌,当然你觉得不想听的话可以点击停止,歌曲 from 王菲 <梦中人& ...

  8. Asp.Net构架(Http请求处理流程)、(Http Handler 介绍)、(HttpModule 介绍)

    Asp.Net构架(Http请求处理流程) Http请求处理流程概述 对于普通访问者来说,这就像每天太阳东边升起西边落下一样是理所当然的:对于很多程序员来说,认为这个与己无关,不过是系统管理员或者网管 ...

  9. Asp.Net构架(Http请求处理流程) - Part.1

    引言 我查阅过不少Asp.Net的书籍,发现大多数作者都是站在一个比较高的层次上讲解Asp.Net.他们耐心.细致地告诉你如何一步步拖放控件.设置控件属性.编写CodeBehind代码,以实现某个特定 ...

随机推荐

  1. 增加C盘空间大小

    随着我们使用电脑的时间越来越久,电脑C盘的空间会出现不够用的情况,这时我们需要的就是增加C盘的大小,基本上有两种方式 1.通过系统自带的磁盘管理(有可能没法操作,主要介绍第二种) 2.通过分区软件进行 ...

  2. 浏览器下出现net::ERR_BLOCKED_BY_CLIENT的解决办法

    转发网址:https://www.cnblogs.com/wenzheshen/p/7724065.html 当我们在做开发时,调试页面图片会出现部分图片无法正常显示,并且确认图片的地址正确: 按F1 ...

  3. IOS 蓝牙(GameKit、Core Bluetooth)

    GameKit的蓝牙开发注意 ● 只能用于iOS设备之间的连接 ● 只能用于同一个应用程序之间的连接 ● 最好别利用蓝牙发送比较大的数据 /* 关于蓝牙的数据传输  1. 一次性传送,没有中间方法,所 ...

  4. datatable Left and right fixed columns

    $(document).ready(function() { var table = $('#example').DataTable( { scrollY: "300px", sc ...

  5. 昨天刚看了Handler和HandlerThread这个东西,不明白为什么要用这么复杂的东西,而且Handler直接post的话好像还不是子线程运行。那我再开发的时候直接用Thread行不行?两个有什么区别?

    Handler就是android中一个机制,主要是考虑到线程安全的! Handler是可以实现线程间通信的,LZ知道Android的UI线程不安全的吧,也就是说不可以在UI线程以外的其他线程对UI进行 ...

  6. sougou输入法无法正常输入汉字

    删除~/.config目录下的SougouPY SogouPY.users sogou-qimpanel文件夹,然后重启搜狗输入法即可

  7. 【洛谷P2470】[SCOI2007]压缩

    压缩 #include<iostream> #include<cstring> #include<cstdio> using namespace std; #def ...

  8. 【题解】洛谷P1731 [NOI1999] 生日蛋糕(搜索+剪枝)

    洛谷P1731:https://www.luogu.org/problemnew/show/P1731 思路 三重剪枝 当前表面积+下一层表面积如果超过最优值就退出 当前体积+下一层体积如果超过总体积 ...

  9. SpringBoot非官方教程 | 第七篇:springboot开启声明式事务

    转载请标明出处: http://blog.csdn.net/forezp/article/details/70833629 本文出自方志朋的博客 springboot开启事务很简单,只需要一个注解@T ...

  10. selenium之css定位

    实在记不住,烂笔头就记一下吧. 一. 单一属性定位 1:type selector driver.find_element_by_css_selector('input') 2:id 定位 drive ...