在django启动流程源码分析中,最后是父进程开启一个子进程,子进程开启一个守护线程开启WSGI,开启WSGI时会传递一个handler参数,那么django如何处理请求就跟这个handler有关系,从handler最开始出现的位置开始分析。 在django.core.management.commands.Command类的inner_run()方法里把这个 handler传给run()方法启动WSGI服务。
注:django.core.management.commands.Command类在"python环境/site-packages/django/core/management/commands/runserver.py"文件里
handler = self.get_handler(*args, **options)
在本类中找到了这个 get_handler()方法:
def get_handler(self, *args, **options):
"""Return the default WSGI handler for the runner."""
return get_internal_wsgi_application()
get_handler()方法里调用get_internal_wsgi_application()函数并返回结果。 查找get_internal_wsgi_application()函数
from django.core.servers.basehttp import WSGIServer, get_internal_wsgi_application
代码:
def get_internal_wsgi_application():
"""
Load and return the WSGI application as configured by the user in
``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
this will be the ``application`` object in ``projectname/wsgi.py``.
This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
for Django's internal server (runserver); external WSGI servers should just
be configured to point to the correct application object directly.
If settings.WSGI_APPLICATION is not set (is ``None``), return
whatever ``django.core.wsgi.get_wsgi_application`` returns.
"""
from django.conf import settings
app_path = getattr(settings, "WSGI_APPLICATION")
if app_path is None:
return get_wsgi_application()
try:
return import_string(app_path)
except ImportError as err:
raise ImproperlyConfigured(
"WSGI application '%s' could not be loaded; "
"Error importing module." % app_path
) from err
"from django.conf import settings"引用了settings实例,从这个实例的WSGI_APPLICATION属性拿到在settings.py里配置的值:
WSGI_APPLICATION = 'myproject.wsgi.application'
如果WSGI_APPLICATION没有配置,通过 get_wsgi_application()获取返回。如果存在就会根据这个值指向获取并且返回:
return import_string(app_path)
由于项目是通过startproject 创建的,这个文件是有的,在myproject.wsgi里创建了这个application
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_wsgi_application()
查看get_wsgi_application
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.setup(set_prefix=False)
return WSGIHandler()
前面调用了django.setup(set_prefix=False),这个我们在启动过程有分析,核心是: 扫描应用里的apps.py文件,按照一定的创建规则创建AppConfig类示例,存放在apps.app_configs的字典中。这里仍然需要调用django.setup(set_prefix=False),因为线上环境基本都是Gunicorn / uWSGI启动的,不会通过python manage.py runserver 启动。 在这个函数里返回WSGIHandler类对象。找到WSGIHandler类定义:
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.load_middleware()
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
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.OutputString()) 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.
response.file_to_stream.close = response.close
response = environ["wsgi.file_wrapper"](
response.file_to_stream, response.block_size
)
return response
(1)定义了一个类属性request_class,初始值为WSGIRequest,定义了一个__call__()方法,意味着外部返回的示例对象加()这个方法会被调用,我们交给ESGI虽然是实例,但是WSGI使用时是这样使用的"名称(environ,start_response)",所以这里的__call__就是在实现WSGI的application (2)在初始化__init__()函数里,调用了父类的初始化,同时调用了self.load_middleware(),我们猜测这里self.load_middleware()应该是加载中间件。 (3)继承base.BaseHandler类
往上找到这个base.BaseHandler类:
class BaseHandler:
_view_middleware = None
_template_response_middleware = None
_exception_middleware = None
_middleware_chain = None
BaseHandlermer没有写__init__()方法,所以WSGIHandler类里super().init(*args, **kwargs)会调用object.init(*args, **kwargs)。 在这个类中的定义了4个类属性,初始值为None
我们再回头看看 WSGIHandler类__init__()方法里self.load_middleware()在实现什么,load_middleware()方法在本类没有重写,在父类中有定义:
def load_middleware(self, is_async=False):
"""
Populate middleware lists from settings.MIDDLEWARE.
Must be called after the environment is fixed (see __call__ in
subclasses).
"""
self._view_middleware = []
self._template_response_middleware = []
self._exception_middleware = []
get_response = self._get_response_async if is_async else self._get_response
handler = convert_exception_to_response(get_response)
handler_is_async = is_async
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
middleware_can_sync = getattr(middleware, "sync_capable", True)
middleware_can_async = getattr(middleware, "async_capable", False)
if not middleware_can_sync and not middleware_can_async:
raise RuntimeError(
"Middleware %s must have at least one of "
"sync_capable/async_capable set to True." % middleware_path
)
elif not handler_is_async and middleware_can_sync:
middleware_is_async = False
else:
middleware_is_async = middleware_can_async
try:
# Adapt handler, if needed.
adapted_handler = self.adapt_method_mode(
middleware_is_async,
handler,
handler_is_async,
debug=settings.DEBUG,
name="middleware %s" % middleware_path,
)
mw_instance = middleware(adapted_handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if str(exc):
logger.debug("MiddlewareNotUsed(%r): %s", middleware_path, exc)
else:
logger.debug("MiddlewareNotUsed: %r", middleware_path)
continue
else:
handler = adapted_handler
if mw_instance is None:
raise ImproperlyConfigured(
"Middleware factory %s returned None." % middleware_path
)
if hasattr(mw_instance, "process_view"):
self._view_middleware.insert(
0,
self.adapt_method_mode(is_async, mw_instance.process_view),
)
if hasattr(mw_instance, "process_template_response"):
self._template_response_middleware.append(
self.adapt_method_mode(
is_async, mw_instance.process_template_response
),
)
if hasattr(mw_instance, "process_exception"):
# The exception-handling stack is still always synchronous for
# now, so adapt that way.
self._exception_middleware.append(
self.adapt_method_mode(False, mw_instance.process_exception),
)
handler = convert_exception_to_response(mw_instance)
handler_is_async = middleware_is_async
# 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
(1)将本对象拥有的类属性(sef.后变为实例属性)重置为空列表
self._view_middleware = []
self._template_response_middleware = []
self._exception_middleware = []
同时这3个变为实例属性,实例优先使用。 (2)声明get_response变量 get_response = self._get_response_async if is_async else self._get_response 是异步还是同步,由于没有传递参数,所以is_async是默认的False get_response=self._get_response,具体_get_response在干嘛,这里暂时不看。 这里只是把_get_response方法内存地址赋值给get_response变量。 (3)声明handler变量 handler = convert_exception_to_response(get_response)
def convert_exception_to_response(get_response):
"""
Wrap the given get_response callable in exception-to-response conversion.
All exceptions will be converted. All known 4xx exceptions (Http404,
PermissionDenied, MultiPartParserError, SuspiciousOperation) will be
converted to the appropriate response, and all other exceptions will be
converted to 500 responses.
This decorator is automatically applied to all middleware to ensure that
no middleware leaks an exception and that the next middleware in the stack
can rely on getting a response instead of an exception.
"""
if iscoroutinefunction(get_response):
@wraps(get_response)
async def inner(request):
try:
response = await get_response(request)
except Exception as exc:
response = await sync_to_async(
response_for_exception, thread_sensitive=False
)(request, exc)
return response
return inner
else:
@wraps(get_response)
def inner(request):
try:
response = get_response(request)
except Exception as exc:
response = response_for_exception(request, exc)
return response
return inner
convert_exception_to_response函数就是一个装饰器,里面通过iscoroutinefunction函数判断get_response是同步还是异步的,我们这里是分析同步,所以会执行else,handler仍然指向get_response方法 (4)中间件处理
for middleware_path in reversed(settings.MIDDLEWARE):
middleware = import_string(middleware_path)
middleware_can_sync = getattr(middleware, "sync_capable", True)
middleware_can_async = getattr(middleware, "async_capable", False)
if not middleware_can_sync and not middleware_can_async:
raise RuntimeError(
"Middleware %s must have at least one of "
"sync_capable/async_capable set to True." % middleware_path
)
elif not handler_is_async and middleware_can_sync:
middleware_is_async = False
else:
middleware_is_async = middleware_can_async
try:
# Adapt handler, if needed.
adapted_handler = self.adapt_method_mode(
middleware_is_async,
handler,
handler_is_async,
debug=settings.DEBUG,
name="middleware %s" % middleware_path,
)
mw_instance = middleware(adapted_handler)
except MiddlewareNotUsed as exc:
if settings.DEBUG:
if str(exc):
logger.debug("MiddlewareNotUsed(%r): %s", middleware_path, exc)
else:
logger.debug("MiddlewareNotUsed: %r", middleware_path)
continue
else:
handler = adapted_handler
if mw_instance is None:
raise ImproperlyConfigured(
"Middleware factory %s returned None." % middleware_path
)
if hasattr(mw_instance, "process_view"):
self._view_middleware.insert(
0,
self.adapt_method_mode(is_async, mw_instance.process_view),
)
if hasattr(mw_instance, "process_template_response"):
self._template_response_middleware.append(
self.adapt_method_mode(
is_async, mw_instance.process_template_response
),
)
if hasattr(mw_instance, "process_exception"):
# The exception-handling stack is still always synchronous for
# now, so adapt that way.
self._exception_middleware.append(
self.adapt_method_mode(False, mw_instance.process_exception),
)
handler = convert_exception_to_response(mw_instance)
handler_is_async = middleware_is_async
循环倒转的settings.MIDDLEWARE(settings.py配置的MIDDLEWARE中间件列表)
-
根据字符串获取里面定义的类 middleware = import_string(middleware_path)
-
声明两个标志变量 middleware_can_sync = getattr(middleware, "sync_capable", True) # 中间件能不能处理同步请求 middleware_can_async = getattr(middleware, "async_capable", False) #中间件能不能处理异步请求
-
判断中间件
pythonif not middleware_can_sync and not middleware_can_async: raise RuntimeError( "Middleware %s must have at least one of " "sync_capable/async_capable set to True." % middleware_path ) elif not handler_is_async and middleware_can_sync: middleware_is_async = False else: middleware_is_async = middleware_can_async中间件sync_capable/async_capable至少有一个是是True,否则抛出异常。 声明一个变量 middleware_is_async 如果中间件sync_capable设置为True并且传进来的handler_is_async是False,middleware_is_async=False 其他情况 middleware_is_async=middleware_can_async 在我们这里 handler_is_async=False,middleware_is_async=False
-
adapted_handler
pythondef adapt_method_mode( self, is_async, method, method_is_async=None, debug=False, name=None, ): """ Adapt a method to be in the correct "mode": - If is_async is False: - Synchronous methods are left alone - Asynchronous methods are wrapped with async_to_sync - If is_async is True: - Synchronous methods are wrapped with sync_to_async() - Asynchronous methods are left alone """ if method_is_async is None: method_is_async = iscoroutinefunction(method) if debug and not name: name = name or "method %s()" % method.__qualname__ if is_async: if not method_is_async: if debug: logger.debug("Synchronous handler adapted for %s.", name) return sync_to_async(method, thread_sensitive=True) elif method_is_async: if debug: logger.debug("Asynchronous handler adapted for %s.", name) return async_to_sync(method) return method里面是对同步和异步的不同处理。这我们这里,method 原样返回 adapted_handler仍然是get_response
-
创建mw_instance实例 使用middleware类,传递get_response创建中间件实例 mw_instance = middleware(adapted_handler)
-
判断实例mw_instance是否存在
pythonif mw_instance is None: raise ImproperlyConfigured( "Middleware factory %s returned None." % middleware_path )如果不存在抛出异常
-
process_view
textif 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_template_response
pythonif 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方法,将这个方法append到self._template_response_middleware里,最终self._template_response_middleware与配置里的顺序相反
-
process_exception
pythonif hasattr(mw_instance, "process_exception"): # The exception-handling stack is still always synchronous for # now, so adapt that way. self._exception_middleware.append( self.adapt_method_mode(False, mw_instance.process_exception), )如果有process_exception方法,将这个方法append到sself._exception_middleware里,最终self._exception_middleware与配置里的顺序相反
-
变量重新赋值
texthandler = convert_exception_to_response(mw_instance) handler_is_async = middleware_is_async将mw_instance赋给handler,在下一个循环中,当前实例作为参数去创建下一个mw_instance 下一个mw_instance = middleware(adapted_handler)里adapted_handler就是上一个中间件实例
这个循环就是(屏蔽同步和异步判断实现):反转循环中间件列表,将get_response注入到最后一个中间件实例,然后将最后实例作为参数注入给倒数第二个中间件类的实例中,以此类推。
跳出循环,此时handler是中间件列表最上面的那个实例对象,然后存放在实例的self._middleware_chain中
# 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
所以self.load_middleware()完成就是根据settings.py中配置的MIDDLEWARE列表,将相关值初始化到WSGIHandler()实例中。到这里WSGIHandler()实例化完成,最终层层交给到WSGI。
当请求到来,上面的实例会被这样使用:
application(environ,start_response)
application是WSGIHandler类的实例,会调用WSGIHandlerl类创建的实例的__call__()方法:
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
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.OutputString()) 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.
response.file_to_stream.close = response.close
response = environ["wsgi.file_wrapper"](
response.file_to_stream, response.block_size
)
return response
这里再放上wsgiref源码的结论:
status要求:
*至少4个字符
*前3个必须是数字
*数字后面需要空格
*返回的response需要是一个可迭代对象
*environ,start_response是WSGI传给这个函数,我们只要专注修改
代码分析: set_script_prefix(get_script_name(environ))
def get_script_name(environ):
"""
Return the equivalent of the HTTP request's SCRIPT_NAME environment
variable. If Apache mod_rewrite is used, return what would have been
the script name prior to any rewriting (so it's the script name as seen
from the client's perspective), unless the FORCE_SCRIPT_NAME setting is
set (to anything).
"""
if settings.FORCE_SCRIPT_NAME is not None:
return settings.FORCE_SCRIPT_NAME
# If Apache's mod_rewrite had a whack at the URL, Apache set either
# SCRIPT_URL or REDIRECT_URL to the full resource URL before applying any
# rewrites. Unfortunately not every web server (lighttpd!) passes this
# information through all the time, so FORCE_SCRIPT_NAME, above, is still
# needed.
script_url = get_bytes_from_wsgi(environ, "SCRIPT_URL", "") or get_bytes_from_wsgi(
environ, "REDIRECT_URL", ""
)
if script_url:
if b"//" in script_url:
# mod_wsgi squashes multiple successive slashes in PATH_INFO,
# do the same with script_url before manipulating paths (#17133).
script_url = _slashes_re.sub(b"/", script_url)
path_info = get_bytes_from_wsgi(environ, "PATH_INFO", "")
script_name = script_url.removesuffix(path_info)
else:
script_name = get_bytes_from_wsgi(environ, "SCRIPT_NAME", "")
return script_name.decode()
这个函数的作用时拿到请求里的SCRIPT_NAME,if else 是在做不同环境的适配。如果在settings.py 里有定义FORCE_SCRIPT_NAME,则直接返回这个配置。默认 SCRIPT_NAME是空的
set_script_prefix代码:
def set_script_prefix(prefix):
"""
Set the script prefix for the current thread.
"""
if not prefix.endswith("/"):
prefix += "/"
_prefixes.value = prefix
如果拿到的SCRIPT_NAME不是以"/"结尾,那就在尾部加上,然后复制给全局变量_prefixes实例的value属性。接着执行下面代码:
signals.request_started.send(sender=self.__class__, environ=environ)
查询signals.request_started,request_started 是 django.dispatch包定义的一个 Signal类。
from django.dispatch import Signal
request_started = Signal()
request_finished = Signal()
got_request_exception = Signal()
setting_changed = Signal()
找到Signal类代码:
class Signal:
"""
Base class for all signals
Internal attributes:
receivers:
[
(
(id(receiver), id(sender)),
ref(receiver),
ref(sender),
is_async,
)
]
sender_receivers_cache:
WeakKeyDictionary[sender, list[receiver]]
"""
def __init__(self, use_caching=False):
"""
Create a new signal.
"""
self.receivers = []
self.lock = threading.Lock()
self.use_caching = use_caching
# For convenience we create empty caches even if they are not used.
# A note about caching: if use_caching is defined, then for each
# distinct sender we cache the receivers that sender has in
# 'sender_receivers_cache'. The cache is cleaned when .connect() or
# .disconnect() is called and populated on send().
self.sender_receivers_cache = weakref.WeakKeyDictionary() if use_caching else {}
self._dead_receivers = False
这个类__init__()里初始化了个实例属性,use_caching是默认的False。回到这里:
signals.request_started.send(sender=self.__class__, environ=environ)
self.__class__是WSGIHandler,environ是WSGI传进来的参数,send()方法在Signal类中有定义
def send(self, sender, **named):
"""
Send signal from sender to all connected receivers.
If any receiver raises an error, the error propagates back through
send, terminating the dispatch loop. So it's possible that all
receivers won't be called if an error is raised.
If any receivers are asynchronous, they are called after all the
synchronous receivers via a single call to async_to_sync(). They are
also executed concurrently with asyncio.TaskGroup().
Arguments:
sender
The sender of the signal. Either a specific object or None.
named
Named arguments which will be passed to receivers.
Return a list of tuple pairs [(receiver, response), ... ].
"""
if (
not self.receivers
or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
):
return []
responses = []
sync_receivers, async_receivers = self._live_receivers(sender)
for receiver in sync_receivers:
response = receiver(signal=self, sender=sender, **named)
responses.append((receiver, response))
if async_receivers:
async def asend():
async_responses = await _run_parallel(
*(
receiver(signal=self, sender=sender, **named)
for receiver in async_receivers
)
)
return zip(async_receivers, async_responses)
responses.extend(async_to_sync(asend)())
return responses
代码分析: (1)判断
if (
not self.receivers
or self.sender_receivers_cache.get(sender) is NO_RECEIVERS
):
return []
如果self.receivers空或者self.sender_receivers_cache.get(sender)拿到的是NO_RECEIVERS是 NO_RECEIVERS类型,直接返回空列表[]
这里的sender是WSGIHandler类,上层传过来的值 (2)定义了一个局部空列表
responses = []
(3)获取同步异步接受者
sync_receivers, async_receivers = self._live_receivers(sender)
回到WSGIHandler类的__calls__方法,继续执行
request = self.request_class(environ)
self.request_class就是WSGIRequest类,在WSGIHandler类定义了一个类属性
class WSGIHandler(base.BaseHandler):
request_class = WSGIRequest
class WSGIRequest(HttpRequest):
def __init__(self, environ):
script_name = get_script_name(environ)
# If PATH_INFO is empty (e.g. accessing the SCRIPT_NAME URL without a
# trailing slash), operate as if '/' was requested.
path_info = get_path_info(environ) or "/"
self.environ = environ
self.path_info = path_info
# be careful to only replace the first slash in the path because of
# http://test/something and http://test//something being different as
# stated in RFC 3986.
self.path = "%s/%s" % (script_name.rstrip("/"), path_info.replace("/", "", 1))
self.META = environ
self.META["PATH_INFO"] = path_info
self.META["SCRIPT_NAME"] = script_name
self.method = environ["REQUEST_METHOD"].upper()
# Set content_type, content_params, and encoding.
self._set_content_type_params(environ)
try:
content_length = int(environ.get("CONTENT_LENGTH"))
except (ValueError, TypeError):
content_length = 0
self._stream = LimitedStream(self.environ["wsgi.input"], content_length)
self._read_started = False
self.resolver_match = None
init()函数定义了一些属性,通过调用各种方法,将environ信息规整到实例属性中。 request就是一个WSGIRequest类实例 继续往下,调用self.get_response(request)方法,返回值赋值给response
response = self.get_response(request)
这个方法在WSGIHandler类中没有定义,在父类中有定义:
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
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
代码分析: (1)注册ROOT_URLCONF到_urlconfs
set_urlconf(settings.ROOT_URLCONF)
在settings.py中定义 ROOT_URLCONF = 'myproject.urls'
def set_urlconf(urlconf_name):
"""
Set the URLconf for the current thread or asyncio task (overriding the
default one in settings). If urlconf_name is None, revert back to the
default.
"""
if urlconf_name:
_urlconfs.value = urlconf_name
else:
if hasattr(_urlconfs, "value"):
del _urlconfs.value
将ROOT_URLCONF注册给django.urls模块里的Local类实例_urlconfs的value属性中。
(2)获取response response = self.middleware_chain(request) self.middleware_chain指向的是MIDDLEWARE列表中第一个实例,这里是实例(),说明这个实例肯定有个__call()方法,根据MRO总能找得到。
我们找一个已经写好的中间件类看看实例化过程,以"django.middleware.security.SecurityMiddleware"为例:
import re
from django.conf import settings
from django.http import HttpResponsePermanentRedirect
from django.utils.deprecation import MiddlewareMixin
class SecurityMiddleware(MiddlewareMixin):
def __init__(self, get_response):
super().__init__(get_response)
self.sts_seconds = settings.SECURE_HSTS_SECONDS
self.sts_include_subdomains = settings.SECURE_HSTS_INCLUDE_SUBDOMAINS
self.sts_preload = settings.SECURE_HSTS_PRELOAD
self.content_type_nosniff = settings.SECURE_CONTENT_TYPE_NOSNIFF
self.redirect = settings.SECURE_SSL_REDIRECT
self.redirect_host = settings.SECURE_SSL_HOST
self.redirect_exempt = [re.compile(r) for r in settings.SECURE_REDIRECT_EXEMPT]
self.referrer_policy = settings.SECURE_REFERRER_POLICY
self.cross_origin_opener_policy = settings.SECURE_CROSS_ORIGIN_OPENER_POLICY
类里第一个调用super().init(get_response),后面的都是基于settings对象里的值创建实例属性,所以重点看super().init(get_response)
查看父类MiddlewareMixin代码:
class MiddlewareMixin:
sync_capable = True
async_capable = True
def __init__(self, get_response):
if get_response is None:
raise ValueError("get_response must be provided.")
self.get_response = get_response
# If get_response is a coroutine function, turns us into async mode so
# a thread is not consumed during a whole request.
self.async_mode = iscoroutinefunction(self.get_response)
if self.async_mode:
# Mark the class as async-capable, but do the actual switch inside
# __call__ to avoid swapping out dunder methods.
markcoroutinefunction(self)
super().__init__()
(1)定义了两个属性
sync_capable = True
async_capable = True
默认值都是True,标记中间件是否同步异步。 如果自定义类继承了MiddlewareMixin,最好重写下这两个值。 (2)__init__方法中 将传进来的get_response注册到本类的get_response属性中,根据上面的分析,在WSGIHandler实例化时,init()里会调用self.load_middleware(),这里面进行了一系列初始化。
self.get_response = get_response
所以settings.py中MIDDLEWARE列表里的第一个实例的get_response属性存的是第二个里的实例,一次类推,最后一个实例的get_response属性保存的才是get_response。 (3)异步处理及调用object类__init__()方法
self.async_mode = iscoroutinefunction(self.get_response)
if self.async_mode:
# Mark the class as async-capable, but do the actual switch inside
# __call__ to avoid swapping out dunder methods.
markcoroutinefunction(self)
super().__init__()
接下来就是__call__方法,这个定义在MiddlewareMixin类中:
def __call__(self, request):
# Exit out to async mode, if needed
if self.async_mode:
return self.__acall__(request)
response = None
if hasattr(self, "process_request"):
response = self.process_request(request)
response = response or self.get_response(request)
if hasattr(self, "process_response"):
response = self.process_response(request, response)
return response
如果response有返回值,当前中间件直接返回response,如果没有执行self.process_request(request),调用下一层中间件实例的response。 如果都通过,就会执行我们最后中间件指向的get_response,这个就是WSGIHandler类里__init__()方法 self.load_middleware()里注入的。
回到WSGIHandler类继承的base.BaseHandler类:
class BaseHandler:
_view_middleware = None
_template_response_middleware = None
_exception_middleware = None
_middleware_chain = None
def load_middleware(self, is_async=False):
"""
Populate middleware lists from settings.MIDDLEWARE.
Must be called after the environment is fixed (see __call__ in
subclasses).
"""
self._view_middleware = []
self._template_response_middleware = []
self._exception_middleware = []
get_response = self._get_response_async if is_async else self._get_response
就是这个load_middleware注入给最后一个中间件实例的get_response给,这个在BaseHandler类中有定义,代码如下:
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.
"""
response = None
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
代码解析: (1)resolve_request
def resolve_request(self, request):
"""
Retrieve/set the urlconf for the request. Return the view resolved,
with its args and kwargs.
"""
# Work out the resolver.
if hasattr(request, "urlconf"):
urlconf = request.urlconf
set_urlconf(urlconf)
resolver = get_resolver(urlconf)
else:
resolver = get_resolver()
# Resolve the view, and assign the match object back to the request.
resolver_match = resolver.resolve(request.path_info)
request.resolver_match = resolver_match
return resolver_match
如果request进入if,没有就else,这个默认应该是没有的,直接进入else:
resolver = get_resolver()
查看get_resolver()是什么:
def get_resolver(urlconf=None):
if urlconf is None:
urlconf = settings.ROOT_URLCONF
return _get_cached_resolver(urlconf)
如果 urlconf是None,从settings.ROOT_URLCONF,返回_get_cached_resolver(urlconf) 看看_get_cached_resolver()又是什么:
@functools.cache
def _get_cached_resolver(urlconf=None):
return URLResolver(RegexPattern(r"^/"), urlconf)
使用了functools.cache装饰器缓存
URLResolver:
class URLResolver:
def __init__(
self, pattern, urlconf_name, default_kwargs=None, app_name=None, namespace=None
):
self.pattern = pattern
# urlconf_name is the dotted Python path to the module defining
# urlpatterns. It may also be an object with an urlpatterns attribute
# or urlpatterns itself.
self.urlconf_name = urlconf_name
self.callback = None
self.default_kwargs = default_kwargs or {}
self.namespace = namespace
self.app_name = app_name
self._reverse_dict = {}
self._namespace_dict = {}
self._app_dict = {}
# set of dotted paths to all functions and classes that are used in
# urlpatterns
self._callback_strs = set()
self._populated = False
self._local = Local()
RegexPattern源码:
class RegexPattern(CheckURLMixin):
regex = LocaleRegexDescriptor()
def __init__(self, regex, name=None, is_endpoint=False):
self._regex = regex
self._regex_dict = {}
self._is_endpoint = is_endpoint
self.name = name
self.converters = {}
所以get_resolver()返回的是URLResolver类实例 继续往下:
resolver_match = resolver.resolve(request.path_info)
上面代码中 request.path_info就是请求中不含域名(或者ip:8000)及get参数的(如果有)的url路径,查看resolve()方法源码:
def resolve(self, path):
path = str(path) # path may be a reverse_lazy object
tried = []
match = self.pattern.match(path)
if match:
new_path, args, kwargs = match
for pattern in self.url_patterns:
try:
sub_match = pattern.resolve(new_path)
except Resolver404 as e:
self._extend_tried(tried, pattern, e.args[0].get("tried"))
else:
if sub_match:
# Merge captured arguments in match with submatch
sub_match_dict = {**kwargs, **self.default_kwargs}
# Update the sub_match_dict with the kwargs from the
# sub_match.
sub_match_dict.update(sub_match.kwargs)
# If there are *any* named groups, ignore all non-named
# groups. Otherwise, pass all non-named arguments as
# positional arguments.
sub_match_args = sub_match.args
if not sub_match_dict:
sub_match_args = args + sub_match.args
current_route = (
""
if isinstance(pattern, URLPattern)
else str(pattern.pattern)
)
self._extend_tried(tried, pattern, sub_match.tried)
return ResolverMatch(
sub_match.func,
sub_match_args,
sub_match_dict,
sub_match.url_name,
[self.app_name, *sub_match.app_names],
[self.namespace, *sub_match.namespaces],
self._join_route(current_route, sub_match.route),
tried,
captured_kwargs=sub_match.captured_kwargs,
extra_kwargs={
**self.default_kwargs,
**sub_match.extra_kwargs,
},
)
tried.append([pattern])
raise Resolver404({"tried": tried, "path": new_path})
raise Resolver404({"path": path})
返回ResolverMatch实例
return ResolverMatch(
sub_match.func,
sub_match_args,
sub_match_dict,
sub_match.url_name,
[self.app_name, *sub_match.app_names],
[self.namespace, *sub_match.namespaces],
self._join_route(current_route, sub_match.route),
tried,
captured_kwargs=sub_match.captured_kwargs,
extra_kwargs={
**self.default_kwargs,
**sub_match.extra_kwargs,
},
)
在resolve_request()方法最后返回了这个实例
(2)继续回到_get_response()方法解析 解压3个变量
callback, callback_args, callback_kwargs = self.resolve_request(request)
由于class ResolverMatch类实现了__getitem__方法:
def __getitem__(self, index):
return (self.func, self.args, self.kwargs)[index]
callback就是sub_match.func:视图函数 callback_args就是sub_match_args:匿名位置参数 callback_kwargs就是sub_match_dict:业务参数字典 (3)运行process_view
for middleware_method in self._view_middleware:
response = middleware_method(
request, callback, callback_args, callback_kwargs
)
if response:
break
循环遍历self._view_middleware,这个self.load_middleware(),然后执行这个方法,上上买3个参数传进去,只要相应不为我空直接返回。 (4)如果(3)执行后response仍然为None
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
在视图函数真正执行前,像包粽子一样,给它动态套上全自动的数据库‘防爆服(事务锁)’,在发生任何代码报错时,100% 捍卫数据库的数据一致性。
def make_view_atomic(self, view):
non_atomic_requests = getattr(view, "_non_atomic_requests", set())
for alias, settings_dict in connections.settings.items():
if settings_dict["ATOMIC_REQUESTS"] and alias not in non_atomic_requests:
if iscoroutinefunction(view):
raise RuntimeError(
"You cannot use ATOMIC_REQUESTS with async views."
)
view = transaction.atomic(using=alias)(view)
return view
调用被包裹的wrapped_callback函数,返回response (5)校验(4)返回的response
self.check_response(response, callback)
def check_response(self, response, callback, name=None):
"""
Raise an error if the view returned None or an uncalled coroutine.
"""
if not (response is None or asyncio.iscoroutine(response)):
return
if not name:
if isinstance(callback, types.FunctionType): # FBV
name = "The view %s.%s" % (callback.__module__, callback.__name__)
else: # CBV
name = "The view %s.%s.__call__" % (
callback.__module__,
callback.__class__.__name__,
)
if response is None:
raise ValueError(
"%s didn't return an HttpResponse object. It returned None "
"instead." % name
)
elif asyncio.iscoroutine(response):
raise ValueError(
"%s didn't return an HttpResponse object. It returned an "
"unawaited coroutine instead. You may need to add an 'await' "
"into your view." % name
)
如果response是None 或者返回了一个尚未被唤醒(忘写 await)的异步协程就抛出异常
(6)是否响应模版
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
如果响应有render类似return render(request, "my_template.html", {"name": "Tom"}) 就调用这个方法。 依然是循环self._template_response_middleware 这个是跟中间件列表倒序
(7)返回response 到这里_get_response()调用完成了
程序返回到self.get_response(request) self._middleware_chain(request)完成了,我们拿到了response
def get_response(self, request):
"""Return an HttpResponse object for the given HttpRequest."""
# Setup default url resolver for this thread
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
如果状态吗大于400,打印日志,最后get_response里返回这个response
程序回到get_response调用出:
def __call__(self, environ, start_response):
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
request = self.request_class(environ)
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.OutputString()) 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.
response.file_to_stream.close = response.close
response = environ["wsgi.file_wrapper"](
response.file_to_stream, response.block_size
)
return response
(1)注入WSGIHandler类到response
response._handler_class = self.__class__
(2)格式化status
status = "%d %s" % (response.status_code, response.reason_phrase)
(3)设置响应头
response_headers = [
*response.items(),
*(("Set-Cookie", c.OutputString()) for c in response.cookies.values()),
]
(4)调用start_response
start_response(status, response_headers)
(5)文件相关处理
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.
response.file_to_stream.close = response.close
response = environ["wsgi.file_wrapper"](
response.file_to_stream, response.block_size
)
解析:
response.file_to_stream.close = response.close
把文件对象的.close方法替换成响应对象的.close方法,WSGI服务器调用file_wrapper.close()时,实际执行的是response.close()。
response = environ["wsgi.file_wrapper"](
response.file_to_stream, response.block_size
)
用WSGI服务器的文件包装器把文件流包装成高效的分块传输格式: response.file_to_stream:要传输的文件对象 response.block_size:每次传输的块大小(如8192字节) 效果:服务器可以分块读取文件并传输给客户端,避免一次性把大文件全部加载到内存
(6)返回response
评论
0 条