zoukankan      html  css  js  c++  java
  • flask基础之请求处理核心机制(五)

    前言

    总结一下flask框架的请求处理流程。

    系列文章

    WSGI协议

    一般来说http服务器和框架需要进行解耦,http专门负责接受HTTP请求、解析HTTP请求、发送HTTP,响应请求等;而web框架负责处理请求的逻辑,和数据库的交互等等,那么它们之间需要约定一套接口使得http服务器能够调用web框架的处理逻辑,这个协议就是WSGI协议。

    WSGI协议要求http服务器接收到http请求后经过处理得到两个参数,一个是请求数据封装的字典environ,另一个是需要框架回调的方法start_response。

    在flask框架中,服务器对每个请求调用一次app的wsgi_app方法返回结果,而wsgi_app方法的执行过程就是请求的处理流程。

    class Flask(object):
        def wsgi_app(self, environ, start_response):
            ctx = self.request_context(environ)
            ctx.push()
            error = None
            try:
                try:
                    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)
    

    第一步:服务器启动

    服务器启动后,假设服务器是基于线程的,此时app对象被创建,加载了相关的初始化参数,这时代理对象如current_app、g、session、request等会被创建,但是它们目前并没有代理任何的对象,如果此时使用它们会报错,需要在第一次接收到请求后才会真正地代理上下文。那么服务器启动究竟干了什么事呢?

    详细请参考:flask之app初始化

    第二步:接收请求,创建上下文,入栈

    服务器收到一个http请求后,使用app上下文和请求数据创建一个线程,调用app的request_context(self, environ)方法,将解包后封装的http请求数据当做environ参数传入,返回一个RequestContext实例对象,每一个请求都有一个RequestContext实例对象,同时他们都拥有各自的app上下文,也就是说在本线程中的app应用是服务器初始化app的一个引,因此我们可以动态修改app的属性。

    将RequestContext对象push进_request_ctx_stack里面,_request_ctx_stack是一个栈对象,此时代理对象request指向栈顶的RequestContext对象的request属性,该request是一个Request对象,而session此时指向栈顶的RequestContext对象的session属性。

    判断_app_ctx_stack栈顶是否存在应用上下文对象AppContext,不存在就创建,同时将AppContext推送到_app_ctx_stack栈对象中,此时current_app指向栈顶AppContext对象的app属性,而g变量指向栈顶AppContext对象的g属性,本质上是一个_AppCtxGlobals对象,数据结构是一个字典。

    • 应用上下文和请求上下文存放的栈对象
    _request_ctx_stack = LocalStack()
    _app_ctx_stack = LocalStack()
    
    • 动态修改app的属性
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/test1')
    def test1():
        """
        动态添加一个视图函数
        """
        @app.route('/test2')
        def test2():
            return 'test2'
        return 'OK'
    
    • 应用上下文和请求上下文源码分析
    class Flask(object):
        def app_context(self):
            return AppContext(self)
        def request_context(self, environ):
            return RequestContext(self, environ)
    
    class AppContext(object):
        def __init__(self, app):
            self.app = app
            self.url_adapter = app.create_url_adapter(None)
            self.g = app.app_ctx_globals_class()
        def push(self):
            self._refcnt += 1
            if hasattr(sys, 'exc_clear'):
                sys.exc_clear()
            _app_ctx_stack.push(self) # 将自己推送到栈中
            appcontext_pushed.send(self.app)
    
    class RequestContext(object):
        def __init__(self, app, environ, request=None):
            self.app = app
            if request is None:
                request = app.request_class(environ)
            self.request = request
            self.url_adapter = app.create_url_adapter(self.request)
            self.flashes = None
            self.session = None
        def push(self):
            pass
    

    第三步:请求分派

    分发请求并执行处理逻辑的函数为full_dispatch_request,其返回一个Response对象。处理的过程为:

    • 先执行app对象before_first_request_funcs列表中的所有方法,这是针对app的第一次请求需要的预处理方法,执行该列表中的所有方法是一个原子操作,被加了线程锁,如果不是第一次请求就跳过;

    • 然后执行app对象的url_value_preprocessors字典中对应蓝图的列表中的所有方法,对所有的URL进行预处理;

    • 执行app对象的before_request_funcs列表中的所有方法,其会按照加载的顺序链执行,并且如果中间有任何一个方法返回的结果不是None,那么执行中断,直接返回结果,不再执行视图函数。这是针对app所有的请求都会执行的方法,当然也可以通过蓝图来进行管理;

    • 通过request对象的url_rule(Rule)找到app中的url_map中对应的视图函数执行,返回一个元组的结果rv,就是我们平时写视图函数时返回的元组;

    • 调用make_response函数,以返回的结果rv作为参数构建一个Response对象;

    • 执行app对象中的after_request_funcs列表的所有方法,以构建的Response对象作为参数,每个方法必须都返回Response类型的对象,最后调用session保存本次的状态信息;

    第四步:出栈

    先执行app对象的teardown_request_funcs列表中的所有的方法,其方法和after_request_funcs中的一样,只不过是在出栈前才触发,这意味着即使处理逻辑的部分出错,这里方法也会执行,然后从_request_ctx_stack中弹出RequestContext请求上下文,然后执行app对象中的teardown_appcontext_funcs列表的所有方法,最后从_app_ctx_stack中弹出AppContext应用上下文。

    class AppContext:
        def pop(self, exc=_sentinel):
            app_ctx = self._implicit_app_ctx_stack.pop() 
    
            try:
                clear_request = False
                if not self._implicit_app_ctx_stack:
                    self.preserved = False
                    self._preserved_exc = None
                    if exc is _sentinel:
                        exc = sys.exc_info()[1]
                    self.app.do_teardown_request(exc) # 调用请求钩子
                    if hasattr(sys, 'exc_clear'):
                        sys.exc_clear()
    
                    request_close = getattr(self.request, 'close', None)
                    if request_close is not None:
                        request_close()
                    clear_request = True
            finally:
                rv = _request_ctx_stack.pop() # 弹出请求上下文
                if clear_request:
                    rv.request.environ['werkzeug.request'] = None
                if app_ctx is not None:
                    app_ctx.pop(exc) # 弹出应用上下文
    

    flask请求处理最简代码模型

    假设服务器使用的是多进程模式。

    from multiprocessing import Process, Pool
    class Flask(object):
        def __call__(self, environ, start_response):
            """定义app对请求的处理过程"""
            pass
    
    def listen_port():
        """假设这是端口监听并解析http请求的方法"""
        pass
    
    def run_web():
        """假设这是程序主循环"""
        app = Flask() # 创建一个app,这是app初始化做的
        pool = Pool(10)
        while True:
            # 获取一个http请求的数据
            environ, start_response = listen_port()
            # 调用app处理请求
            pool.apply_async(app, args=(environ, start_response))
    
    if __name__ == '__main__':
        run_web()
    

    总结

    • 无论是gunicorn服务器还是uwsgi服务器,其启动后加载了app对象;

    • 当收到http请求后,按照http协议解析数据,将数据打包成一个字典,将其和响应函数一起作为参数调用app对象的wsgi_app方法;

    • wsgi_app方法按照接收请求,创建上下文,入栈,请求分发,出栈的步骤处理完业务逻辑返回响应数据;

    参考

  • 相关阅读:
    网络编程定时器一:使用升序链表
    木秀林网,网站专注于消息队列技术的研究
    Disruptor Ringbuffer
    Elasticsearch( 插件开发)
    Elasticsearch源码分析(一)启动流程 ModuleBuilder injector
    NativeScriptEngineService 被调用流程
    JAX-RS Resteasy
    gradle eclipse 配置
    maven repository 配置
    Eclipse中部署ES源码运行
  • 原文地址:https://www.cnblogs.com/cwp-bg/p/9917239.html
Copyright © 2011-2022 走看看