Flask启动流程

Posted Flask学习笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flask启动流程相关的知识,希望对你有一定的参考价值。

Flask 启动流程

请结合Flask上下文一起阅读:https://wenyan.online/2020/10/19/flask-shang-xia-wen/

Flask 依赖于werkzeugjinja 两个核心库,werkzeugHTTPWSGI相关的包集合.jinja 主要用于渲染前端模板文件.

werkzeug  实现了WSGI 协议,一个典型的WSGI 类似于:

wsgi
  • WSGI:全称 Web Server Gateway Interface ,他不是服务器, Python 模块,框架, API,只是一种规范,用来描述 web server 如何与 web application通信的规范.根据它,可以实现很多的相关框架,比如 Flask,Django
  • WSGI server 主要负责从客户端接受 request ,并转发给 application, application 处理完数据后, WSGI server 在把 Response 返回给客户端.
  • WSGI application 主要是来处理 request,它可以包含多个中间件.
  • environ 指的是一个包含请求信息的对象.

1.Flask 简单应用

一个简单的Flask 服务器如下.

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
 return 'This is {}'.format(username)

if __name__ == '__main__':
 app.run(debug=True)

这里从app.run() 来一步一步的看,它都执行了什么.

app.run()
# 定义在 flask/app.py中
class Flask(_PackageBoundObject):
 # .....
 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:
         self._got_first_request = False
# 可以看到 run() 方法最重要的是使用了 werkzeug.serving.run_simgle()方法
# 查看 run_simple()
#  werkzeug.serving.run_simgle()
def run_sinple():
 # ...
     srv = make_server(
         hostname,
         port,
         application,
         threaded,
         processes,
         request_handler,
         passthrough_errors,
         ssl_context,
         fd=fd,
     )
   #...
# 可以看到这里主要使用了一个 make_server的方法
# 此方法也定义在  werkzeug.serving.make_server()
def make_server():
 # ''''''
 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
     )
# 查看 ThreadedWSGIServer,ForkingWSGIServer,BaseWSGIServer
# 这三个方法同样定义在  werkzeug.serving.BaseWSGIServer
class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
 pass
class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
 pass
class BaseWSGIServer(HTTPServer, object):
 pass
# 可以看到最主要的类是 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
   HTTPServer.__init__(self, server_address, handler)
  # ....
# 这里使用了最重要的一个类   WSGIRequestHandler         
# 这个类也定义在 werkzeug.serving.WSGIRequestHandler
class WSGIRequestHandler(BaseHTTPRequestHandler, object):
 # ...
 def execute(app):
     application_iter = app(environ, start_response)
  # ...

  try:
       execute(self.server.app)
  # ...
# 可以看到这个类最主要就是执行了 execute(app) 这个方法,而根据传递 这里的
# app 就是最开始生成的app=Flask(__name__)
# 执行到最后相当于执行 app() 这个函数
# 找到 flask/app.py 下的 Flask.__call__
class Flask:
 #...
 def wsgi_app(self, environ, start_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:  # noqa: B001
             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):
     return self.wsgi_app(environ, start_response)
 
# 最终的逻辑定义在    Flask.wsgi_app()中.

2.wsgi_app

Flask 虽然运行,但是并没有真正的去处理请求,当真正有请求时,是调用Flask.wsgi_app() 去处理

class Flask:
 def request_context(self, environ):
     return RequestContext(self, environ)
 
 def wsgi_app(self, environ, start_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:  # noqa: B001
             error = sys.exc_info()[1]
             raise
         return response(environ, start_response)
     finally:
         if self.should_ignore_error(error):
             error = None
         ctx.auto_pop(error)
ctx = self.request_context(environ)
# 开始就进行了 请求上下文
# 这个方法同样定义在Flask中,引用的是 RequestContext
# 此方法定义在 flask/ctx.py 中
class RequestContext(object):

 def __init__(self, app, environ, request=None, session=None):
     self.app = app
     # 最开始 request 为None
     if request is None:
         # 定义在 flask/app.py中
         # request_class =Request 
         # 这个Request,就是 flask/wrappers.py 下的Request类
         # 这个类主要是用来实现路由的.
         request = app.request_class(environ)
     self.request = request
     self.url_adapter = None
     try:
         # 上下文对象的 url_adapter,主要是将请求中的URL和Map实例中的URL
         # 进行匹配
         self.url_adapter = app.create_url_adapter(self.request)
     except HTTPException as e:
         self.request.routing_exception = e
     self.flashes = None
     self.session = session

     self._implicit_app_ctx_stack = []
     self.preserved = False
     self._preserved_exc = None
     self._after_request_functions = []

 #全局变量g    
 @property
 def g(self):
     return _app_ctx_stack.top.g

 @g.setter
 def g(self, value):
     _app_ctx_stack.top.g = value

 def copy(self):
     return self.__class__(
         self.app,
         environ=self.request.environ,
         request=self.request,
         session=self.session,
     )

 def match_request(self):
     try:
         result = self.url_adapter.match(return_rule=True)
         self.request.url_rule, self.request.view_args = result
     except HTTPException as e:
         self.request.routing_exception = e

# 最主要的是实现了 push 和 pop方法
# 将请求上下文绑定到当前上下文
 def push(self):
     # 获取请求上下文,开始是None
     top = _request_ctx_stack.top
     if top is not None and top.preserved:
         top.pop(top._preserved_exc)
     # 获取应用上下文,开始为None
     app_ctx = _app_ctx_stack.top
     # 先创建 application context,并push到堆栈结构
     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)

     if hasattr(sys, "exc_clear"):
         sys.exc_clear()
         
         
  # self是RequestContext对象,其中包含了请求的所有数据
     # push RequestContext 到堆栈结构
     # push当前上下文到堆栈结构中
     _request_ctx_stack.push(self)
     # 这是最重要的方法,保证了_request_ctx_stack中永远指向了当前的上下文
     
     if self.session is None:
         session_interface = self.app.session_interface
         self.session = session_interface.open_session(self.app, self.request)

         if self.session is None:
             self.session = session_interface.make_null_session(self.app)

     if self.url_adapter is not None:
         self.match_request()

刚启动app,应该还没有生成上下文,也就是上下文为{} 的时候,因为我们并没有去访问网页.

from flask import Flask, request
from flask.globals import _app_ctx_stack, _request_ctx_stack

app = Flask(__name__)

# 堆栈结构
print(_app_ctx_stack._local.__storage__)
print(_request_ctx_stack._local.__storage__)
# {}
# {}

@app.route('/')
def index():
 username = request.args.get('username')
 print(_app_ctx_stack._local.__storage__)
 print(_request_ctx_stack._local.__storage__)
 return 'This is {}'.format(username)

if __name__ == '__main__':
 app.run(debug=True)

3.路由

现在来看一下路由的形成.

@app.route('/')
def index():
username = request.args.get('username')
return 'This is {}'.format(username)

这里route 方法是一个装饰器,具体的实现逻辑在add_url_rule()

# flask/app.py
class Flask():
# ...
def route(self,rule,**options):
  def decorator(f):
      endpoint = options.pop("endpoint"None)
      self.add_url_rule(rule, endpoint, f, **options)
      return f
  return decorator  

#  add_url_rule
@setupmethod
def add_url_rule(
  self,
  rule,
  endpoint=None,
  view_func=None,
  provide_automatic_options=None,
  **options
)
:

  if endpoint is None:
      endpoint = _endpoint_from_view_func(view_func)
  options["endpoint"] = endpoint
  methods = options.pop("methods"None)

  # ....

  rule = self.url_rule_class(rule, methods=methods, **options)
  rule.provide_automatic_options = provide_automatic_options

  self.url_map.add(rule)
  if view_func is not None:
      old_func = self.view_functions.get(endpoint)
      if old_func is not None and old_func != view_func:
          raise AssertionError(
              "View function mapping is overwriting an "
              "existing endpoint function: %s" % endpoint
          )
      self.view_functions[endpoint] = view_func

它主要的作用是将路由添加到self.url_map 中.通过ruleendpoint 的对应,可以很方便的访问到网页.

以上,当执行完ctx.push() 后,Local 对象中已经有数据了,接着执行self.full_dispatch_request,也就是执行视图函数,它的作用是根据请求的url找到对应的蓝本里面的视图函数,并生成一个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 as e:
      rv = self.handle_user_exception(e)
  return self.finalize_request(rv)

def finalize_request(self, rv, from_error_handler=False):
  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

最后,会清空堆栈结构中的数据,为下一次的上下文做准备.

ctx.auto_pop(error)
# auto_pop中实现了 ResponseContext.pop()方法.

4.发起访问

from flask import Flask, request
from flask.globals import _app_ctx_stack, _request_ctx_stack

app = Flask(__name__)

# 堆栈结构
print(_app_ctx_stack._local.__storage__)
print(_request_ctx_stack._local.__storage__)
# {}
# {}

@app.route('/')
def index():
username = request.args.get('username')
print(_app_ctx_stack._local.__storage__)
print(_request_ctx_stack._local.__storage__)
return 'This is {}'.format(username)

if __name__ == '__main__':
app.run(debug=True)

在浏览器中发起访问http://127.0.0.1:5000/?username=python

{<greenlet.greenlet object at 0x7f0b30a17260>: {'stack': [<flask.ctx.AppContext object at 0x7f0b30a1e210>]}}
{<greenlet.greenlet object at 0x7f0b30a17260>: {'stack': [<RequestContext 'http://127.0.0.1:5000/?username=python' [GET] of app>]}}

可以看到其中有一个RequestContext 对象.当真正发起访问http://127.0.0.1:5000/?username=python ,就会调用Flask.wsgi_app() ,此时调用了RequestContext.push(self) ,也就是当前上下文被push 到了__request_ctx_stack 堆栈结构中.这就是上下文结构.

当访问结束后,ctx.auto_pop(error)会清空堆栈结构.

- END -


以上是关于Flask启动流程的主要内容,如果未能解决你的问题,请参考以下文章

flask启动流程02

Flask启动原理,源码流程分析

Flask源码流程分析

flask请求流程

springboot启动流程构造SpringApplication实例对象

VSCode自定义代码片段——git命令操作一个完整流程