全局对象request解析

flask全局对象之request

由源代码布局可知,globals.py保存flask的全局对象,包括

  1. current_app
  2. request
  3. g

我们首先来讲讲request对象:
源码:

from functools import partial
from werkzeug.local import LocalProxy
from werkzeug.local import LocalStack

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)

_request_ctx_stack = LocalStack()
request = LocalProxy(partial(_lookup_req_object, "request"))

由代码可知,request的实现主要依赖于LocalProxyLocalStack,简单说一说LocalProxyLocalStack

  • LocalProxy:werkzeug实现的一种代理对象,可知实现被代理对象的动态更新。
  • LocalStack:LocalStack是werkzeug库实现的栈,对象压入的规则简单总结为先入后出,并且可以方便的访问栈顶对象以及各种常用操作,比如push、pop等。

werkzeug实现的具体细节,感兴趣的同学可以翻看源码,后续我们也会一起来看。

接下来,看一下request的对象的具体的工作原理和生命周期:

  • 生命周期:request的对象的进栈和出栈的逻辑都在Flask类的wsgi_app方法中进行的,我们来看一下:
class Flask(_PackageBoundObject):

    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)

由代码可知,当http请求到达时,flask先通过environ参数构造RequestContext对象ctx,然后调用ctx.push(),在请求处理完毕后调用ctx.auto_pop(error),整个请求流程就结束了,request的进栈和出栈也同时完成。

接下来看看RequestContextpush方法,到底做了什么操作:

class RequestContext(object):

    def __init__(self, app, environ, request=None, session=None):
        self.app = app
        if request is None:
            request = app.request_class(environ)
        self.request = request
        self.url_adapter = None
        try:
            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 = []

    def push(self):

        top = _request_ctx_stack.top
        if top is not None and top.preserved:
            top.pop(top._preserved_exc)

        app_ctx = _app_ctx_stack.top
        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()

        _request_ctx_stack.push(self)

    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)

            assert rv is self, "Popped wrong request context. (%r instead of %r)" % (
                rv,
                self,
            )

    def auto_pop(self, exc):
        if self.request.environ.get("flask._preserve_context") or (
            exc is not None and self.app.preserve_context_on_exception
        ):
            self.preserved = True
            self._preserved_exc = exc
        else:
            self.pop(exc)

push方法可知,此时不仅进行reqeuest的入栈操作,同时进行app对象的入栈操作,这样我导入from flask import request,就可以保障_request_ctx_stack的栈顶是当前请求信息构造的request对象,方便我们对请求信息的读取,并在处理完毕后,进行pop操作,清理_request_ctx_stack,等待下次请求的到达。

  • 工作原理
request = LocalProxy(partial(_lookup_req_object, "request"))  

由全局变量的逻辑可知,request本质是RequestContext实例的request属性,而RequestContext实例的request属性是Flask类的request_class属性,即Request类,综合而看request就是Request类实例化对象。而Request有多个父类,包含以下种类:

  • JSONMinxin
  • AcceptMixin
  • ETagRequestMixin
  • UserAgentMixin
  • AuthorizationMixin
  • CORSRequestMixin
  • CommonRequestDescriptorsMixin

这样request实例就被赋予多种属性与方法,方便在调用时访问当前http请求的各种信息。

「真诚赞赏,手留余香」

roc

请我喝杯咖啡?

使用微信扫描二维码完成支付