django源码分析之请求响应流程

web应用或者网站本质上都是围绕着请求-响应的方式来运作的。当你通过浏览器访问网站时,浏览器会向web服务器发送请求。当web服务器收到请求后,服务器会对请求进行相应的处理,然后返回相应的响应给浏览器,最后浏览器呈现给你。
毫无意外,Django应用也是如此。它也需要给用户发送的请求返回相应的响应。接着我将会通过分析Django框架的代码来解释Django的请求响应流程是如何的。

代码分析

Django应用最开始的入口处在wsgi.py,这里是从web服务器转发请求到Django应用的地方,同时也是Django应用返回响应给web服务器的地方。

# django/core/handlers/wsgi.py
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)
        # 根据web服务器传入的参数,初始化request请求
        request = self.request_class(environ)
        # Django开始处理请求,生成响应
        response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%d %s' % (response.status_code, response.reason_phrase)
        response_headers = list(response.items())
        for c in response.cookies.values():
            response_headers.append(('Set-Cookie', c.output(header='')))
        # wsgi 协议,调用start_response,然后给web服务器
        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)
        return response

上面这段代码可以看到WSGIHandler是引导Django应用运用的地方,从这里才开始能真正处理请求以及返回响应给web服务器。具体的操作相关的代码在base.BaseHandler,先去base.BaseHandler深入分析self.load_middleware()加载中间件的过程.

#django/core/handlers/base.py
class BaseHandler:
    _request_middleware = None
    _view_middleware = None
    _template_response_middleware = None
    _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).
        """
        #中间件链表,这其中的_request_middleware和_response_middleware已经没有用处,这不表示请求中间件和响应中间件没用,只是用了新的机制来应用
        self._request_middleware = []
        self._view_middleware = []
        self._template_response_middleware = []
        self._response_middleware = []
        self._exception_middleware = []

        #将self._get_response包装在异常处理内,此时的handler是一个包装了_get_response的修饰器函数,进行了异常处理。
        handler = convert_exception_to_response(self._get_response)
        # 遍历settings配置文件中的中间件
        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
                )
            '''
            可以看到,这里只有对_view_middleware,_template_response_middleware,_exception_middleware这3类中间件的链表进行了处理。
            '''
            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)
            # 是这里对_request_middleware和_response_middleware进行了处理。对handler进行了一层包装。handler = convert_exception_to_response(middleware(handler))
            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.
        # 所以这里的_middleware_chain就是一个修饰器函数,他最内层是真正的_get_response,外层则是哪些中间件
        self._middleware_chain = handler

可以看到self.load_middleware()是加载中间件的过程。

可以看到主循环是对配置文件里中间件进行逆序遍历,_view_middleware链表是执行的insert(0操作,所以这个链接最后的顺序是和配置文件的顺序相同。而_template_response_middleware_exception_middleware都是append的操作,所以最后生成的链表顺序是和配置文件的顺序相反。这里面比较特殊的是_request_middleware_response_middleware,并没有使用其他3种中间件那样的链表,而是用了包装器,外层的中间件包装着里面的中间件,最里面的是_get_response。其实这两类中间件都会继承自MiddlewareMixin,看完它就一目了然了。

# django/utils/deprecation.py
class MiddlewareMixin:
    def __init__(self, get_response=None):
        self.get_response = get_response
        super().__init__()

    def __call__(self, request):
        response = None
        # 处理request中间件
        if hasattr(self, 'process_request'):
            response = self.process_request(request)
        response = response or self.get_response(request)
        # 处理response中间件
        if hasattr(self, 'process_response'):
            response = self.process_response(request, response)
        return response

所以这里我们可以看出大致流程,request中间件和view中间件的处理顺序和配置顺序相同,另外3种,则是相反的顺序。

继续看self.get_response(request),看看是如何处理请求,生成响应的。

#django/core/handlers/base.py
def get_response(self, request):
    """Return an HttpResponse object for the given HttpRequest."""
    # Setup default url resolver for this thread
    # 根据settings.ROOT_URLCONF设置urlconf,初始化路由
    set_urlconf(settings.ROOT_URLCONF)

    # 调用包装了self._get_response的_middleware_chain
    response = self._middleware_chain(request)

    response._closable_objects.append(request)

    # If the exception handler returns a TemplateResponse that has not
    # been rendered, force it to be rendered.
    if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):
        response = response.render()

    if response.status_code == 404:
        logger.warning(
            'Not Found: %s', request.path,
            extra={'status_code': 404, 'request': request},
        )

    return response

这里是WSGIHandler处理请求调用的函数,最关键的就是_middleware_chain(request), 他是被中间件包装了的_get_response的handler。最后看self._get_response.

#django/core/handlers/base.py
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.
    如注释说的,_get_response是被request/response中间件包装在最里面的。所以这里面就是实际调用具体view的地方了。
    """
    response = None

    if hasattr(request, 'urlconf'):
        urlconf = request.urlconf
        set_urlconf(urlconf)
        resolver = get_resolver(urlconf)
    else:
        resolver = get_resolver()

    # 通过request的url来匹配得到具体的view
    resolver_match = resolver.resolve(request.path_info)
    callback, callback_args, callback_kwargs = resolver_match
    request.resolver_match = resolver_match

    # Apply view middleware
    # 先调用view中间件
    for middleware_method in self._view_middleware:
        response = middleware_method(request, callback, callback_args, callback_kwargs)
        if response:
            break
    # 这里调用匹配到的具体的view
    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

上面的self._get_response就是通过请求的url匹配具体的view,然后调用view的过程。通过url匹配具体的view的原理后面再写一篇文章来介绍。

总结

通过上面代码分析,我们已经大致了解了Django请求响应的流程。大致如下
用户请求首先会到web服务器;
web服务器会把请求发到django.core.handlers.wsgiBaseHandler
生成request,response,view, exception,template_response中间件链表;
按中间件配置顺序应用request中间件来处理request,如果这中间生成response,则直接返回;
通过urlresolvers.resolve匹配请求的url来找到对应的view;
应用view中间件,如果有response,则直接返回;
调用对应的view,这个过程和和models进行交互,比如从数据库获取数据等,并渲染模板;
接着response中间件会被应用来处理repsonse;
这其中忽略了一些其他重要的步骤,比如异常中间件的调用。
最后放一张网上的图,我觉得画的比较形象

c0c87634e652807dbebe683a5cc46482.png

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容