初始
在前端请求到达 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_code
和response.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.close
为response.close
,确保关闭文件时调用response.close
。
- 检查
7. 返回响应内容
return response
- 作用:将
HttpResponse
内容返回给 WSGI 服务器。 - 结果:WSGI 服务器将响应传回给 Web 服务器,最终返回到前端。
总结
这个 __call__
方法的流程如下:
- 设置脚本前缀:确保生成的 URL 包含子路径。
- 发送请求开始信号:通知监听者一个请求开始了。
- 创建请求对象:将 WSGI 的
environ
转为 Django 的HttpRequest
。 - 获取响应:通过 Django 的中间件和视图生成
HttpResponse
。 - 设置响应状态码和头信息:调用
start_response
发送状态和头部信息。 - 文件流处理:使用
wsgi.file_wrapper
包装文件流(如有必要)。 - 返回响应:将最终响应返回给 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_response
和 handler
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)
- 解释:检查中间件是否支持同步或异步模式,默认值分别为
True
和False
。 - 流程:
- 如果中间件既不支持同步也不支持异步,抛出异常。
- 根据当前
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
,完成中间件的加载过程。
总结流程
- 初始化三个中间件列表以存储不同类型的中间件方法。
- 根据请求的同步或异步模式,选择合适的
get_response
处理方法。 - 逆序遍历
settings.MIDDLEWARE
列表并逐个加载中间件。 - 检查每个中间件是否支持同步或异步模式,并适配相应的
handler
。 - 将中间件中的特定方法(如
process_view
、process_template_response
、process_exception
)添加到各自列表中。 - 最后生成并保存完整的中间件链
_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
函数被调用时,会执行以下步骤:
- 实例化视图类:
self = cls(**initkwargs)
,这里的cls
就是视图类本身。 - 设置请求和参数:
self.setup(request, *args, **kwargs)
,这个方法将request
和其他参数绑定到视图实例上。 - 调用
dispatch
方法:return self.dispatch(request, *args, **kwargs)
,dispatch
方法根据请求的HTTP方法(如GET、POST)分发到对应的处理方法(如get
、post
)。
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!"})