Flask中一次请求到响应的流程

Flask的请求到响应的处理流程和Django还是比较类似的,毕竟都是遵循着Pep3333,即WSGI协议。但在对请求的具体处理上还是有很多不同的,本文主要介绍Flask的请求处理流程。

Flask的APP的实例是Flask类的实例,所以下面的方法几乎都是Flask类的源码,有兴趣的自己也可以看一下。

先把响应接口方法贴出来,然后进行分析:

def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)


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::
        """
        ctx = self.request_context(environ)
        ctx.push()
        error = None
        try:
            try:
                response = self.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应用的接口。

当我们接收到一个请求后:

1、请求入栈

首先根据WSGI发送的environ变量构造请求上下文,主要是根据函数ctx = self.request_context(environ),然后将该请求上下文推入全局变量_request_ctx_stack中(ctx.push()).

def request_context(self, environ): 
    return RequestContext(self, environ)

这一点上和Django还是有很大的不同的。

        Flask是将request直接压入栈,request成为了请求上下文,包含了所有请求相关的信息,然后我们在使用时就可以直接使用request了,比如直接在视图函数中用request.args.get('query_string')。

        但Django就不是这种处理方式,Django是采取传入参数的方式,即将request传入到下一步调用的函数。

2、before_first_functions执行

得到请求后,要触发钩子函数:第一次请求函数,如果是该应用是第一次实例化,并存在第一次请求之前函数(存在before_first_req_func字典中),会调用存在该字典中的函数。当然一个实例也只执行一次,即在初始化的时候执行。

3、发送请求开始信号

在真正调度视图,处理请求前,此时会首先发送请求开始信号,request_started,告知所有的subscriber请求开始了。FLask的信号机制可以保证一定程度的解耦性。后面还有  before_request,after_request等等钩子函数。在Django中,与其对应的是中间件的几个不同阶段的方法。

4、before_request钩子

如果存在before_request装饰的函数(函数位置在before_request_func字典中),那么在调用正常请求前会调用该函数。

@setupmethod 
def before_request(self, f): 
 """Registers a function to run before each request.
 The function will be called without any arguments.
 If the function returns a non-None value, it's handled as
 if it was the return value from the view and further
 request handling is stopped.
 """
 self.before_request_funcs.setdefault(None, []).append(f)
 return f

从函数可以看到,这是一个装饰器,被该装饰器修饰的函数会添加到字典中。

5、视图调度

如果上面的钩子函数没有返回值,就调用正常的请求,返回一个该请求函数的值。 但如果有返回值就掉过该部。所以,我们想自己定义的时候,函数也不要添加返回值。

调用请求的源代码:

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`.

        .. versionchanged:: 0.7
           This no longer does the exception handling, this code was
           moved to the new :meth:`full_dispatch_request`.
        """
        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)

看上面,适配URL的时候,首先我们从栈顶拿出我们压入的请求。然后得到request的url_rule,这是我们在请求开始的时候,根据URL_Aadpter已经获得了对应的URL的Rule实例。具体的可以看我写的博客:Flask路由思想

然而,对于Django是不同的,Django在每次请求时,都会根据你已经定义好的ROOT_URLCONF模块中,在urlpatterns列表中进行正则匹配。

 

6、构造响应类

上述返回的只是视图函数的返回值,我们还要构造标准的HTTP响应类。包括响应头信息。

def make_response(self, rv):
        """Converts the return value from a view function to a real
        response object that is an instance of :attr:`response_class`.

        The following types are allowed for `rv`:

    

        ======================= ===========================================
        :attr:`response_class`  the object is returned unchanged
        :class:`str`            a response object is created with the
                                string as body
        :class:`unicode`        a response object is created with the
                                string encoded to utf-8 as body
        a WSGI function         the function is called as WSGI application
                                and buffered as response object
        :class:`tuple`          A tuple in the form ``(response, status,
                                headers)`` where `response` is any of the
                                types defined here, `status` is a string
                                or an integer and `headers` is a list of
                                a dictionary with header values.
        ======================= ===========================================

        :param rv: the return value from the view function

        .. versionchanged:: 0.9
           Previously a tuple was interpreted as the arguments for the
           response object.
        """
        status = headers = None
        if isinstance(rv, tuple):
            rv, status, headers = rv + (None,) * (3 - len(rv))

        if rv is None:
            raise ValueError('View function did not return a response')

        if not isinstance(rv, self.response_class):
            # When we create a response object directly, we let the constructor
            # set the headers and status.  We do this because there can be
            # some extra logic involved when creating these objects with
            # specific values (like default content type selection).
            if isinstance(rv, (text_type, bytes, bytearray)):
                rv = self.response_class(rv, headers=headers, status=status)
                headers = status = None
            else:
                rv = self.response_class.force_type(rv, request.environ)

        if status is not None:
            if isinstance(status, string_types):
                rv.status = status
            else:
                rv.status_code = status
        if headers:
            rv.headers.extend(headers)

        return rv


7、执行process_response

在得到响应之后,发送给WSGI SERVER之前会调用after_request装饰的函数,并返回响应类。 Flask中也把其叫做process_response,和Django一样,不过Django中还多了一个异常的钩子方法,即异常的时候我自动调用异常的钩子,process_exception_response。

除了执行钩子函数外,还有一个过程,即更新session。如果此时session内容发生更新了,就需要重新执行Set-cookie。

process_response会调用下面的这个方法:

 def save_session(self, app, session, response):
        domain = self.get_cookie_domain(app)
        path = self.get_cookie_path(app)
        if not session:
            if session.modified:
                response.delete_cookie(app.session_cookie_name,
                                       domain=domain, path=path)
            return
        httponly = self.get_cookie_httponly(app)
        secure = self.get_cookie_secure(app)
        expires = self.get_expiration_time(app, session)
        val = self.get_signing_serializer(app).dumps(dict(session))
        response.set_cookie(app.session_cookie_name, val,
                            expires=expires, httponly=httponly,
                            domain=domain, path=path, secure=secure)

8、发送请求结束信号

到此为止,已经得到了响应类,这时发送一个request_finished.通知订阅者,我结束了。

 

9、应用将响应发送给客户端,利用响应函数response(environ, start_response)。

 

 

--------EOF---------
微信分享/微信扫码阅读