【Django】Django架構流程分析

Refer to: www.threemeal.com/blog/12/

中間件

中間件是一個鉤子框架,它們可以介入Django的請求和響應處理過程。它是一個輕量級、底層的“插件”系統,用于在全局修改Django的輸入和輸出。
每個中間件組件負責完成某個特定的功能。例如,Django包含的一個中間件組件AuthenticationMiddleware,它使用會話將用戶和請求關聯起來。

編寫自己的中間件

中間件工廠是可調用的,它接收一個可調用的get_response作為參數,返回一個中間件。一個中間件是可調用的,它接收request,返回response,就像view。
一個中間件可以是一個函數,像下面這樣:

  def simple_middleware(get_response):
      #One-time configuration and initialization.
      def middleware(request):
          #Code to be executed for each request before the view(and later middleware) are called.
          response = get_response(request)
          #Code to be executed for each request/response after the view is called.
          return response
      return middlware

或者可以寫一個類,它的實例是可調用的,就像:

  class SimpleMiddleware(object):
        def __init__(self, get_response):
            self.get_response = get_response
            #One-time configuration and initialization.
        def __call__(self, request):
            #Code to be executed for each request before the view(and later middleware) are called.
            response = self.get_response(request)
            #Code to be executed for each request/response after the view is called.
            return response

這個調用的get_response是由Django提供的,可能是真正的視圖(如果是最后一個中間件)或者是下一個中間件提供的。當前中間件無需關注它是什么只需要知道它代表了下一步將要發生什么。

中間件可以存在于你Python路徑的任何地方。
__init__(get_response)
中間件工廠必須接收一個get_response參數。你可以初始化中間件的一些全局狀態。時刻謹記一些警告:

  1. Django通過唯一的get_response參數,初始化你的中間,因此,你不能定義__init__()一些其他的參數;
  2. 不像__call__()方法會在每次請求的時候調用一次。__init__()方法只在服務器啟動的時候調用一次。

標記中間件不被使用

有時候在運行時決定一個中間件是否使用是很有用的。在這種情況下,你的中間件中的__init__方法可以拋出一個django.core.exceptions.MiddlewareNotUsed異常。Django會從中間件處理過程中移除這個中間件,并且當DEBUGTrue的時候在django.request記錄器中記錄調試信息。

激活中間件

要激活一個中間件組件,需要把它添加到Django配置文件中的MIDDLEWARE_CLASSES列表。
MIDDLEWARE_CLASSES中,每一個中間件組件用字符串的方式描述:一個完整的中間件工廠類或者函數所在的Python路徑。例如,使用django-amdin startproject創建工程的時候生成的默認值:

  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',
]

Django的程序中,中間件不是必需的---只要你喜歡, MIDDLWWARE_CLASSES可以為空---但是強烈推薦你至少使用CommonMiddleware。MIDDLEWARE_CLASSES中的順序非常重要,因為一個中間件可能依賴于另外一個。例如,AuthenticationMiddleware在會話(session)中儲存已認證的用戶。所以它必須在SessionMiddleware之后運行。

中間件的順序和層次

在請求階段,調用視圖之前,Django會按照MIDDLEWARE_CLASSES中定義的順序自頂向下應用中間件。會用到兩個鉤子:

  • process_request();
  • process_view();

在響應階段,調用視圖之后,中間件會按照相反的順序應用,自底向上。會用到三個鉤子:

  • process_exception();
  • process_template_response();
  • process_response()
image.png

你可以把它想象成一顆洋蔥:每個中間件都是包裹視圖的一層“皮”,而視圖就是洋蔥心。如果request通過洋蔥的各層(每層通過調用get_response傳遞request到下一層),傳向中心的視圖,response會原路返回穿過各層。
如果某一次決定短路直接返回response(不再調用get_response),余下的各層和視圖將不會見到任何request和response。

Django的運行方式

運行Django項目的方法很多,一種是在開發和調試中經常用到的runserver方法,使用Django自己的Web Server。另外一種就是使用fastcgi, uWSGI等協議運行Django項目,這里以uWSGI為例。

1.runserver方法

runserver方法是調用Django時經常用到的運行方式,主要用在測試和開發中使用,使用方法如下:

    Usage: manage.py runserver [options: port number or ipaddr]
    #python manage.py runserver #default port is 8000
    #python manage.py runserver 8080
    #python manage.py runserver 127.0.0.1:9000

看一下manage.py的源代碼,你會發現上面的命令其實是通過Djangoexecute_from_command_line方法執行了內部實現的runserver命令,那么現在看一下runserver具體做了什么。
runserver命令主要做了兩件事情:

  1. 解析參數,并通過django.core.servers.basehttp.get_internal_wsgi_application方法獲取wsgi handler;

  2. 根據ip addressport生成一個WSGIServer對象,接受用戶請求,get_internal_wsgi_application的源碼如下:

     def get_internal_wsgi_application():
       """
       Loads and returns 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 servers (runserver, runfcgi); 
       external WSGI servers should just be configured to point to the 
       correct application object directly.
       If settings.WSGI_APPLICATION is not set (is ``None``), we just   
       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()
       return import_by_path(
             app_path,
             error_prefix="WSGI application '%s' could not be loaded; " % app_path
       )
    

通過上面的代碼我們可以知道,Django會先根據settings中的WSGI_APPLICATION來獲取handler;在創建project的時候,Django會默認創建一個wsgi.py文件,而settings中的WSGI_APPLICATION配置也會默認指向這個文件。看一下這個wsgi.py文件,其實它也和上面的邏輯一樣,最終調用````get_wsgi_application```實現。

2.uWSGI方法

uWSGI+Nginx的方法是現在最常見的在生產環境中運行Django的方法,要了解這種方法,首先要了解一下WSGIuWSGI協議。

  • WSGI:全稱Web Server Gateway Interface,或者Python Web Server Gateway Interface,是為Python語言定義的Web服務器和Web應用程序或框架之間的一種簡單而通用的接口,基于現存的CGI標準而設計的。WSGI其實就是一個網關(Gateway),其作用就是在協議之間進行轉換。
  • uWSGI:是一個Web服務器,它實現了WSGI協議、uwsgi、http等協議。注意uWSGI是一種通信協議,而uWSGI是實現uwsgi協議和WSGI協議Web服務器。uWSGI具有超快的性能、低內存占用和多app管理等優點。

HTTP請求處理流程

Django和其他Web框架一樣,HTTP的處理流程基本類似:接收request,返回response內容。Django的具體處理流程大致如下圖所示:

  1. 加載project settings
    在通過django-admin.py創建project的時候,Django會自動生成默認的settings文件和manage.py等文件,在創建WSGIServer之前會執行下面的引用:
    from django.conf import settings
    上面引用在執行時,會讀取os.environ中的DJANGO_SETTINGS_MODULE配置,加載項目配置文件,生成settings對象。所以,在manage.py文件中你可以看到,在獲取WSGIServer之前,會先將projectsettings路徑加到os路徑中。
  2. 創建WSGIServer
    不管是使用runserver還是uWSGI運行Django項目,在啟動時都會調用django.core.servers.basehttp中的run()方法,創建一個django.core.servers.basehttp.WSGIServer類的實例,之后調用其serve_forever()方法啟動HTTP服務。run方法的源碼如下:
    def run(addr, port, wsgi_handler, ipv6=False, threading=False):
    server_address = (addr, port)
    if threading:
    httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
    else:
    httpd_cls = WSGIServer
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    # Sets the callable application as the WSGI application that will receive requests
    httpd.set_app(wsgi_handler)
    httpd.serve_forever()
    如上,我們可以看到:在創建WSGIServer實例的時候會指定HTTP請求的Handler,上述代碼使用WSGIRequestHandler。當用戶的HTTP請求到達服務器時,WSGIServer會創建WSGIRequestHandler實例,使用其handler方法來處理HTTP請求(其實最終是調用wsgiref.handlers.BaseHandler中的run方法處理)。WSGIServer通過set_app方法設置一個可調用(callable)的對象作為application,上面提到的handler方法最終會調用設置的application處理request,并返回response。
    其中,WSGIServer繼承自wsgiref.simple_server.WSGIServer,而WSGIRequestHandler繼承自wsgiref.simple_server.WSGIRequestHandler,wsgirefPython標準庫給出的WSGI的參考實現。
  3. 處理Request
    第二步中說到的application,在Django中一般是django.core.handlers.wsgi.WSGIHandler對象,WSGIHandler繼承自django.core.handlers.base.BaseHandler,這個是Django處理request的核心邏輯,它會創建一個WSGIRequest實例,而WSGIRequest是從http.HttpRequest繼承而來。
  4. 返回Response
    上面提到的BaseHandler中有個get_response方法,該方法會先加載Django項目的ROOT_URLCONF,然后根據url規則找到對應的view方法(類),view邏輯會根據request實例生成并返回具體的response。

Django返回結果之后,第二步中提到wsgiref.handlers.BaseHandler.run方法會調用finish_response結束請求,并將內容返回給用戶。

Paste_Image.png

Paste_Image.png

上面的兩張流程圖可以大致描述Django處理request的流程,按照流程圖2的標注,可以分為以下幾個步驟:

1.用戶通過瀏覽器請求一個頁面
2.請求到達Request Middlewares,中間件對request做一些預處理或者直接response請求
3.URLConf通過urls.py文件和請求的URL找到相應的View
4.View Middlewares被訪問,它同樣可以對request做一些處理或者直接返回response
5.調用View中的函數
6.View中的方法可以選擇性的通過Models訪問底層的數據
7.所有的Model-to-DB的交互都是通過manager完成的
8.如果需要,Views可以使用一個特殊的Context
9.Context被傳給Template用來生成頁面
a.Template使用Filters和Tags去渲染輸出
b.輸出被返回到View
c.HTTPResponse被發送到Response Middlewares
d.任何Response Middlewares都可以豐富response或者返回一個完全不同的response
e.Response返回到瀏覽器,呈現給用戶

上述流程中最主要的幾個部分分別是:Middleware(中間件,包括request, view, exception, response),URLConf(url映射關系),Template(模板系統),下面一一介紹一下。

Middleware(中間件)

Middleware并不是Django所獨有的東西,在其他的Web框架中也有這種概念。在Django中,Middleware可以滲入處理流程的四個階段:request,view,response和exception,相應的,在每個Middleware類中都有rocess_request,process_view, process_response 和 process_exception這四個方法。你可以定義其中任意一個活多個方法,這取決于你希望該Middleware作用于哪個處理階段。每個方法都可以直接返回response對象。

Middleware是在Django BaseHandler的load_middleware方法執行時加載的,加載之后會建立四個列表作為處理器的實例變量:
_request_middleware:process_request方法的列表
_view_middleware:process_view方法的列表
_response_middleware:process_response方法的列表
_exception_middleware:process_exception方法的列表
Django項目的安裝并不強制要求任何中間件,如果你愿意,MIDDLEWARE_CLASSES可以為空。中間件出現的順序非常重要:在request和view的處理階段,Django按照MIDDLEWARE_CLASSES中出現的順序來應用中間件,而在response和exception異常處理階段,Django則按逆序來調用它們。也就是說,Django將MIDDLEWARE_CLASSES視為view函數外層的順序包裝子:在request階段按順序從上到下穿過,而在response則反過來。以下兩張圖可以更好地幫助你理解:


Paste_Image.png

Paste_Image.png
URLConf(URL映射)

如果處理request的中間件都沒有直接返回response,那么Django會去解析用戶請求的URL。URLconf就是Django所支撐網站的目錄。它的本質是URL模式以及要為該URL模式調用的視圖函數之間的映射表。通過這種方式可以告訴Django,對于這個URL調用這段代碼,對于那個URL調用那段代碼。具體的,在Django項目的配置文件中有ROOT_URLCONF常量,這個常量加上根目錄”/”,作為參數來創建django.core.urlresolvers.RegexURLResolver的實例,然后通過它的resolve方法解析用戶請求的URL,找到第一個匹配的view。

Template(模板)

大部分web框架都有自己的Template(模板)系統,Django也是。但是,Django模板不同于Mako模板和jinja2模板,在Django模板不能直接寫Python代碼,只能通過額外的定義filter和template tag實現。

Django處理Request的詳細流程

上述的第三步和第四步邏輯只是大致說了一下處理過程,Django在處理request的時候其實做了很多事情,下面我們詳細的過一下。首先給大家分享兩個網上看到的Django流程圖:

Django框架總覽

如下圖所示Django的架構總覽圖,整體上把握Django的組成。


image.png

核心在于middleware(中間件),Django所有的請求/返回都由中間件來完成。
中間件,就是處理HTTP的request和reponse的,類似插件,比如有request中間件、View中間件、response中間件、exception中間件等,Middleware都需要在"project/settings.py"中MIDDLEWARE_CLASS的定義。大致的程序流程圖如下所示:

image.png

首先,Middleware都需要在"project/settings.py"中的MIDDLEWARE_CLASSES定義,一個HTTP請求,將被這里指定的中間件從頭到尾處理一遍,暫且稱這些需要挨個處理的中間件為處理鏈,如果鏈中某個處理器處理后沒有返回response,就把請求傳遞給下一個處理器;如果鏈中某個處理器返回了response,直接跳出處理鏈由response中間件處理后返回給客戶端,可以稱之為短路處理。

了解Django Middleware的幾個關鍵方法

Django處理一個Request的過程是首先通過中間件,然后再通過默認的URL方式進行的。我們可以在Middleware這個地方把所有Request攔截住,用我們自己的方式完成處理以后直接返回Response。因此了解中間件的構成是非常必要的。

Initializer: __init__(self)

出于性能的考慮,每個已啟用的中間件在每個服務器進程中只初始化一次。也就是說__init__()僅在服務進程啟動的時候調用,而在針對單個request處理時并不執行。
對一個middleware而言,定義__init__()方法通常是為了檢查自身的必要性。如果__init__()拋出異常django.core.exception.MiddlewareNotUsed,則Django將從middleware棧中移出該middleware。
在中間件中定義__init__()方法時,除了標準的self參數之外,不應該定義任何其它參數。

Request預處理函數:process_request(self, request)

這個方法的調用時機在Django接收到request之后,但仍未解析URL以確定應當運行的view之前。Django向它傳入相應的HttpRequest對象,以便在方法中修改。
process_request()應當返回None或HttpResponse對象。
如果返回None,Django將繼續處理這個request,執行后續的中間件,然后調用相應的view。
如果返回HttpResponse對象,Django將不再執行任何其它的中間件(無視其種類)以及相應的view。Django將立即返回該HttpResponse。

View預處理函數:process_view(self,request,callback,callback_args,callback_kwargs)

這個方法的調用時機在Django執行完request預處理函數并確定待執行的view之后,但在view函數實際執行之前。

  • request:HttpRequest對象。
  • callback:Django將調用的處理request的python函數。這是實際的函數對象本身,而不是字符串表述的函數名。
  • args:將傳入view的位置參數列表,但不包括request參數(它通常是傳入view的第一個參數)。
  • kwargs:將傳入view的關鍵字參數字典。
    如同process_request(),process_view()應當返回None或者HttpResponse對象。如果返回None,Django將繼續處理這個request,執行后續的中間件,然后調用相應的view。
    如果返回HttpResponse對象,Django將不再執行任何其它的中間件(不論種類)以及相應的view,Django將立即返回。
Response后處理函數:process_response(self, request, response)

這個方法的調用時機在Django執行view函數并生成response之后。
該處理器能修改response的內容:一個常見的用途是內容壓縮,如gzip所請求的HTML頁面。
這個方法的參數相當直觀: request是request對象,而response則是從view中返回的response對象。
process_response()必須返回HttpResponse對象。這個response對象可以是傳入函數的那個原始對象(通常已被修改),也可以是全新生成的。

Exception后處理函數:process_exception(self, request, exception)

這個方法只有在request處理過程中出了問題并且view函數拋出了一個未捕獲的異常時才會被調用。這個鉤子可以用來發送錯誤通知,將現成相關信息輸出到日志文件,或者甚至嘗試從錯誤中自動恢復。
這個函數的參數除了一貫的request對象之外,還包括view函數拋出的實際的異常對象exception。
process_exception()應當返回None或者HttpResponse對象。
如果返回None,Django將用框架內置的異常處理機制繼續處理相應request。
如果返回HttpResponse對象,Django將使用該response對象,而短路框架內置的異常處理機制。

Django HTTP請求的處理流程

Django處理一個Request的過程是首先通過中間件,然后再通過默認的URL方式進行的。我們可以在Middleware這個地方把所有Request攔截住,用我們自己的方式完成處理以后直接返回Response。

  1. 加載配置
    Django的配置都在"Project/settings.py"中定義,可以是Django的配置,也可以是自定義的配置,并且都通過django.conf.settings訪問,非常方便。
  2. 啟動
    最核心的動作是通過django.core.management.commands.runfcgiCommand來啟動,它運行django.core.servers.fastcgi中的runfastcgi,runfastcgi使用了flup的WSGIServer來啟動fastcgi。而WSGIServer中攜帶了django.core.servers.fastcgi的WSGIHandler類的一個實例,通過WSGIHandler來處理由Web服務器(比如Apache, Lighttpd等)傳過來的請求,此時才是真正進入Django的世界。
  3. 處理Request
    當有HTTP請求來時,WSGIHandler就開始工作了,它從BaseHandler繼承而來。WSGIHandler為每個請求創建一個WSGIRequest實例,而WSGIRequest是從http.HttpRequest繼承而來。接下來就開始創建Response了。
  4. 創建Response
    BaseHandler的get_response方法就是根據request創建response,而具體生成response的動作就是執行urls.py中對應的view函數了,這也是Django可以處理“友好URL”的關鍵步驟,每個這樣的函數都要返回一個Response實例。此時一般的做法是通過loader加載template并生成頁面內容,其中重要的就是通過ORM技術從數據庫中取出數據,并渲染到template中,從而生成具體的頁面了。
  5. 處理Response
    Django返回Response給flup,flup就取出Response的內容返回給Web服務器,由后者返回給瀏覽器。
    總之,Django在fastcgit中主要做了兩件事:處理Request和創建Response,而它們對應的核心就是"urls分析",“模板技術”和“ORM技術”。


    image.png

    如圖所示,一個HTTP請求,首先被轉化成一個HttpRequest對象,然后該對象被傳遞給Request中間件處理,如果該中間件返回了Response,則直接傳遞給Response中間件做收尾處理。否則的話Request中間將訪問URL配置,確定哪個view來處理,在確定了哪個view要執行,但是還沒有執行該view的時候,系統會把request傳遞給View中間件處理器進行處理,如果該中間件返回了Response,那么該Response直接被傳遞給Response中間件進行后續處理,否則將執行確定的View函數處理并返回Response,在這個過程中如果引發了異常并拋出,會被Exception中間件處理器進行處理。

請求處理機制其一:進入Django前的準備

一個Request到達了!

首先發生的是一些和Django有關(前期準備)的其他事情,分別是:

  • 如果是Apache/mod_python提供服務,requestmod_python創建的django.core.handlers.modpython.ModPythonHander實例傳遞給Django
  • 如果是其它服務器,則必須兼容WSGI,這樣,服務器將創建一個django.core.handlers.wsgi.WsgiHander實例。
    這兩個類都繼承自django.core.handlers.base.BaseHandler,它包含對任何類型的request來說都需要的公共代碼。
快準備處理器(Handler)

當上面其中一個處理器實例化后,緊接著發生了一系列的事情;

  1. 這個處理器(handler)導入你的Django配置文件;
  2. 這個處理器導入Django的自定義異常類;
  3. 這個處理器調用它自己的load_middleware方法,加載所有列在MIDDLEWARE_CLASSES中的middleware類并且內省它們。

最后一條有點復雜,我們仔細瞧瞧。
一個middlware類可以滲入處理過程中的四個階段:request, view, response和exception。要做到這一點,只需要定義指定的、恰當的方法:process_request,process_view,process_response和process_exceptions。middleware可以定義其中任何一個或所有的這些方法,這取決于你想要它提供什么樣的功能。

當處理器內省middleware時,它查找上述名字的方法,并建立四個列表作為處理器的實例變量:

  • _request_middleware是一個保存process_request方法的列表(在每一種情況下,它們是真正的方法,可以直接調用),這些方法來自于任一個定義了它們的middleware類。
  • _view_middlware是一個保存process_view方法的列表,這些方法來自于任一個定義了它們的middleware類。
  • _response_middleware是一個保存process_response方法的列表,這些方法來自于任一個定義了它們的middleware類。
  • _exception_middlware是一個保存process_exception方法的列表,這些方法來自于任一個定義了它們的middleware類。
HttpRequest準備好了就可以進入Django

現在處理器已經準備好真正開始處理了,因此它給調度程序發送一個信號request_started(Django內部的調度程序允許各種不同的組件聲明它們正在干什么,并可以寫一些代碼監聽特定的事件。關于這一點目前還沒有官方的文檔,但在wiki上有一些注釋),接下來它實例化一個django.http.HttpRequest的子類。
根據不同的處理器,可能是django.core.handlers.modpython.ModPythonRequest的一個實例,也可能是django.core.handlers.wsgi.WSGIRequest的一個實例。需要兩個不同的類是因為mod_pythonWSGI APIs以不同的格式傳入request信息,這個信息需要解析為Django能夠處理的一個單獨的標準格式。
一旦一個HttpRequest或者類似的東西存在了,處理器就調用它自己的get_response方法,傳入這個HttpRequest作為唯一的參數。這里就是幾乎所有真正的活動發生的地方。

請求處理機制其二:Django中間件的解析

Middleware開始工作了!

get_response做的第一件事就是遍歷處理器的_request_middleware實例變量并調用其中的每一個方法,傳入HttpRequest的實例作為參數。

  for middleware_method in self._request_middleware:
      response = middleware_method(request)
      if response:
         break

這些方法可以選擇短路剩下的處理并立即讓get_response返回,通過返回自身的一個值(如果他們這樣做,返回值必須是django.http.HttpResponse的一個實例)。如果其中之一這樣做了,我們會立即回到主處理器代碼,get_response不會等著看其它middleware類想要做什么,它直接返回,然后處理器進入response階段。
然而,更一般的情況是,這里應用的middleware方法簡單地做一些處理并決定是否增加,刪除或補充request的屬性。

URL resolver的解析

假設沒有一個作用于requestmiddleware直接返回response,處理器下一步會嘗試解析請求的URL。它在配置文件中尋找一個叫做ROOT_URLCONF的配置,用這個配置加上根/,作為參數來創建django.core.urlresolvers.RegexURLResolver的一個實例,然后調用它的resolve方法來解析請求的URL路徑。
URL resolver遵循一個相當簡單的模式。對于在URL配置文件中根據ROOT_URLCONF的配置產生的每一個在urlpatterns列表中的條目,它會檢查請求的URL路徑是否與這個條目的正則表達式相匹配,如果是的話,有兩種選擇:
1.如果這個條目有一個可以調用的include,resolver截取匹配的URL,轉到include指定的URL配置文件并開始遍歷其中urlpatterns列表中的每一個條目。根據你URL的深度和模塊性,這可能重復好幾次。
2.否則,resolver返回三個條目:

  • 匹配的條目指定的view function

  • 一個從URL得到的未命名匹配組(被用來作為view的位置參數);

  • 一個關鍵字參數字典,它由從URL得到的任意命名匹配組和從URLConf中得到的任意其它關鍵字參數組合而成。
    注意這一過程會在匹配到第一個指定了view的條目時停止,因此最好讓你的URL配置從復雜的正則過渡到簡單的正則,這樣能確保resolver不會首先匹配到簡單的那一個而返回錯誤的view function。
    如果沒有找到匹配的條目,resolvers會產生django.core.urlresolvers.Resolver404異常,它是django.http.Http404的子類。后面我們會知道它是如何處理的。

    #Apply view middleware
    for middleware_method in self._view_middleware:
        response = middleware_method(request, callback, callback_args, callback_kwargs)
        if response:
          break
    

一旦知道了所需的 view function和相關的參數,處理器就會查看它的_view_middleware列表,并調用其中的方法,傳入 HttpRequst,view function,針對這個view的位置參數列表和關鍵字參數字典。
還有,Middleware有可能介入這一階段并強迫處理器立即返回。

請求處理機制其三:view層與模板解析

進入View了!

如果處理過程這時候還在繼續的話,處理器會調用view function。Django中的Views不很嚴格因為它只需要滿足幾個條件:

  • 必須可以調用;
  • 必須接受django.http.HttpRequest的實例作為第一位置參數;
  • 必須能產生一個異?;蚍祷?code>django.http.HttpResponse的一個實例;
    一般來說,views會使用Djangodatabase API來創建、檢索、更新和刪除數據庫的某些東西,還會加載并渲染一個模板來呈現一些東西給最終用戶。
模板

Django的模板系統有兩個部分:一部分是給設計師使用的混入少量其它東西的HTML,另一部分是給程序員使用純```Python``。
從一個HTML作者的角度,Django的模板系統非常簡單,需要知道的僅有三個結構:

  • 變量引用。在模板中是這樣:{{foo}}。
  • 模板過濾。在上面的例子中使用過濾豎線,類似{{foo|bar}}。通常這用來格式化輸出(比如:運行Textile,格式化日期等等)。
  • 模板標簽。類似{%bar%}。這是模板的“邏輯”實現的地方,你可以{%if foo%},{%for bar in foo%},等等,if和for都是模板標簽。
    變量引用以一種非常簡單的方式工作。如果你只是要打印變量,只要{{foo}},模板系統就會輸出它。這里唯一的復雜情況是{{foo.bar}},這時模板系統按順序嘗試幾件事:
  1. 首先它嘗試一個字典的方式的查找,看看foo['bar']是否存在。如果存在,則它的值被輸出,這個過程也隨之結束。
  2. 如果字典查找失敗,模板系統嘗試屬性查找,看看foo.bar是否存在。同時它還檢查這個屬性是否可以被調用,如果可以,調用之。
  3. 如果屬性查找失敗,模板系統嘗試把它作為列表索引進行查找。
    如果所有這些都失敗了,模板系統輸出配置TEMPLATE_STRING_IF_INVALID的值,默認是空字符串。
    模板過濾就是簡單的Python functions, 它接受一個值和一個參數,返回一個新的值。比如,date過濾用一個Python datetime對象作為它的值,一個標準的strftime格式化字符串作為它的參數,返回對datetime對象應用了格式化字符串之后的結果。
    模板標簽用在事情有一點點復雜的地方,它是你了解 Django 的模板系統是如何真正工作的地方。
Django模板的結構

在內部,一個Django模板體現為一個'nodes'集合,它們都是從基本的django.template.Node類繼承而來。Nodes可以做各種處理,但有一個共同點:每一個Node必須有一個叫做render的方法,它接受的第二個參數(第一個參數,是Node實例)是django.template.Context的一個實例,這是一個類似于字典的對象,包含所有模板可以獲得的變量。Node 的 render 方法必須返回 一個字符串,但如果 Node 的工作不是輸出(比如,它是要通過增加,刪除或修 改傳入的 Context 實例變量中的變量來修改模板上下文),可以返回空字符串。
Django 包含許多 Node 的子類來提供有用的功能。比如,每個內置的模板標簽都 被一個 Node 的子類處理(比如,IfNode 實現了 if 標簽,ForNode 實現了 for 標簽,等等)。所有內置標簽可以在 django.template.defaulttags 找到。

image.png

實際上,上面介紹的所有模板結構都是某種形式的Nodes。變量查找由VariableNode處理,出于自然,過濾也應用在VariableNode上,標簽是各種類型的Nodes,純文本是一個TextNode。
一般來說,一個view渲染一個模板要經過下面的步驟,依次是:

  1. 加載需要渲染的模板。這是由django.template.loader.get_template完成的,它能利用這許多方法中的任意一個來定位需要的模板文件。get_template函數返回一個django.template.Template實例,其中包含經過解析的模板和用到的方法。
  2. 實例化一個Context用來渲染模板。如果用的是Context的子類django.template.RequestContext,那么附帶的上下文處理函數就會自動添加在view中沒有定義的變量。Context的構建器方法用一個鍵/值對的字典作為它唯一的參數,RequestContext則用HttpRequest的一個實例和一個字典。
  3. 調用Template實例的render方法,Context對象作為第一個位置參數。
    Template的render方法的返回值是一個字符串,它由Template中所有Nodes的render方法返回的值連接而成,調用順序為它們出現在Template中的順序。
關于Response,一點點!

一旦一個模板完成渲染,或者產生了其它某些合適的輸出,view就會負責產生一個django.http.HttpResponse實例,它的構建器接受兩個可選的參數:

  • 一個作為response主體的字符串(它應該是第一位置參數,或者是關鍵字參數content)。大部分時間,這將作為渲染一個模板的輸出,但不是必須這樣,在這里你可以傳入任何有效的Python字符串。
  • 作為reponse的Content-Type header的值(它應該是第二位置參數,或者是關鍵字參數mime_type)。如果沒有提供這個參數,Django將會使用配置中DEFAULT_MIME_TYPE的值和DEFAULT_CHARSET的值,如果你沒有在Django的全局配置文件中更改它們的話,分別是'text/html'和‘utf-8’
異常

如果view函數,后者其中的什么東西,發生了異常,那么get_response將遍歷它的_exception_middleware實例變量并調用那里的每個方法,傳入HttpResponse和這個exception作為參數。如果順利,這些方法中的一個會實例化一個HttpResponse并返回它。
這時候有可能還是沒有得到一個HttpResponse,這可能有幾個原因:

  • view可能沒有返回值;
  • view可能產生了異常但是沒有一個middleware能夠處理它;
  • 一個middleware方法視圖處理一個異常時自己又產生了一個新的異常。

這時候,get_response會回到自己的異常處理機制中,它們有幾個層次:

  1. 如果exceptionHttp404并且DEBUG設置為Trueget_response將執行view django.views.debug.technical_404_response,傳入HttpRequestexception作為參數。這個view會展示URL resolver試圖匹配的模式信息。
  2. 如果DEBUGFalse并且異常是Http404,get_response會調用URL resolverresolve_404方法。這個方法查看URL配置以判斷哪一個view被指定用來處理404錯誤。默認是django.views.defaults.page_not_found,但可以在URL配置中給handler404變量賦值來更改。
  3. 對于任何其它類型的異常,如果DEBUG設置為True,get_response將執行view.django.views.debug.technical_500_response,傳入HttpRequestexception作為參數。這個view提供了關于異常的詳細信息,包括traceback,每一個層次stack中的本地變量,HttpRequest對象的詳細描述和所有無效配置的列表。
  4. 如果 DEBUGFalse,get_response會調用 URL resolverresolve_500方法,它和resolve_404方法非常相似,這時默認的viewdjango.views.defaults.server_error,但可以在 URL 配置中給 handler500 變量賦值來更改。

此外,對于除了django.http.Http404Python內置的 SystemExit之外的任 何異常,處理器會給調度者發送信號got_request_exception,在返回之前,構建一個關于異常的描述,把它發送給列在 Django 配置文件的ADMINS 配置中的每一個人。
現在,無論 get_response 在哪一個層次上發生錯誤,它都會返回一個 HttpResponse 實例,因此我們回到處理器的主要部分。一旦它獲得一個 HttpResponse它做的第一件事就是遍歷它的````_response_middleware 實例變量并 應用那里的方法,傳入HttpRequest HttpResponse``` 作為參數。

    finally:
    # Reset URLconf for this thread on the way out for complete
    # isolation of request.urlconf
    urlresolvers.set_urlconf(None)

    try:
    # Apply response middleware, regardless of the response
    for middleware_method in self._response_middleware:
      response = middleware_method(request, response)
      response = self.apply_response_fixes(request, response)

一旦 middleware 完成了最后環節,處理器將給調度者發送 信號 request_finished,對與想在當前的 request中執行的任何東西來說,這是最后的調用。監聽這個信號的處理者會清空并釋放任何使用中的資源。比如,Djangorequest_finished監聽者會關閉所有數據庫連接。

這件事發生以后,處理器會構建一個合適的返回值送返給實例化它的任何東西 (現在,是一個恰當的 mod_python response 或者一個 WSGI兼容的 response,這取決于處理器)并返回。

這就是 Django 如何處理一個 request

Django中的request與resposne對象

關于request與response

前面幾個Sections介紹了關于Django請求(Request)處理的流程分析,我們也了解到,Django是圍繞著Request與Response進行處理,也就是無外乎‘求’與‘應’。
當請求一個頁面時,Django把請求的metadata數據包裝成一個HttpRequest對象,然后Django加載合適的view方法,把這個HttpRequest對象,作為第一個參數傳給view方法。任何view方法都應該返回一個HttpResponse對象。


Paste_Image.png
HttpRequest

HttpRequest對象表示來自某客戶端的一個單獨的HTTP請求。HttpRequest對象是Django自動創建的。
它的屬性有很多,可以參考DjangoBook,比較常用的有以下幾個:

  1. method請求方法,如:

     if request.method == 'POST':
     ...
     elif request.method == 'GET':
      ...
    
  2. 類字典對象GET、POST

  3. COOKIES(字典形式)

  4. user:
    一個django.contrib.auth.models.User對象表示當前登錄用戶,若當前用戶尚未登錄,user會設為django.contrib.auth.models.AnonymousUser的一個實例。
    可以將它們用is_authenticated()區分開:

     if request.user.is_authenticated():
      ...
     else:
      ...
    
  5. session(字典形式)

  6. request.META
    具體可以參考《request.META里包含了哪些數據?》。
    request.META是一個Python字典,包含了所有本次HTTP請求的Header信息,比如用戶IP地址和用戶Agent(通常是瀏覽器的名稱和版本號)。注意,Header信息的完整列表取決于用戶所發送的Header信息和服務器端設置的Header信息。這個字典中幾個常見的鍵值有:

  • HTTP_REFERRER: 進站前鏈接網頁,如果有的話;

  • HTTP_USER_AGENT:用戶瀏覽器的user-agent字符串,如果有的話,例如:Mozilla/5.0 (X11; U; Linux i686; fr-FR; rv:1.8.1.17) Gecko/20080829 Firefox/2.0.0.17" .

  • REMOTE_ADDR:客戶端IP,如"12.345.67.89"。(如果申請是經過代理服務器的話,那么它可能是以逗號分割的多個IP地址,如:"12.345.67.89,23.456.78.90" 。)

    def request_test(request):
    context={}
    try:
      http_referer=request.META['HTTP_REFERRER']
      http_user_agent=request.META['HTTP_USER_AGENT']
      remote_addr=request.META['REMOTE_ADDR']
      return HttpResponse('[http_user_agent]:%s,[remote_addr]=%s' %(http_user_agent,remote_addr))
    except Exception,e:
      return HttpResponse("Error:%s" %e)
    

注意:GET、POST屬性都是django.http.QueryDict的實例,在DjangoBook可具體了解。

HttpResponse

Request和Response對象起到了服務器與客戶端之間的信息傳遞作用。Request對象用于接收客戶端瀏覽器提交的數據,而Response對象的功能則是將服務器端的數據發送到客戶端瀏覽器。
比如在view層,一般都是以以下代碼結束一個def:

  return HttpResponse(html)
  return render_to_response('template.html',{'data':data})

對于HttpRequest對象來說,是由Django自動創建,但是,HttpResponse對象就必須我們自己創建。每個View方法必須返回一個HttpResponse對象。HttpResponse類在django.http.HttpResponse。

  1. 構造HttpResponse
    HttpResponse類存在于django.http.HttpResponse,以字符串的形式傳遞給頁面。一般地,你可以通過給HttpResponse的構造函數傳遞字符串表示的頁面內容來構造HttpResponse對象:

     >>> response = HttpResponse("Welcome to nowamagic.net.")
     >>> response = HttpResponse("Text only, please.", mimetype="text/plain")
    

但是如果想要增量添加內容,你可以把response當做filelike對象使用:

    >>> response = HttpResponse()
    >>> response.write("<p>Welcome to nowamagic.net.</p>")
    >>> response.write("<p>Here's another paragraph.</p>")

也可以給HttpResponse傳遞一個iterator作為參數,而不用傳遞硬編碼字符串。如果你使用這種技術,下面是需要注意的一些事項:

  • iterator應該返回字符串;
  • 如果HttpResponse使用iterator進行初始化,就不能把HttpResponse實例座位filelike對象使用。這樣做將會拋出異常。
    最后,再說明一下,HttpResponse實現了write()方法,可以在任何需要filelike對象的地方使用HttpResponse對象。
  1. 設置Headers
    你可以使用字典語法添加,刪除 headers:

     >>> response = HttpResponse() 
     >>> response['X-DJANGO'] = "It's the best."
     >>> del response['X-PHP']
     >>> response['X-DJANGO']
     "It's the best."
    
  2. HttpResponse子類

Paste_Image.png

當然,你也可以自己定義不包含在上表中的HttpResponse子類。

參考

  1. http://www.nowamagic.net/academy/detail/13281808
  2. http://python.jobbole.com/80836/
  3. http://python.usyiyi.cn/translate/django_182/topics/http/middleware.html
  4. https://my.oschina.net/tenking/blog/29439
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容