Django源码分析 wsgi应用实现、中间件加载和调用顺序

在分析代码前先来看下wsgi的介绍

WSGI的全称是Web Server Gateway Interface,Web服务器网关接口。具体的来说,WSGI是一个规范,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。具体可参考官方文档

 wsgi中有两端,一端是server端,一端是application端,application端必须是可调用对象,所以django的WSGIHandler实现了__call__方法

application必须返回可迭代对象

pep-0333给出了示例代码参考,代码注释中也说了,如果使用类的实例作为application需要实现__call__方法,django正是使用了这种方法

environ包含了http请求信息的dict

start_response是一个发送http响应的函数

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']


class AppClass:
    """Produce the same output, but using a class

    (Note: 'AppClass' is the "application" here, so calling it
    returns an instance of 'AppClass', which is then the iterable
    return value of the "application callable" as required by
    the spec.

    If we wanted to use *instances* of 'AppClass' as application
    objects instead, we would have to implement a '__call__'
    method, which would be invoked to execute the application,
    and we would need to create an instance for use by the
    server or gateway.
    """

    def __init__(self, environ, start_response):
        self.environ = environ
        self.start = start_response

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield "Hello world!\n"

下面我们来看下django wsgi application的代码,我们以django正是部署时为例,如果使用manage.py runserver 8000的话会涉及到静态文件支持相关的代码,增加分析的复杂度

正式部署时入口文件为项目下的wsgi文件

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'toystore.settings')
# 获取wsgi application
application = get_wsgi_application()
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()

返回WSGIHandler实例,这个实例是个可调用对象,在初始化时加载了中间件,先看下中间件的加载过程,再分析下application被调用时的处理流程

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
# 加载中间件 self.load_middleware()
class BaseHandler:
    _view_middleware = None
    _template_response_middleware = None
    _exception_middleware = None
    _middleware_chain = None

    def load_middleware(self):
        """
        Populate middleware lists from settings.MIDDLEWARE.

        Must be called after the environment is fixed (see __call__ in subclasses).
        """
# 存储中间件的process_view方法 self._view_middleware = []
# 存储中间件的process_template_response方法 self._template_response_middleware
= []
# 存储中间件的process_exception方法 self._exception_middleware
= [] # 这里handler是经过包装后的_get_response,convert_exception_to_response对一些异常做了处理 handler = convert_exception_to_response(self._get_response)
# 逆序迭代中间件,我们以创建项目后默认的中间件为例,第一个加载的是XFrameOptionsMiddleware,handler作为初始化参数赋予self.get_response,最后handler被赋值成
# 经过包装后的中间件实例的__call__,接着MessageMiddleWare中间件实例化时get_response值为这个handler(),如此往复,直到迭代结束。下面会详细分析下中间件,以便了解中间件是如何实现中间件链的
# MIDDLEWARE = [ #'django.middleware.security.SecurityMiddleware', #'django.contrib.sessions.middleware.SessionMiddleware', #'django.middleware.common.CommonMiddleware', #'django.middleware.csrf.CsrfViewMiddleware', #'django.contrib.auth.middleware.AuthenticationMiddleware', #'django.contrib.messages.middleware.MessageMiddleware', #'django.middleware.clickjacking.XFrameOptionsMiddleware', #] for middleware_path in reversed(settings.MIDDLEWARE): middleware = import_string(middleware_path) try: mw_instance = middleware(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 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, mw_instance.process_view)
if hasattr(mw_instance, 'process_template_response'):
# 这里方法按中间件逆序排列 self._template_response_middleware.append(mw_instance.process_template_response)
if hasattr(mw_instance, 'process_exception'):
# 这里方法按中间件逆序排列 self._exception_middleware.append(mw_instance.process_exception) handler
= convert_exception_to_response(mw_instance) # We only assign to this when initialization is complete as it is used # as a flag for initialization being complete. self._middleware_chain = handler 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 if hasattr(request, 'urlconf'): urlconf = request.urlconf set_urlconf(urlconf) resolver = get_resolver(urlconf) else: resolver = get_resolver() resolver_match = resolver.resolve(request.path_info) callback, callback_args, callback_kwargs = resolver_match request.resolver_match = resolver_match # 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) try: response = wrapped_callback(request, *callback_args, **callback_kwargs) except Exception as e: response = self.process_exception_by_middleware(e, request) # Complain if the view returned None (a common error). if response is None: if isinstance(callback, types.FunctionType): # FBV view_name = callback.__name__ else: # CBV view_name = callback.__class__.__name__ + '.__call__' raise ValueError( "The view %s.%s didn't return an HttpResponse object. It " "returned None instead." % (callback.__module__, view_name) ) # If the response supports deferred rendering, apply template # response middleware and then render the response elif 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). if response is None: raise ValueError( "%s.process_template_response didn't return an " "HttpResponse object. It returned None instead." % (middleware_method.__self__.__class__.__name__) ) try: response = response.render() except Exception as e: response = self.process_exception_by_middleware(e, request) return response

现在回到到WSGIHandler,初始化的时候加载了中间件,且是逆序加载的,此时wsgi应用就初始化完成了,在被调用时会执行__call__方法,这里主要涉及到中间件的调用,下面画个图来理解下中间件的加载和调用过程,以默认的中间件为例,加载顺序自下而上,调用process_request自上而下,调用process_response自下而上

MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

在调用完所有中间件的process_request后,会调用WSGIHandler._get_response

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)
# 处理request和response,这里的调用的是BaseHandler的get_response 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'): response = environ['wsgi.file_wrapper'](response.file_to_stream)
# 返回response给wsgi服务器
return response
class BaseHandler:
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._closable_objects.append(request) if response.status_code >= 400: log_response( '%s: %s', response.reason_phrase, request.path, response=response, request=request, ) return response 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
# 与路由相关的处理,这部分代码还未分析,后面另起一篇分析
if hasattr(request, 'urlconf'): urlconf = request.urlconf set_urlconf(urlconf) resolver = get_resolver(urlconf) else: resolver = get_resolver() resolver_match = resolver.resolve(request.path_info) callback, callback_args, callback_kwargs = resolver_match request.resolver_match = resolver_match # Apply view middleware
# 处理请求,调用视图,渲染response等,返回response,这部分代码还未分析,后面另起一篇进行详细分析
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) try: response = wrapped_callback(request, *callback_args, **callback_kwargs) except Exception as e: response = self.process_exception_by_middleware(e, request) # Complain if the view returned None (a common error). if response is None: if isinstance(callback, types.FunctionType): # FBV view_name = callback.__name__ else: # CBV view_name = callback.__class__.__name__ + '.__call__' raise ValueError( "The view %s.%s didn't return an HttpResponse object. It " "returned None instead." % (callback.__module__, view_name) ) # If the response supports deferred rendering, apply template # response middleware and then render the response elif 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). if response is None: raise ValueError( "%s.process_template_response didn't return an " "HttpResponse object. It returned None instead." % (middleware_method.__self__.__class__.__name__) ) try: response = response.render() except Exception as e: response = self.process_exception_by_middleware(e, request) return response
class MiddlewareMixin:
    def __init__(self, get_response=None):
        self.get_response = get_response
        super().__init__()

    def __call__(self, request):
        response = None
# 检查中间件自己是否有process_request方法,有就调用,对request进行处理
if hasattr(self, 'process_request'): response = self.process_request(request)
# 这里如果response是None则继续调用下一个中间件的__call__;如果上面process_request返回非None,则后续中间件将都不会被调用 response
= response or self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response

小结:

本章主要分析了django中wsgi应用的实现,顺带分析了一下中间件的加载与调用过程

1.wsgi只是简单的了解了一下,实际开发中可能并不会做进一步的开发;

2.中间件还是比较重要的,通过这次分析源码,知道了process_request,process_view顺序是自上而下的,他们在请求阶段被调用;process_reponse,process_template_response,process_exception都是自下而上,在响应阶段被调用

3.process_request返回None时,后续中间件的process_request会继续处理,反之则不会

 

下一篇分析下路由相关的代码

第一次分析源码,还有许多不足之处,欢迎指正

posted @ 2021-02-04 16:46  小麦香茶  阅读(417)  评论(0)    收藏  举报