Django 從啓動到請求到響應全過程分析-入門版

本文所用 Django 代碼版本:2.1.3python

本文中進行的分析並不侷限於某一個 Django 版本但都會盡可能討論版本 2.0+django

流程總覽

Django 處理請求流程

概述:

Django 和其餘 Web 框架的 HTTP 處理的流程大體相同:先經過 Request Middleware 對請求對象作定義處理,而後再經過默認的 URL 指向的方法,最後再經過 Response Middleware 對響應對象作自定義處理。瀏覽器

細則:

  1. [啓動->WSGI]經過任意方式啓動 Django 建立 WSGIServer 類的實例
  2. 用戶經過瀏覽器請求某個 Django 頁面
  3. [WSGI]Django WSGIServer 接收客戶端(瀏覽器)請求初始化 WSGIHandler 實例
  4. [WSGI->加載配置]導入 setting 配置和 Django 異常類
  5. [WSGI->中間件]加載 setting 中設置的中間件
  6. [中間件]建立 _request_middleware,_view_middleware,_response_middleware,_exception_middleware 四個列表
  7. [中間件]遍歷執行 _request_middleware,對 request 進行處理:若返回 None 進入到 8;若直接返回 HttpResponse 對象進入到 12
  8. [URL Resolver]解析 url 並進行匹配(假設匹配成功)
  9. [中間件]遍歷執行 _view_middleware,對 request 進行處理:若返回 None 進入到 10;若直接返回 HttpResponse 對象進入到 12。
  10. [中間件]實現 url 匹配的 view 邏輯:若引起異常進入到 11;若正常返回 HttpResponse 對象進入到 12
  11. [中間件]遍歷執行 _exception_middleware
  12. [中間件]遍歷執行 _response_middleware,對 HttpResponse 進行處理並最終返回 response

啓動

在開發環境中,咱們通常是經過命令行執行 runserver 命令,ruserver 命令是使用 Django 自帶的的 Web Server,而在正式的環境中,通常會使用 Nginx+uWSGI 模式。bash

不管經過哪一種方式,啓動一個項目時,都會作兩件事:服務器

  1. 建立一個 WSGIServer 類的實例,來接受用戶的請求。
  2. 當一個用戶的 HTTP 請求到達的時,爲用戶指定一個 WSGIHandler,用於處理用戶請求與響應,這個 Handler 是處理整個 Request 的核心。

WSGI

WSGI:全稱 Web Server Gateway Interfacecookie

WSGI 不是服務器,Python 模塊,框架,API 或者任何軟件,只是一種規範,描述 Web Server 如何與 Web Application 通訊的規範。架構

WSGI 協議主要包括 serverapplication 兩部分:app

  1. WSGI Server 負責從客戶端接收請求,將 request 轉發給 application,將application 返回的 response 返回給客戶端;
  2. WSGI Application 接收由 server 轉發的 request,處理請求,並將處理結果返回給 serverapplication 中能夠包括多個棧式的中間件(middlewares),這些中間件須要同時實現 serverapplication,所以能夠在 WSGI 服務器與 WSGI 應用之間起調節做用:對服務器來講,中間件扮演應用程序,對應用程序來講,中間件扮演服務器。

Django WSGI Application

WSGI Application 應該實現爲一個可調用對象,例如:函數、方法、類(包含 call 方法)。框架

須要接收兩個參數:socket

  1. 包含客戶端請求的信息以及其餘信息的字典。能夠認爲是請求上下文,通常叫作environment(編碼中多簡寫爲 environenv);
  2. 用於發送 HTTP 響應狀態(HTTP Status)、響應頭(HTTP Headers)的回調函數;

經過回調函數將響應狀態和響應頭返回給 WSGI Server,同時返回響應正文,響應正文是可迭代的、幷包含了多個字符串。下面是 Django WSGI Application 的具體實現:

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 = list(response.items())
        for c in response.cookies.values():
            response_headers.append(('Set-Cookie', c.output(header='')))
        # 本文做者注:將響應的 header 和 status 返回給 server
        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
複製代碼

能夠看出 Django WSGI Application 的流程包括:

  1. 加載全部中間件,以及執行框架相關的操做,設置當前線程腳本前綴,發送請求開始信號;
  2. 處理請求,調用 get_response() 方法處理當前請求,該方法的的主要邏輯是經過urlconf 找到對應的 viewcallback,按順序執行各類 middlewarecallback
  3. 調用由 WSGI Server 傳入的 start_response() 方法將響應 headerstatus 返回給 WSGI Server
  4. 返回響應正文。

Django WSGI Server

負責獲取 HTTP 請求,將請求傳遞給 Django WSGI Application,由 Django WSGI Application 處理請求後返回 response。以 Django 內建 server 爲例看一下具體實現。

經過 runserver 命令運行 Django 項目,在啓動時都會調用下面的 run 方法,建立一個 WSGIServer 的實例,以後再調用其 serve_forever() 方法啓動服務。

def run(addr, port, wsgi_handler, ipv6=False, threading=False, server_cls=WSGIServer):
    server_address = (addr, port)
    if threading:
        httpd_cls = type('WSGIServer', (socketserver.ThreadingMixIn, server_cls), {})
    else:
        httpd_cls = server_cls
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        httpd.daemon_threads = True
    httpd.set_app(wsgi_handler)
    httpd.serve_forever()
複製代碼

下圖表示 WSGI Server 服務器處理流程中關鍵的類和方法(來自:參考引用_1

WSGI Server 服務器處理流程中關鍵的類和方法

1. WSGIServer

run() 方法會建立 WSGIServer 實例,主要做用是接收客戶端請求,將請求傳遞給WSGI Application,而後將 WSGI Application 返回的 response 返回給客戶端。

  • 建立實例時會指定 HTTP 請求的 handlerWSGIRequestHandler 類;
  • 經過 set_appget_app 方法設置和獲取 WSGIApplication 實例wsgi_handler;
  • 處理 HTTP 請求時,調用 handler_request 方法,會建立 WSGIRequestHandler 實例處理 HTTP 請求;
  • WSGIServerget_request 方法經過 socket 接受請求數據;

2. WSGIRequestHandler

  • WSGIServer 在調用 handle_request 時建立實例,傳入 request,cient_address,WSGIServer 三個參數,__init__ 方法在實例化同時還會調用自身的 handle 方法;
  • handle 方法會建立 ServerHandler 實例,而後調用其 run 方法處理請求;

3. ServerHandler

  • WSGIRequestHandler 在其 handle 方法中調用 run 方法,傳入self.server.get_app() 參數,獲取 WSGIApplication,而後調用實例(call),獲取 response,其中會傳入 start_response 回調,用來處理返回的 headerstatus
  • 經過 application 獲取 response 之後,經過 finish_response 返回 response

4. WSGIHandler(即 Django WSGI Application)

  • WSGI 協議中的 application,接收兩個參數,environ 字典包含了客戶端請求的信息以及其餘信息,能夠認爲是請求上下文,start_response 用於發送返回 statusheader 的回調函數

雖然上面一個 Django WSGI Server 涉及到多個類實現以及相互引用,但其實原理仍是調用WSGIHandler,傳入請求參數以及回調方法 start_response(),並將響應返回給客戶端。

Python wsgiref simple_server

Python3.7 的源碼中給出了一個 simple_server 案例位於 python3.7/wsgiref/simple_server.py

模塊實現了一個簡單的 HTTP 服務器,並給出了一個簡單的 demo,能夠直接運行,運行結果會將請求中涉及到的環境變量在瀏覽器中展現出來。其中包括上述描述的整個 HTTP 請求的全部組件:ServerHandler,WSGIServer,WSGIRequestHandler 以及 demo_app 表示的簡易版的 WSGIApplication

感興趣的話能夠本身去看一下源碼。

加載配置

Django 的配置都在 {project_name}/settings.py 中定義,能夠是 Django 的配置,也能夠是自定義的配置,而且都經過 django.conf.settings 訪問。

中間件-Middleware

概述:

Django 中的 Middleware 相似底層中一個輕量級的插件系統,它可以介入 Django 的請求和響應過程,在全局修改 Django 的輸入和輸出內容。從流程總覽圖中能夠看出 Django 請求處理過程的核心在於 MiddlewareDjango 中全部的請求和響應都有 Middleware 的參與。

細則:

中間件流程圖

一個 HTTP 請求,首先被轉化成一個 HttpRequest 對象,而後該對象被傳遞給 Request Middleware 處理,若是它返回了 HttpResponse 對象,則直接傳遞給 Response Middleware 作收尾處理。不然的話 Request Middleware 將訪問 URL 配置,肯定目標 view 來處理 HttpRequest 對象,在肯定了 view,可是尚未執行時候,系統會把 HttpRequest 對象傳遞給 View Middleware 進行處理,若是它返回了 HttpResponse 對象,那麼該 HttpResponse 對象將被傳遞給 Response Middleware 進行後續處理,不然將執行肯定的 view 函數處理並返回 HttpResponse 對象,在整個過程當中若是引起了異常並拋出,會被 Exception Middleware 進行處理。

中間件執行順序

中間件流程圖

在請求階段,調用視圖以前,Django 按照 setting.py 設置的順序,自頂向下應用遍歷執行 Request Middleware。你能夠把它想象成一個洋蔥:每一箇中間件類都是一個「層」,它覆蓋了洋蔥的核心。若是請求經過洋蔥的全部層(每個調用 get_response)以將請求傳遞到下一層,一直到內核的視圖,那麼響應將在返回的過程當中經過每一個層(以相反的順序)。

如何編寫本身的中間件即中間件的深刻了解

編寫一個本身的中間件是很容易的,每一箇中間件組件都是一個獨立的 Python Class,你能夠在自定義的 Class 下編寫一個或多個下面的方法:

process_request

函數樣式:process_request(request)

參數解析:request 是一個 HTTPRequest 對象;

調用時間:在 Django 決定執行哪一個 view 以前,process_request() 會被請求調用;

產生響應:它應該返回一個 None 或一個 HttpResponse 對象,若是返回 NoneDjango 會繼續處理這個請求;若是它返回一個 HTTPResponse 對象,Django 會直接跳轉到 Response Middleware

process_view

函數樣式:process_view(request, view_func, view_args, view_kwargs)

參數解析:request 是一個 HTTPRequest 對象,view_funcDjango 會調用的一個函數(準確的說是一個函數對象而非一個表示函數名的字符串),view_args 是一個會被傳遞到視圖的 *argsview_kwargs 是一個會被傳遞到視圖的 **kwargsview_argsview_kwargs 都不包括 request

調用時間:process_view() 會在 Django 調用 view 前被調用;

產生響應:它應該返回一個 None 或一個 HttpResponse 對象,若是返回 NoneDjango 會繼續處理這個請求;若是它返回一個 HTTPResponse 對象,Django 會直接跳轉到 Response Middleware

PS:除 CsrfViewMiddleware 外中間件運行時在視圖運行前或在 process_view() 中訪問 request.POST 會使得以後的全部視圖沒法修改 request,因此應該儘可能避免。

process_template_response

函數樣式:process_template_response(request, response)

參數解析:request 是一個 HttpRequest 對象,response 是一個 TemplateResponse 對象(或相似對象),由 Django 視圖或中間件返回;

調用時間:若是 response 的實例有 render() 方法,process_template_response() 在視圖恰好執行完畢以後被調用,這代表他是一個 TemplateResponse 對象(或相似對象);

產生響應:這個方法必須返回一個實現了 render() 方法的 TemplateResponse 對象(或相似對象),它能夠修改給定的 response 對象,也能夠建立一個全新的 TemplateResponse 對象(或相似對象);

PS:在響應處理階段,中間件以相反的順序運行,包括 process_template_response

process_response

函數樣式:process_response(request, response)

參數解析:request 是一個 HttpRequest 對象,response 是一個 HttpResponse 對象,由 Django 視圖或中間件返回;

調用時間:process_request 在全部響應返回客戶端前被調用;

產生響應:這個方法必須返回一個 HttpRequest 對象,它能夠修改給定的 response 對象,也能夠建立一個全新的 HttpRequest 對象;

PS:process_response 老是被調用,這意味着你的 process_response 不能依賴 process_request

process_exception

函數樣式:process_exception(request, exception)

參數解析:request 是一個 HttpRequest 對象,exception 是一個被視圖拋出 Exception 對象;

調用時間:當一個視圖拋出異常,Django 會調用 process_exception 來處理;

產生響應:它應該返回一個 None 或一個 HttpResponse 對象,若是返回 NoneDjango 會繼續處理這個請求;若是它返回一個 HTTPResponse 對象,模板對象和 Response Middleware 會被直接返回給客戶端;不然,將啓動默認異常處理。;

URL Resolver

概述:

假設:中間件便利執行完 _request_middleware,_view_middleware 後都返回 None

Django 遍歷執行完 _request_middleware 後會獲得一個通過處理的 request 對象,此時 Django 將按順序進行對 url 進行正則匹配,若是匹配不成功,就會拋出異常;若是匹配成功,Django 會繼續循環執行 _view_middleware 並在執行後繼續執行剛剛匹配成功的 view

setting 中有一個 ROOT_URLCONF,它指向 urls.py 文件,根據這個文件能夠生產一個 urlconf,本質上,他就是 url 與視圖函數之間的映射表,而後經過 resolver 解析用戶的 url,找到第一個匹配的 view

細則:

URL Resolver流程圖

重要函數源碼位置:

_path: django/urls/conf.py
URLPattern: django/urls/resolvers.py
ResolverMatch: django/urls/resolvers.py
URLResolver: django/urls/resolvers.py
複製代碼

源碼比較長,就不放出來了,感興趣的話本身去看吧。

  1. 經過 urlpatterns 的配置執行 _path 函數;
  2. _path 函數進行判斷:若是是一個 list 或者 tuple,就用 URLResolver 處理,跳至 4;若是是一個正常的可調用的 view 函數,則用 URLPattern 處理,跳至;若是匹配失敗,拋出異常;
  3. URLPattern 初始化相應值後執行 resolve 方法:若是匹配成功,返回 ResolverMatch;若是匹配失敗,拋出異常;
  4. URLResolver 匹配 path 若是匹配成功,則繼續匹配它的 url_patterns,跳至 5;匹配失敗,拋出異常;
  5. 匹配 url_patterns:若爲 urlpattern 匹配成功,返回 ResolverMatch;若爲 URLResolver 遞歸調用 URLResolver 跳至 4;若匹配失敗,拋出異常;

能夠發現,整個過程的關鍵就是 ResolverMatchURLPatternURLResolver 三個類,其中: ResolverMatch 是匹配結果,包含匹配成功後須要的信息; URLPattern 是一個 url 映射信息的對象,包含了 url 映射對應的可調用對象等信息; URLResolver 是實現 url 路由,解析 url 的關鍵的地方,它的 url_patterns既能夠是URLPattern 也能夠是 URLResolver,正是由於這種設計, 實現了對 url 的層級解析。

總述

真實的請求響應過程確定是比我提到的這些還要複雜的多,可是個人能力實在有限,目前僅能理解到這個層面了,若是錯誤歡迎指正。

參考引用:

  1. 簡書:作Python Web開發你要理解:WSGI & uWSGI 做者:rainybowe
  2. 掘金:Django從請求到響應的過程 做者:__奇犽犽
  3. 現代魔法學院:Python 與 Django 篇-Django 架構流程分析
  4. 簡書:django源碼分析之url路由(URLResolver) 做者:2hanson
  5. Django 官方文檔
相關文章
相關標籤/搜索