zoukankan      html  css  js  c++  java
  • 七 .Flask 请求流程解析 和 路由url源码分发解析

    一 .Flask请求流程解析 和 路由url源码分发解析

    1. flask项目整个请求流程(源码摘要)

    flask项目整个请求流程其实就是执行:wsgi_app()方法中调用的full_dispatch_request(),包括请求扩展和真正的视图函数

    full_dispatch_request()

    def full_dispatch_request(self):
        # 执行before_first_request函数
        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)

    在分析这段代码之前,先回顾下请求扩展函数

    (1)@app.before_first_request:就是将第一次请求之前的函数刚入self.before_first_request_funcs列表中

    def before_first_request(self, f):
        self.before_first_request_funcs.append(f)
        return f

    @before_request:将请求之前函数放入self.before_request_funcs列表中

    def before_request(self, f):
        self.before_request_funcs.setdefault(None, []).append(f)
        return f

    @after_request:将请求之后函数放在self.after_request_funcs列表中

    def after_request(self, f):
        self.after_request_funcs.setdefault(None, []).append(f)
        return f

    1. try_trigger_before_first_request_functions():第一次请求之前

    def try_trigger_before_first_request_functions(self):
        if self._got_first_request:  # 初始为False
            return
        with self._before_request_lock:
            if self._got_first_request:
                return
            for func in self.before_first_request_funcs:
                func()
            self._got_first_request = True

    将self.before_first_request_funcs也就是第一次请求前函数一个个取出执行。执行完后将self._got_first_request设为True。这样在后面的请求中就不再执行,也就是说只在第一次请求之前执行

    2. self.preprocess_request():请求之前

    ef preprocess_request(self):
        bp = _request_ctx_stack.top.request.blueprint
    
        funcs = self.url_value_preprocessors.get(None, ())
        if bp is not None and bp in self.url_value_preprocessors:
            funcs = chain(funcs, self.url_value_preprocessors[bp])
        for func in funcs:
            func(request.endpoint, request.view_args)
       
        funcs = self.before_request_funcs.get(None, ())
        if bp is not None and bp in self.before_request_funcs:
            funcs = chain(funcs, self.before_request_funcs[bp])
        # 将请求之前函数遍历取出执行
        for func in funcs:
            rv = func()
            # 如果请求之前函数有返回值,则返回,下面的请求之前函数不再执行
            if rv is not None:
                return rv

    3. self.dispatch_request():请求

    def dispatch_request(self):
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
            self.raise_routing_exception(req)
        rule = req.url_rule
        if (
            getattr(rule, "provide_automatic_options", False)
            and req.method == "OPTIONS"
        ):
            return self.make_default_options_response()
        # 执行真正的视图函数
        return self.view_functions[rule.endpoint](**req.view_args)

    4.finalize_request():请求之后

    def finalize_request(self, rv, from_error_handler=False):
        # 先make_response响应
        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
    
    def process_response(self, response):
        ctx = _request_ctx_stack.top
        bp = ctx.request.blueprin        
        funcs = ctx._after_request_functions
        # reversed将请求之后函数列表进行了反转
        if bp is not None and bp in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
        if None in self.after_request_funcs:
            funcs = chain(funcs, reversed(self.after_request_funcs[None]))
        # 便利执行反转后的函数列表中的请求之后函数
        for handler in funcs:
            # 注意参数中有个response,所以我们的请求之后函数必须要有个参数。并且要有返回值
            response = handler(response)
        if not self.session_interface.is_null_session(ctx.session):
            self.session_interface.save_session(self, ctx.session, response)
        return response

    以上就是请求相关的流程。总结:

    • 先执行第一次请求之前函数,执行完之后后续请求中不再执行
    • 再从上往下执行请求之前函数,如果有返回值,不再执行后面的请求之前函数,也不执行真正的视图函数
    • 没有返回值时执行视图函数,再执行请求之后函数
    • 请求之后函数必须要有参数,并且有返回值
    • 请求之后函数是从下往上返回执行

    2. flask项目整个请求流程(源码分析)

    Flask中的wsgi接口(注意:每个进入Flask的请求都会调用Flask.__call__)
    # 摘自Flask源码 app.py
    class Flask(_PackageBoundObject):
      # 中间省略
      def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)
           
      def wsgi_app(self, environ, start_response):
        # environ: 一个包含全部HTTP请求信息的字典, 由WSGI Server解包HTTP请求生成
        # start_response: WSGI Server提供的函数, 调用可以发送响应的状态码和HTTP报文头,
        # 函数在返回前必须调用一次.
        :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)

    wsgi_app中定义的就是Flask处理一个请求的基本流程,
    1.创建上下文
    2.把上下文入栈
    3.分发请求
    4.上下文出栈
    5.返回结果
    其中response = self.full_dispatch_request()请求分发的过程我们需要关注一下
    # 摘自Flask源码 app.py
    class Flask(_PackageBoundObject):
      # 中间省略
      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 dispatch_request(self):
        req = _request_ctx_stack.top.request
        if req.routing_exception is not None:
          self.raise_routing_exception(req)
        rule = req.url_rule
        if getattr(rule, 'provide_automatic_options', False) 
          and req.method == 'OPTIONS':
          return self.make_default_options_response()
        return self.view_functions[rule.endpoint](**req.view_args)
     
      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


    我们可以看到, 请求分发的操作其实是由dispatch_request来完成的, 而在请求进行分发的前后我们可以看到Flask进行了如下操作:
    1.try_trigger_before_first_request_functions, 首次处理请求前的操作,通过@before_first_request定义,可以进行数据库连接
    2.preprocess_request, 每次处理请求前进行的操作, 通过@before_request来定义, 可以拦截请求
    3.process_response, 每次正常处理请求后进行的操作, 通过@after_request来定义, 可以统计接口访问成功的数量
    4.finalize_request, 把视图函数的返回值转换成一个真正的响应对象

    以上的这些是Flask提供给我们使用的钩子(hook), 可以根据自身需求来定义,
    而hook中还有@teardown_request, 是在每次处理请求后执行(无论是否有异常), 所以它是在上下文出栈的时候被调用

    如果同时定义了四种钩子(hook), 那么执行顺序应该是

    graph LR
    before_first_request --> before_request
    before_request --> after_request
    after_request --> teardown_request

    在请求函数和钩子函数之间,一般通过全局变量g实现数据共享

    现在的处理流程就变为:

    1.创建上下文
    2.上下文入栈
    3.执行before_first_request操作(如果是第一次处理请求)
    4.执行before_request操作
    5.分发请求
    6.执行after_request操作
    7.执行teardown_request操作
    8.上下文出栈
    9.返回结果

    其中3-7就是需要我们完成的部分.

    
    

    如何使用Flask   上面我们知道, Flask处理请求的步骤, 那么我们来试试  



    from
    flask import Flask app = Flask(__name__) @app.before_first_request def before_first_request(): print('before_first_request run') @app.before_request def before_request(): print('before_request run') @app.after_request def after_request(param): print('after_request run') return param @app.teardown_request def teardown_request(param): print('teardown_request run') @app.route('/home/') def hello_world(): return 'Hello World!' if __name__ == '__main__': app.run() 执行顺序: before_first_request run before_request run after_request run teardown_request run

     3. 路由url源码分发解析

    看了上面的代码, 我们可能还是会有疑问, 为什么我们的请求就会跑到hello world 函数去处理呢?我们先来普及几个知识点:

    • url: 客户端访问的网址
    • view_func: 即我们写的视图函数
    • rule: 定义的匹配路由的地址
    • url_map: 存放着rule与endpoint的映射关系
    • endpoint: 可以看作为每个view_func的ID
    • view_functions: 一个字典, 以endpoint为key, view_func 为value
    添加路由的方法:
    1.@app.route
    2.add_url_rule

    我们先来看看@app.route干了什么事情

    # 摘自Flask源码 app.py
    class Flask(_PackageBoundObject):
      # 中间省略
      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

    我们可以看到, route函数是一个装饰器, 它在执行时会先获取endpoint,

    然后再通过调用add_url_rule来添加路由,

    也就是说所有添加路由的操作其实都是通过add_url_rule来完成的. 下面我们再来看看add_url_rule.

    # 摘自Flask源码 app.py
    class Flask(_PackageBoundObject):
      # 中间省略
      # 定义view_functions
      self.view_functions = {}
      # 定义url_map
      self.url_map = Map()
       
      def add_url_rule(self, rule, endpoint=None, view_func=None,
               provide_automatic_options=None, **options):
        # 创建rule
        rule = self.url_rule_class(rule, methods=methods, **options)
        rule.provide_automatic_options = provide_automatic_options
        # 把rule添加到url_map
        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)
          # 把view_func 添加到view_functions字典
          self.view_functions[endpoint] = view_func

    可以看到, 当我们添加路由时, 会生成一个rule, 并把它存放到url_map里头, 然后把view_func与其对应的endpoint存到字典.

    当一个请求进入时, Flask会先根据用户访问的Url到url_map里边根据rule来获取到endpoint,

    然后再利用view_functions获取endpoint在里边所对应的视图函数

    graph LR
    url1 -->url_map
    url2 -->url_map
    url3 -->url_map
    urln -->url_map
    url_map --> endpoint
    endpoint --> view_functions
  • 相关阅读:
    python的各版本的不同
    keras中的early stopping
    NER的数据处理
    ner处理数据的方式
    python的数据处理一
    linux下的终端利器----tmux
    BiseNet学习笔记
    《Harnessing Synthesized Abstraction Images to Improve Facial Attribute Recognition》论文阅读笔记
    转:玩玩三维重建
    《Cascaded Pyramid Network for Multi-Person Pose Estimation》论文阅读及复现笔记
  • 原文地址:https://www.cnblogs.com/lovershowtime/p/11746852.html
Copyright © 2011-2022 走看看