Django处理前端请求的流程梳理

初始

在前端请求到达 Django 应用时,首先到达的是 WSGI 接口(Web Server Gateway Interface)。WSGI 是 Python Web 应用和 Web 服务器之间的标准接口,Django 使用它来处理和响应 Web 请求。也就是首先会经过Django 项目中的 wsgi.py 文件

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'drf_learn.settings')

application = get_wsgi_application()

以上代码就是wsgi.py 文件, 在这里完成WSGIHandler的初始化。代码如下:

import django
from django.core.handlers.wsgi import WSGIHandler


def get_wsgi_application():
    """
    The public interface to Django's WSGI support. Return a WSGI callable.

    Avoids making django.core.handlers.WSGIHandler a public API, in case the
    internal WSGI implementation changes or moves in the future.
    Django 的 WSGI 支持的公共接口。返回一个 WSGI 可调用对象。避免将 django.core.handlers.WSGIHandler 设为公共 API,
    以防内部 WSGI 实现在未来发生变化或移动。
    """
    django.setup(set_prefix=False)
    return WSGIHandler()

WSGIHandler类内部,实现了__call__方法,WSGI服务会调用__call__方法。方法如下:

    def __call__(self, environ, start_response):
        """

        :param environ: 一个包含所有请求信息的字典,由 WSGI 服务器传入。
        :param start_response: 一个回调函数,用于开始 HTTP 响应并设置响应状态和头信息
        :return:
        """
        #  # 设置脚本前缀
        # 设置请求的脚本前缀。get_script_name 从 environ 中获取请求的路径前缀(即当前应用的路径前缀,通常在部署在子路径下时有用)。
        # Django 使用 SCRIPT_NAME 环境变量设置脚本前缀,以正确地处理多路径部署情况。
        """
        在很多应用场景中,我们可能需要将一个 Django 应用部署到服务器的某个子路径下,而不是根路径。例如:

        假设你的主域名为 https://example.com
        你的 Django 应用部署在子路径 /myapp/ 下
        客户端访问你的 Django 应用的 URL 是 https://example.com/myapp/
        在这种情况下,Django 需要知道当前的子路径 (/myapp/),以便正确地解析和生成 URL。
        这就是 SCRIPT_NAME 的作用——它告诉 Django 请求的路径前缀是什么,以便应用在生成链接时知道要添加的子路径。
        """
        # get_script_name(environ):从 environ 中读取 SCRIPT_NAME 的值,获取当前请求的路径前缀。在上例中,这个值就是 "/myapp"。
        # set_script_prefix:设置 Django 全局的脚本前缀,使得后续请求中引用的所有 URL 都会以这个前缀开头。
        set_script_prefix(get_script_name(environ))


        # # 发送请求开始信号
        # 发送 request_started 信号,通知所有接收者一个新的请求即将开始。接收者可以是中间件或其他监听这个信号的函数。
        #         # 这在一些自定义逻辑中可能有用,例如日志记录或初始化特定的上下文变量,
        """
            signals.request_started:这是 Django 的 request_started 信号对象。
            send 方法:用于发送信号,触发所有连接到该信号的接收者。
            参数解释:
                sender=self.__class__:指定信号的发送者,这里是 WSGIHandler 类本身。这样接收者可以根据发送者来判断是否处理该信号。
                environ=environ:传递给接收者的附加信息,这里是 WSGI 的环境变量字典 environ,包含了请求的所有信息。
            常见的接收者:
                数据库连接管理:
                    Django 的数据库连接组件会监听 request_started 信号,以确保在请求开始时建立数据库连接,或者重置连接。
                缓存系统:
                    一些缓存机制可能需要在请求开始时进行清理或初始化。
                第三方应用程序:
                    一些中间件或应用程序可能需要在请求开始时执行特定的逻辑,如统计请求数量、日志记录等。
            意义:允许接收者在请求处理的早期阶段执行初始化操作,确保在请求处理过程中所需的资源和环境已准备就绪。
        """
        signals.request_started.send(sender=self.__class__, environ=environ)


        # # 创建 HttpRequest 对象 将 WSGI 的 environ 字典转换为 Django 的 HttpRequest 对象。
        #  这个 HttpRequest 对象包含了所有关于请求的信息(例如路径、请求头、请求方法等),供后续处理使用。
        request = self.request_class(environ)


        # # 获取响应 处理请求,返回 HttpResponse 对象
        """
            2.1 请求处理的核心方法
                self.get_response(request) 是 Django 中处理请求的核心方法,定义在 django.core.handlers.base.BaseHandler 类中。
                它负责从接收到的 HttpRequest 对象出发,经过一系列步骤,最终生成并返回 HttpResponse 对象。
            这个方法会调用 Django 的中间件和视图,处理请求并生成响应。具体步骤如下:
                请求预处理:调用中间件的 process_request 方法。
                URL 解析:根据请求的路径,解析出对应的视图函数或类。
                视图处理:调用视图函数或类,生成响应。
                异常处理:在视图处理过程中捕获异常,进行处理。
                请求后处理:调用中间件的 process_response 方法,对响应进行后续处理。
                返回响应:最终返回一个 HttpResponse 对象。
        """
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = "%d %s" % (response.status_code, response.reason_phrase)
        # 设置响应头
        response_headers = [
            *response.items(),
            *(("Set-Cookie", c.output(header="")) for c in response.cookies.values()),
        ]
        # 开始响应
        start_response(status, response_headers)
        if getattr(response, "file_to_stream", None) is not None and environ.get(
            "wsgi.file_wrapper"
        ):
            # If `wsgi.file_wrapper` is used the WSGI server does not call
            # .close on the response, but on the file wrapper. Patch it to use
            # response.close instead which takes care of closing all files.
            # 如果使用 'wsgi.file_wrapper',则 WSGI 服务器不会在响应上调用 .close,
            # 而是在文件包装器上调用 .close。将其修补为使用 response.close,它负责关闭所有文件。
            response.file_to_stream.close = response.close
            response = environ["wsgi.file_wrapper"](
                response.file_to_stream, response.block_size
            )
        # 返回相应内容
        return response

这个 __call__ 方法实现了 Django 的 WSGIHandler 的核心处理流程。以下是方法的详细流程分解:

__call__流程梳理

1. 设置脚本前缀

set_script_prefix(get_script_name(environ))
  • 作用:设置请求的脚本前缀,使 Django 能正确处理子路径的 URL。
  • 过程
    • get_script_name(environ)environ 中读取 SCRIPT_NAME 的值,获取当前请求的路径前缀(如 /myapp)。
    • set_script_prefix 设置 Django 的全局脚本前缀,以确保生成的 URL 都包含此路径。

2. 发送 request_started 信号

signals.request_started.send(sender=self.__class__, environ=environ)
  • 作用:发送 request_started 信号,通知所有接收者一个新的请求开始。
  • 过程:Django 的信号机制允许接收者在请求开始时执行一些初始化操作,如数据库连接管理、缓存清理、请求日志记录等。

3. 创建 HttpRequest 对象

request = self.request_class(environ)
  • 作用:将 environ 字典转换为 Django 的 HttpRequest 对象。
  • 过程request_class(通常是 HttpRequest 类)从 environ 中解析请求路径、请求头、请求方法等,创建包含所有请求信息的 HttpRequest 实例,供后续处理。

4. 获取响应

response = self.get_response(request)
  • 作用:核心请求处理步骤,调用 Django 的视图或中间件,最终生成 HttpResponse 对象。
  • 具体过程
    • 请求预处理:调用中间件的 process_request 方法。
    • URL 解析:根据请求的路径解析对应的视图函数。
    • 视图处理:执行视图函数或类,生成响应。
    • 异常处理:处理视图处理过程中的异常。
    • 请求后处理:调用中间件的 process_response 方法。
  • 返回值:生成并返回一个 HttpResponse 对象。

5. 设置响应状态码和响应头

status = "%d %s" % (response.status_code, response.reason_phrase)
response_headers = [
 *response.items(),
 *(("Set-Cookie", c.output(header="")) for c in response.cookies.values()),
]
start_response(status, response_headers)
  • 作用:构建并设置 HTTP 响应的状态和头部信息。
  • 过程
    • status:通过 response.status_coderesponse.reason_phrase 生成状态字符串(如 200 OK)。
    • response_headers:将响应头和 cookies 转换为 WSGI 标准的格式。
    • start_response:调用 start_response 回调函数,发送 HTTP 响应的状态和头部信息。

6. 文件流处理(可选)

if getattr(response, "file_to_stream", None) is not None and environ.get("wsgi.file_wrapper"):
 response.file_to_stream.close = response.close
 response = environ["wsgi.file_wrapper"](response.file_to_stream, response.block_size)
  • 作用:处理文件流响应。
  • 过程
    • 检查 file_to_stream 是否存在,若存在则调用 wsgi.file_wrapper,用它来包装文件流。
    • 修改 response.file_to_stream.closeresponse.close,确保关闭文件时调用 response.close

7. 返回响应内容

return response
  • 作用:将 HttpResponse 内容返回给 WSGI 服务器。
  • 结果:WSGI 服务器将响应传回给 Web 服务器,最终返回到前端。

总结

这个 __call__ 方法的流程如下:

  1. 设置脚本前缀:确保生成的 URL 包含子路径。
  2. 发送请求开始信号:通知监听者一个请求开始了。
  3. 创建请求对象:将 WSGI 的 environ 转为 Django 的 HttpRequest
  4. 获取响应:通过 Django 的中间件和视图生成 HttpResponse
  5. 设置响应状态码和头信息:调用 start_response 发送状态和头部信息。
  6. 文件流处理:使用 wsgi.file_wrapper 包装文件流(如有必要)。
  7. 返回响应:将最终响应返回给 WSGI 服务器。

整个过程将 WSGI 服务器的请求有效地转换成 Django 的响应,并返回给客户端。在这个过程中,response = self.get_response(request) 核心请求处理步骤,调用 Django 的视图或中间件,最终生成 HttpResponse 对象。接下来我们具体分析这个方法都做了什么。一下是该方法的代码。

def get_response(self, request):
    """Return an HttpResponse object for the given HttpRequest."""
    # Setup default url resolver for this thread
    # 配置当前线程使用的 URL 配置。Django 的函数,用于设置当前线程的 URL 配置模块。
    set_urlconf(settings.ROOT_URLCONF)
    # 将请求传递给中间件链进行处理,最终生成响应。
    response = self._middleware_chain(request)
    # 在响应对象中添加资源关闭器,以便在响应完成后关闭请求相关的资源。
    response._resource_closers.append(request.close)
    if response.status_code >= 400:
        log_response(
            "%s: %s",
            response.reason_phrase,
            request.path,
            response=response,
            request=request,
        )
        return response

在这个方法中,最最最关键的就是_middleware_chain属性,我们仅需深入的分析。这个属性实际上是一个BaseHandler类的一个属性,但是该属性在类中的load_middleware方法中,被赋值为一个hander,但是hander是一个方法。代码如下:

# Adapt the top of the stack, if needed.
handler = self.adapt_method_mode(is_async, handler, handler_is_async)
# We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._middleware_chain = handler

所以,在get_response方法中,response = self._middleware_chain(request)就合理了。接下来我们详细的讲解load_middleware方法。

load_middleware 方法的主要流程是从项目设置中的 settings.MIDDLEWARE 列表中加载中间件,并将它们适配成一个链式处理流程(middleware chain),用于在请求生命周期中调用。以下是每个步骤的详细流程:

load_middleware流程梳理

1. 初始化中间件列表

self._view_middleware = []
self._template_response_middleware = []
self._exception_middleware = []
  • 解释:初始化空列表,用于存储不同类型的中间件方法:
    • self._view_middleware:存储带有 process_view 方法的中间件。
    • self._template_response_middleware:存储带有 process_template_response 方法的中间件。
    • self._exception_middleware:存储带有 process_exception 方法的中间件。
  • 作用:确保加载中间件前清空列表,避免重复添加。

2. 设置处理函数 get_responsehandler

get_response = self._get_response_async if is_async else self._get_response
handler = convert_exception_to_response(get_response)
  • 解释:根据是否异步,选择不同的响应处理方法。
    • convert_exception_to_response:装饰 get_response,捕获异常并将其转换为 HttpResponse 对象。
  • 作用:确保所有异常被捕获,并转换为标准的 HTTP 响应。

3. 逆序加载中间件

for middleware_path in reversed(settings.MIDDLEWARE):
    middleware = import_string(middleware_path)
  • 解释:通过 reversed() 方法逆序遍历 settings.MIDDLEWARE 列表。这样后添加的中间件会包裹先添加的中间件,确保请求生命周期中调用顺序正确。
  • 过程:使用 Django 的 import_string() 将字符串路径转换为中间件类。

4. 检查中间件的同步和异步支持

middleware_can_sync = getattr(middleware, "sync_capable", True)
middleware_can_async = getattr(middleware, "async_capable", False)
  • 解释:检查中间件是否支持同步或异步模式,默认值分别为 TrueFalse
  • 流程
    • 如果中间件既不支持同步也不支持异步,抛出异常。
    • 根据当前 handler 的模式(同步或异步)设置 middleware_is_async,确保中间件与当前模式兼容。

5. 适配 handler 方法

adapted_handler = self.adapt_method_mode(
    middleware_is_async, handler, handler_is_async, debug=settings.DEBUG, name="middleware %s" % middleware_path,
)
  • 解释:使用 self.adapt_method_mode 适配 handler 与中间件的模式(同步或异步),以保证兼容。
  • 作用:确保在异步环境下能够调用同步的中间件,或在同步环境下支持异步调用。

6. 创建中间件实例

mw_instance = middleware(adapted_handler)
  • 解释:创建中间件实例,并将适配后的 handler 传入。若实例创建失败或返回 None,则抛出配置错误 ImproperlyConfigured

7. 注册中间件方法

  • 检查并注册 process_view 方法

    if hasattr(mw_instance, "process_view"):
        self._view_middleware.insert(
            0,
            self.adapt_method_mode(is_async, mw_instance.process_view),
        )
    
    • 解释:若中间件实例包含 process_view 方法,将其插入 self._view_middleware 的开头。
    • 作用:确保请求处理时,按逆序调用 process_view
  • 检查并注册 process_template_response 方法

    if hasattr(mw_instance, "process_template_response"):
        self._template_response_middleware.append(
            self.adapt_method_mode(
                is_async, mw_instance.process_template_response
            ),
        )
    
    • 解释:若中间件包含 process_template_response 方法,将其添加到 self._template_response_middleware 列表。
  • 检查并注册 process_exception 方法

    if hasattr(mw_instance, "process_exception"):
        self._exception_middleware.append(
            self.adapt_method_mode(False, mw_instance.process_exception),
        )
    
    • 解释:若中间件包含 process_exception 方法,将其适配为同步模式并添加到 self._exception_middleware 列表。

8. 更新 handler 并处理异常

handler = convert_exception_to_response(mw_instance)
handler_is_async = middleware_is_async
  • 解释:将新的 mw_instance 包装为 handler,将异常捕获并转换为响应。根据中间件是否异步,更新 handler_is_async

9. 顶部适配 handler 方法

handler = self.adapt_method_mode(is_async, handler, handler_is_async)
self._middleware_chain = handler
  • 解释:最终对整个中间件链进行适配,以匹配当前模式(同步或异步)。
  • 作用:生成完整的中间件链 _middleware_chain,完成中间件的加载过程。

总结流程

  1. 初始化三个中间件列表以存储不同类型的中间件方法。
  2. 根据请求的同步或异步模式,选择合适的 get_response 处理方法。
  3. 逆序遍历 settings.MIDDLEWARE 列表并逐个加载中间件。
  4. 检查每个中间件是否支持同步或异步模式,并适配相应的 handler
  5. 将中间件中的特定方法(如 process_viewprocess_template_responseprocess_exception)添加到各自列表中。
  6. 最后生成并保存完整的中间件链 _middleware_chain

通过这些步骤,Django 可以构建一个完整的中间件链,用于处理请求的各个阶段,并捕获和处理其中的异常。在这一部分中,是有优先处理中间件,中间件处理完后才处理的请求。最最最最关键的地方在_get_response,在这个方法中解决并调用视图,然后应用 view、exception 和 template_response 中间件。此方法是 requestresponse 中间件中发生的一切。代码如下:

    def _get_response(self, request):
        """
        Resolve and call the view, then apply view, exception, and
        template_response middleware. This method is everything that happens
        inside the request/response middleware.
        解决并调用视图,然后应用 view、exception 和 template_response 中间件。此方法是 requestresponse 中间件中发生的一切。
        """
        response = None
        # 通过 resolve_request 方法,根据请求解析出对应的视图函数 callback,以及视图函数需要的参数 callback_args 和关键字参数 callback_kwargs。
        callback, callback_args, callback_kwargs = self.resolve_request(request)

        # Apply view middleware
        for middleware_method in self._view_middleware:
            response = middleware_method(
                request, callback, callback_args, callback_kwargs
            )
            if response:
                break

        if response is None:
            wrapped_callback = self.make_view_atomic(callback)
            # If it is an asynchronous view, run it in a subthread.
            # 如果它是一个异步视图,请在子线程中运行它。
            if iscoroutinefunction(wrapped_callback):
                wrapped_callback = async_to_sync(wrapped_callback)
            try:
                response = wrapped_callback(request, *callback_args, **callback_kwargs)
            except Exception as e:
                response = self.process_exception_by_middleware(e, request)
                if response is None:
                    raise

        # Complain if the view returned None (a common error).
        self.check_response(response, callback)

        # If the response supports deferred rendering, apply template
        # response middleware and then render the response
        if hasattr(response, "render") and callable(response.render):
            for middleware_method in self._template_response_middleware:
                response = middleware_method(request, response)
                # Complain if the template response middleware returned None
                # (a common error).
                self.check_response(
                    response,
                    middleware_method,
                    name="%s.process_template_response"
                    % (middleware_method.__self__.__class__.__name__,),
                )
            try:
                response = response.render()
            except Exception as e:
                response = self.process_exception_by_middleware(e, request)
                if response is None:
                    raise

        return response

_get_response方法流程处理

1. _get_response方法

_get_response方法中,Django开始解析请求的URL,并找到对应的视图函数(或视图类)。具体步骤如下:

def _get_response(self, request):
    response = None
    # 解析请求,获取对应的视图函数和参数
    callback, callback_args, callback_kwargs = self.resolve_request(request)

    # 应用视图中间件
    for middleware_method in self._view_middleware:
        response = middleware_method(request, callback, callback_args, callback_kwargs)
        if response:
            break

    if response is None:
        wrapped_callback = self.make_view_atomic(callback)
        # 如果视图函数是异步的,则转换为同步函数
        if iscoroutinefunction(wrapped_callback):
            wrapped_callback = async_to_sync(wrapped_callback)
        try:
            # 调用视图函数,获取响应
            response = wrapped_callback(request, *callback_args, **callback_kwargs)
        except Exception as e:
            # 处理异常
            response = self.process_exception_by_middleware(e, request)
            if response is None:
                raise
    ...
    return response

2.resolve_request方法

resolve_request方法通过请求的URL,使用URL解析器ResolverMatch找到对应的视图函数(callback)、位置参数(callback_args)和关键字参数(callback_kwargs)。

def resolve_request(self, request):
    urlconf = getattr(request, 'urlconf', None)
    set_urlconf(urlconf)
    resolver = get_resolver(urlconf)
    resolver_match = resolver.resolve(request.path_info)
    request.resolver_match = resolver_match
    return resolver_match.func, resolver_match.args, resolver_match.kwargs

在这里,resolver.resolve(request.path_info)会根据urls.py中定义的URL模式,匹配请求的路径,返回一个ResolverMatch对象,其中包含了视图函数。

3 获取视图函数

对于基于类的视图(Class-Based Views,CBV),在urls.py中,我们通常这样定义:

from django.urls import path
from .views import MyView

urlpatterns = [
    path('my-url/', MyView.as_view(), name='my_view'),
]

这里的MyView.as_view()会返回一个视图函数,这个函数就是callback

4. 调用视图函数

回到_get_response方法,当我们获取到callback后,经过视图中间件的处理(如果有的话),我们会调用wrapped_callback,也就是视图函数。

response = wrapped_callback(request, *callback_args, **callback_kwargs)

5. as_view方法的作用

现在,我们需要深入了解as_view方法是如何将请求处理到视图类的。

@classmethod
def as_view(cls, **initkwargs):
    def view(request, *args, **kwargs):
        # 创建视图类的实例
        self = cls(**initkwargs)
        self.setup(request, *args, **kwargs)
        if not hasattr(self, 'request'):
            raise AttributeError(
                "%s instance has no 'request' attribute. Did you override "
                "setup() and forget to call super()?" % cls.__name__
            )
        # 调用dispatch方法
        return self.dispatch(request, *args, **kwargs)

    view.view_class = cls
    view.view_initkwargs = initkwargs
    # 复制一些元数据信息
    view.__doc__ = cls.__doc__
    view.__module__ = cls.__module__
    view.__annotations__ = cls.dispatch.__annotations__
    view.__dict__.update(cls.dispatch.__dict__)

    # 如果视图类是异步的,标记为协程函数
    if cls.view_is_async:
        markcoroutinefunction(view)

    return view
5.1 as_view返回视图函数

as_view方法是基于类的视图的入口。它是一个类方法,返回一个视图函数view。当Django的URL解析器解析到这个视图时,实际上获取到的就是这个view函数。

5.2 视图函数view的执行流程

view函数被调用时,会执行以下步骤:

  1. 实例化视图类self = cls(**initkwargs),这里的cls就是视图类本身。
  2. 设置请求和参数self.setup(request, *args, **kwargs),这个方法将request和其他参数绑定到视图实例上。
  3. 调用dispatch方法return self.dispatch(request, *args, **kwargs)dispatch方法根据请求的HTTP方法(如GET、POST)分发到对应的处理方法(如getpost)。

6. 视图类的dispatch方法

dispatch方法是处理请求的核心,它会根据请求的方法名,调用视图类中对应的方法。

def dispatch(self, request, *args, **kwargs):
    # 如果视图类有特定的http_method_names,例如['get', 'post']
    if request.method.lower() in self.http_method_names:
        handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
    else:
        handler = self.http_method_not_allowed
    return handler(request, *args, **kwargs)

最终通过handler(request, *args, **kwargs)调用的views.py文件中的get方法,

from rest_framework.response import Response
from rest_framework.views import APIView


class HelloWorldView(APIView):
    def get(self, request):
        return Response({"message": "Hello World!"})

上一篇:JMeter项目实战


下一篇:llamaIndex和langchain对比及优劣对比