Flask学习-Flask请求处理过程

Posted skyflask

tags:

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

一、__call__()

在app启动后,一旦uwsgi收到来自web server的请求,就会调用app,其实此时就是调用app的__call__(environ,start_response).

flask.py:

def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)

  

二、wsgi_app()

当http请求从server发送过来的时候,他会启动__call__功能,这时候就启动了最关键的wsgi_app(environ,start_response)函数。

fask.py:

class Flask(_PackageBoundObject):  
  
........
    #请注意函数的说明,说得非常准确,这个wsgi_app是一个真正的WSGI应用  
    def wsgi_app(self, environ, start_response):    #他扮演的是一个中间角色  
        """The actual WSGI application.  This is not implemented in 
        `__call__` so that middlewares can be applied without losing a 
        reference to the class.  So instead of doing this:: 
 
            app = MyMiddleware(app) 
 
        It‘s a better idea to do this instead:: 
 
            app.wsgi_app = MyMiddleware(app.wsgi_app) 
 
        Then you still have the original application object around and 
        can continue to call methods on it. 
 
        :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)  #
        ctx.push()  
        error = None  
        try:  
            try:  
                response = self.full_dispatch_request()    #full_dispatch_request起到了预处理和错误处理以及分发请求的作用  
            except Exception as e:  
                error = e  
                response = self.make_response(self.handle_exception(e))  #如果有错误发生,则生成错误响应  
            return response(environ, start_response)       #如果没有错误发生,则正常响应请求,返回响应内容  
        finally:  
            if self.should_ignore_error(error):  
                error = None  
            ctx.auto_pop(error)  

  

三、WSGI_APP的内部流程

第一步:生成request请求对象和请求上下文环境

首先,你会看到ctx = self.request_context(environ)的语句,这个涉及到Flask使用了请求上下文和应用上下文的概念,结构为栈结构,这部分比较难,后面第二篇会单独写。

这里只需要理解为,上面语句产生的所用是生成了一个request请求对象以及包含请求信息在内的request context

第二部:请求进入预处理,错误处理及请求转发到响应的过程

进入wsgi_app的函数内部,生成了request对象和上下文环境之后,进入到try

[python] view plain copy
 
  1. response = self.full_dispatch_request()      

 

我们看到,响应被赋值成了full_dispatch_request()方法的返回内容,所以我们来看一下full_dispatch_request方法

 

[python] view plain copy
 
  1. class Flask(_PackageBoundObject):  
  2.   
  3. #此处省略一些代码  
  4.   
  5.     def full_dispatch_request(self):  
  6.         """Dispatches the request and on top of that performs request 
  7.         pre and postprocessing as well as HTTP exception catching and 
  8.         error handling. 
  9.  
  10.         .. versionadded:: 0.7 
  11.         """  
  12.         self.try_trigger_before_first_request_functions()  #进行发生真实请求前的处理  
  13.         try:  
  14.             request_started.send(self)                     #socket部分的操作  
  15.             rv = self.preprocess_request()                 #进行请求的预处理  
  16.             if rv is None:  
  17.                 rv = self.dispatch_request()  
  18.         except Exception as e:  
  19.             rv = self.handle_user_exception(e)  
  20.         response = self.make_response(rv)  
  21.         response = self.process_response(response)  
  22.         request_finished.send(self, response=response)  
  23.         return response  

 

 

 

他首先会触发 try_trigger_before_first_request_function()方法

                      在方法内部 ---------->会触发 _got_first_request 属性,这个属性的返回值是True或者False.  True的话就代表了程序开始处理请求了.

 

来看看 try_trigger_before_first_request_function()的代码,他的目的是,最后将_got_first_request属性置为True.

[python] view plain copy
 
  1. class Flask(_PackageBoundObject):  
  2.   
  3. #省略一些代码  
  4.   
  5.     def try_trigger_before_first_request_functions(self):  
  6.         """Called before each request and will ensure that it triggers 
  7.         the :attr:`before_first_request_funcs` and only exactly once per 
  8.         application instance (which means process usually). 
  9.  
  10.         :internal: 
  11.         """  
  12.         if self._got_first_request:  
  13.             return  
  14.         with self._before_request_lock:  
  15.             if self._got_first_request:  
  16.                 return  
  17.             for func in self.before_first_request_funcs:  
  18.                 func()  
  19.             self._got_first_request = True  

 


再来看看_got_first_request 的定义,他的默认值是False

他的定义中可以明显看到, if the application started,this attribute is set to True.

[python] view plain copy
 
  1. class Flask(_PackageBoundObject):  
  2.   
  3. #省略一些代码  
  4.  
  5.     @property  
  6.     def got_first_request(self):  
  7.         """This attribute is set to ``True`` if the application started 
  8.         handling the first request. 
  9.  
  10.         .. versionadded:: 0.8 
  11.         """  
  12.         return self._got_first_request  

 

接着,当_got_first_request 属性被设置完以后,我们就需要再次回到 full_dispatch_request函数内部,继续往下走

下面一段代码是request_started.send(),他是继承自signal模块,大致作用是进行socket部分的功能,暂时不详细追溯。

preprocess_request()方法的话,主要是进行flask的hook钩子, before_request功能的实现,也就是在真正发生请求之前,有些事情需要提前做

Flask一共有4个hook钩子,另外再写吧

 

随后,继续往下走,来到了一个至  关  重  要的功能  dispatch_request()

[python] view plain copy
 
  1. try:  
  2.     request_started.send(self)  
  3.     rv = self.preprocess_request()  
  4.     if rv is None:  
  5.         rv = self.dispatch_request()  


为什么说至关重要,因为一个http请求到了这里,实际上已经完成了从wsgi部分的过渡,进入到了寻找响应的阶段了,一个请求通过url进来以后,app怎么知道要如何响应呢?

就是通过dispatch_request方法来进行请求判定和分发。

 

第三步:请求分发  dispatch_request

来看源码

[python] view plain copy
 
  1. class Flask(_PackageBoundObject):  
  2.   
  3. #省略一些代码  
  4.   
  5.     def dispatch_request(self):   #看函数定义,matches the URL and returns the value of the view or error.  
  6.         """Does the request dispatching.  Matches the URL and returns the 
  7.         return value of the view or error handler.  This does not have to 
  8.         be a response object.  In order to convert the return value to a 
  9.         proper response object, call :func:`make_response`. 
  10.  
  11.         .. versionchanged:: 0.7 
  12.            This no longer does the exception handling, this code was 
  13.            moved to the new :meth:`full_dispatch_request`. 
  14.         """  
  15.         req = _request_ctx_stack.top.request  
  16.         if req.routing_exception is not None:  
  17.             self.raise_routing_exception(req)  
  18.         rule = req.url_rule  
  19.         # if we provide automatic options for this URL and the  
  20.         # request came with the OPTIONS method, reply automatically  
  21.         if getattr(rule, ‘provide_automatic_options‘, False)   
  22.            and req.method == ‘OPTIONS‘:  
  23.             return self.make_default_options_response()  
  24.         # otherwise dispatch to the handler for that endpoint  
  25.         return self.view_functions[rule.endpoint](**req.view_args)   #最终进入view_functions,取出url对应的视图函数的返回值  

中间不需要过多考虑,req = _request_ctx_stack.top.request 可以暂时理解为,将请求对象赋值给req

这里先简单讲下,每个url进来以后,他都会对应一个view_function

比如下面的一个简单视图函数,路径  ‘/‘ 对应的是index函数

但是,实际上当中是分2步走的,第一步是‘/‘对应的endpoint为‘index‘ ,第二部是endpoint  ‘index‘ 对应到index()视图函数

这个也是放在第二篇文章里面具体写flask 路由的实现,这里暂时可以忽略中间步骤,只要知道URL----------->VIEW FUNCTION的逻辑步骤就ok

 

[python] view plain copy
 
  1. @app.route(‘/‘)  
  2. def index():  
  3.     return ‘Hello world‘  

 

另外说下view_functions 是一个字典形式,他的key和value的关系是endpoint ------> view function

所以每个有效的URL进来,都能找到他对应的视图函数view function,取得返回值并赋值给  rv

比如上面简单的index,他取得的就是 ‘Hello world‘ 值

 

 

请求分发完成后,已经取得了返回的值,再看下一步是如何做

我们再次回到  full_dispatch_request方法内往下走

[html] view plain copy
 
  1. response = self.make_response(rv)  
  2. response = self.process_response(response)  
  3. request_finished.send(self, response=response)  
  4. return response  

 

这时候,通过make_response函数,将刚才取得的 rv 生成响应,重新赋值response

再通过process_response功能主要是处理一个after_request的功能,比如你在请求后,要把数据库连接关闭等动作,和上面提到的before_request对应和类似。

之后再进行request_finished.send的处理,也是和socket处理有关,暂时不详细深入。

之后返回新的response对象

这里特别需要注意的是,make_response函数是一个非常重要的函数,他的作用是返回一个response_class的实例对象,也就是可以接受environ和start_reponse两个参数的对象

 

非   常   重   要!!!

Converts the return value from a view function to a real response object that is an instance of :attr:`response_class

 

[python] view plain copy
 
  1. class Flask(_PackageBoundObject):   #注意函数说明,converts the return value from view function to a real response object  
  2.   
  3.     #省略一部分代码  
  4.   
  5.     def make_response(self, rv):  
  6.         """Converts the return value from a view function to a real 
  7.         response object that is an instance of :attr:`response_class`. 
  8.  
  9.         The following types are allowed for `rv`: 
  10.  
  11.         .. tabularcolumns:: |p{3.5cm}|p{9.5cm}| 
  12.  
  13.         ======================= =========================================== 
  14.         :attr:`response_class`  the object is returned unchanged 
  15.         :class:`str`            a response object is created with the 
  16.                                 string as body 
  17.         :class:`unicode`        a response object is created with the 
  18.                                 string encoded to utf-8 as body 
  19.         a WSGI function         the function is called as WSGI application 
  20.                                 and buffered as response object 
  21.         :class:`tuple`          A tuple in the form ``(response, status, 
  22.                                 headers)`` or ``(response, headers)`` 
  23.                                 where `response` is any of the 
  24.                                 types defined here, `status` is a string 
  25.                                 or an integer and `headers` is a list or 
  26.                                 a dictionary with header values. 
  27.         ======================= =========================================== 
  28.  
  29.         :param rv: the return value from the view function 
  30.  
  31.         .. versionchanged:: 0.9 
  32.            Previously a tuple was interpreted as the arguments for the 
  33.            response object. 
  34.         """  
  35.         status_or_headers = headers = None  
  36.         if isinstance(rv, tuple):  
  37.             rv, status_or_headers, headers = rv + (None,) * (3 - len(rv))  
  38.   
  39.         if rv is None:  
  40.             raise ValueError(‘View function did not return a response‘)  
  41.   
  42.         if isinstance(status_or_headers, (dict, list)):  
  43.             headers, status_or_headers = status_or_headers, None  
  44.   
  45.         if not isinstance(rv, self.response_class):  
  46.             # When we create a response object directly, we let the constructor  
  47.             # set the headers and status.  We do this because there can be  
  48.             # some extra logic involved when creating these objects with  
  49.             # specific values (like default content type selection).  
  50.             if isinstance(rv, (text_type, bytes, bytearray)):  
  51.                 rv = self.response_class(rv, headers=headers,  
  52.                                          status=status_or_headers)  
  53.                 headers = status_or_headers = None  
  54.             else:  
  55.                 rv = self.response_class.force_type(rv, request.environ)  
  56.   
  57.         if status_or_headers is not None:  
  58.             if isinstance(status_or_headers, string_types):  
  59.                 rv.status = status_or_headers  
  60.             else:  
  61.                 rv.status_code = status_or_headers  
  62.         if headers:  
  63.             rv.headers.extend(headers)  
  64.   
  65.         return rv  

 

 

 

 

 

 

 

第四步:返回到wsgi_app内部

终于快进行到了最后一步,流程走回到了wsgi_app的内部

下面这段是wsgi_app内部的代码

 

[python] view plain copy
 
  1. try:  
  2.     try:  
  3.         response = self.full_dispatch_request()  
  4.     except Exception as e:  
  5.         error = e  
  6.         response = self.make_response(self.handle_exception(e))  
  7.     return response(environ, start_response)  
  8. finally:  
  9.     if self.should_ignore_error(error):  
  10.         error = None  
  11.     ctx.auto_pop(error)  

 

 

 

当response从刚刚的full_dispatch_request功能返回之后,函数会对这个response加上environ, start_response的参数并返回给Gunicorn

 

至此,一个HTTP从请求到响应的流程就完毕了.

 

总的来说,一个流程的关键步骤可以简单归结如下:

 

 

技术分享图片

后一篇,将会记录一下flask的route实现,里面的url如何和endpoint对应起来,endpoint和view function又是如何对应起来

 

以上是关于Flask学习-Flask请求处理过程的主要内容,如果未能解决你的问题,请参考以下文章

Flask Web 开发学习稿

flask部署深度学习模型

flask基础之请求钩子

Flask中的请求上下文和应用上下文

Flask中的请求钩子

Flask入门很轻松