Flask 请求到来阶段

3.请求到来

from flask import Flask

app = Flask(__name__)

app.config.from_object('config.setting')

@app.before_request
def f1():
    print("f1")

@app.before_request
def f2():
    print("f2")

@app.after_request
def f3(response):
    print("f3")
    return response

@app.after_request
def f4(response):
    print("f4")
    return response

@app.route("/index")
def index():
    return "Hello World"

# 只在第一次请求到来的时候最先执行,之后不在执行。
@app.before_first_request
def f5():
    print("f5")
if __name__ == '__main__':
    app.run()

所写代码,实例化对象,启动程序并加上对应的装饰器。

3.1 实例化对象

class Flask:
    config_class = Config
    def __init__(
        self,
        import_name: str,
        static_url_path: t.Optional[str] = None,
        static_folder: t.Optional[t.Union[str, os.PathLike]] = "static",
        static_host: t.Optional[str] = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: t.Optional[str] = "templates",
        instance_path: t.Optional[str] = None,
        instance_relative_config: bool = False,
        root_path: t.Optional[str] = None,
    ):
        self.config = self.make_config(instance_relative_config)
        self.before_first_request_funcs: t.List[BeforeFirstRequestCallable] = []
        self.url_map = self.url_map_class()

3.2 加载配置文件

# 执行的是当前对象的config属性的方法
app.config.from_object('config.setting')

# app 相当于 self 
self.config = self.make_config(instance_relative_config)
def make_config(self, instance_relative: bool = False) -> Config:
    """Used to create the config attribute by the Flask constructor.
    The `instance_relative` parameter is passed in from the constructor
    of Flask (there named `instance_relative_config`) and indicates if
    the config should be relative to the instance path or the root path
    of the application.

    .. versionadded:: 0.8
    """
    # 返回值是 Config 类型的对象 
    root_path = self.root_path
    if instance_relative:
        root_path = self.instance_path 
    defaults = dict(self.default_config)
    defaults["ENV"] = get_env()
    defaults["DEBUG"] = get_debug_flag()
    # config_class 为Flask的类属性,可以供对象调用。此处加括号即为实例化相关的对象
    return self.config_class(root_path, defaults)

Config 类

class Config:
    def __init__(self, root_path: str, defaults: t.Optional[dict] = None) -> None:
        # 默认配置文件的字典或者是空字典
        dict.__init__(self, defaults or {})
        self.root_path = root_path

实力化Config返回一个字典,其中存储了相关的配置项。

3.3 请求前的装饰器

@setupmethod
def before_request(self, f: BeforeRequestCallable) -> BeforeRequestCallable:
    # 将被装饰的函数当做参数传入
    """Register a function to run before each request.

    For example, this can be used to open a database connection, or
    to load the logged in user from the session.

    .. code-block:: python

        @app.before_request
        def load_user():
            if "user_id" in session:
                g.user = db.session.get(session["user_id"])

    The function will be called without any arguments. If it returns
    a non-``None`` value, the value is 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

补充:视图函数执行完成之后执行的after_request与这里的原理类似。

3.4 执行视图函数

相关博客:https://www.cnblogs.com/Blogwj123/p/16522441.html

3.5 程序启动

def __call__(self, environ: dict, start_response: t.Callable) -> t.Any:
    """The WSGI server calls the Flask application object as the
    WSGI application. This calls :meth:`wsgi_app`, which can be
    wrapped to apply middleware.
    """
    return self.wsgi_app(environ, start_response)

执行app.__call__方法。

def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
    """The actual WSGI application. This is not implemented in
    :meth:`__call__` so that middlewares can be applied without
    losing a reference to the app object. 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.

    .. versionchanged:: 0.7
        Teardown events for the request and app contexts are called
        even if an unhandled error occurs. Other events may not be
        called depending on when an error occurs during dispatch.
        See :ref:`callbacks-and-errors`.

    :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.
    """
    # 传入的请求环境 environ。实例化上下文的对象
    ctx = self.request_context(environ)
    error: t.Optional[BaseException] = None
    try:
        try:
            # 执行对象的 push().将上下文进行入栈
            ctx.push()
            # 3.6 full_dispatch_request,执行视图函数等相关的操作
            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) # 出现异常时销毁上下文,

request_context 函数。

def request_context(self, environ: dict) -> RequestContext:
    """Create a :class:`~flask.ctx.RequestContext` representing a
    WSGI environment. Use a ``with`` block to push the context,
    which will make :data:`request` point at this request.

    See :doc:`/reqcontext`.

    Typically you should not call this from your own code. A request
    context is automatically pushed by the :meth:`wsgi_app` when
    handling a request. Use :meth:`test_request_context` to create
    an environment and context instead of this method.

    :param environ: a WSGI environment
    """
    # 返回responseContext对象,对应的类如下所示
    return RequestContext(self, environ)

RequestContext 类

class RequestContext:
 
    def __init__(
        self,
        app: "Flask",
        environ: dict,
        request: t.Optional["Request"] = None,
        session: t.Optional["SessionMixin"] = None,
    ) -> 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: t.List[t.Optional["AppContext"]] = []

        self.preserved = False

        self._preserved_exc = None

        self._after_request_functions: t.List[AfterRequestCallable] = []

    @property
    def g(self) -> AppContext:
        return _app_ctx_stack.top.g # 返回应用上下文中的 g

    @g.setter
    def g(self, value: AppContext) -> None:
        _app_ctx_stack.top.g = value # 将 g 写入应用上下文中

    def copy(self) -> "RequestContext":
        return self.__class__(
            self.app,
            environ=self.request.environ,
            request=self.request,
            session=self.session,
        )

    def match_request(self) -> None:

        try:
            result = self.url_adapter.match(return_rule=True)
            self.request.url_rule, self.request.view_args = result  
        except HTTPException as e:
            self.request.routing_exception = e
	
    # ctx.push()直接调用此方法
    def push(self) -> None:
		# _app_ctx_stack = LocalStack()
        top = _request_ctx_stack.top
        if top is not None and top.preserved: # 不为空的时候弹出对应的元素
            top.pop(top._preserved_exc)
        # 取出对应的存储元素,此时由于LocalStack 的线程隔离作用,线程之间是安全的
        app_ctx = _app_ctx_stack.top
        
        # 检查请求的应用上下文存储的值是否存在.
        if app_ctx is None or app_ctx.app != self.app:
            # 存在 且不是None  和 原来的值
            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)
		
        # _request_ctx_stack = LocalStack()请求上下文中的push
        _request_ctx_stack.push(self)# 将当前对象放入请求上下文中

        if self.session is None:
            session_interface = self.app.session_interface
            self.session = session_interface.open_session(self.app, self.request)

            if self.session is None:
                self.session=session_interface.make_null_session(self.app)


        if self.url_adapter is not None:
            self.match_request()

    def pop(self, exc: t.Optional[BaseException] = _sentinel) -> None:  
        app_ctx = self._implicit_app_ctx_stack.pop()
        clear_request = False
        try:
            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)

                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
            ), f"Popped wrong request context. ({rv!r} instead of {self!r})"

    def auto_pop(self, exc: t.Optional[BaseException]) -> None:
        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  # type: ignore
        else:
            self.pop(exc)

    def __enter__(self) -> "RequestContext":
        # 支持上下文管理,入口
        self.push()
        return self

    def __exit__(
        self, exc_type: type, exc_value: BaseException, tb: TracebackType
    ) -> None:
        # 支持上下文管理,出口
        self.auto_pop(exc_value)

    def __repr__(self) -> str:
        return (
            f"<{type(self).__name__} {self.request.url!r}"
            f" [{self.request.method}] of {self.app.name}>"
        )
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
# 这两个对象已经被创建,其他地方只使用的相关对象的,是单例模式。

localstack 的函数

@property
def top(self) -> t.Any:
    """The topmost item on the stack.  If the stack is empty,
    `None` is returned.
    """
    try:
        return self._local.stack[-1] # 取出栈顶的元素
    except (AttributeError, IndexError):
        return None

flask 的两个核心上下文对象,主要是有应用上下文与请求上下文结合使用,其中使用了线程安全隔离的技术。

3.6 视图函数中的源码。

def full_dispatch_request(self) -> Response:
    """Dispatches the request and on top of that performs request
    pre and postprocessing as well as HTTP exception catching and
    error handling.

    .. versionadded:: 0.7
    """
    # 执行特殊的装饰器 @app.before_first_request
    self.try_trigger_before_first_request_functions()
    try:
        request_started.send(self)
        # 执行相关的请求信息
        rv = self.preprocess_request()
        if rv is None:
            # 请求上下文中取出request 匹配 路由信息,去view_funcs中获取相关视图函数信息加括号执行
            rv = self.dispatch_request()
    except Exception as e:
        rv = self.handle_user_exception(e)
    return self.finalize_request(rv)

根据 endpoint查找视图函数

def preprocess_request(self) -> t.Optional[ResponseReturnValue]:
    """Called before the request is dispatched. Calls
    :attr:`url_value_preprocessors` registered with the app and the
    current blueprint (if any). Then calls :attr:`before_request_funcs`
    registered with the app and the blueprint.

    If any :meth:`before_request` handler returns a non-None value, the
    value is handled as if it was the return value from the view, and
    further request handling is stopped.
    """
    names = (None, *reversed(request.blueprints))

    for name in names:
        
        if name in self.url_value_preprocessors:
            for url_func in self.url_value_preprocessors[name]:
                # 传入参数为 endpoint 和 视图参数
                url_func(request.endpoint, request.view_args)

    for name in names:
        if name in self.before_request_funcs:
            # 检查是否存在 before——request函数需要执行
            for before_func in self.before_request_funcs[name]:
                rv = self.ensure_sync(before_func)()

                if rv is not None:
                    return rv

    return None

执行视图函数

def dispatch_request(self) -> ResponseReturnValue:
    """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.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)

执行完之后,按照wsgi_app中的流程销毁上下文对象。

posted @ 2022-08-07 16:58  紫青宝剑  阅读(89)  评论(0)    收藏  举报