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
參數。你可以初始化中間件的一些全局狀態。時刻謹記一些警告:
-
Django
通過唯一的get_response
參數,初始化你的中間,因此,你不能定義__init__()
一些其他的參數; - 不像
__call__()
方法會在每次請求的時候調用一次。__init__()
方法只在服務器啟動的時候調用一次。
標記中間件不被使用
有時候在運行時決定一個中間件是否使用是很有用的。在這種情況下,你的中間件中的__init__
方法可以拋出一個django.core.exceptions.MiddlewareNotUsed
異常。Django會從中間件處理過程中移除這個中間件,并且當DEBUG
為True
的時候在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()
你可以把它想象成一顆洋蔥:每個中間件都是包裹視圖的一層“皮”,而視圖就是洋蔥心。如果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
的源代碼,你會發現上面的命令其實是通過Django
的execute_from_command_line
方法執行了內部實現的runserver
命令,那么現在看一下runserver
具體做了什么。
runserver
命令主要做了兩件事情:
解析參數,并通過
django.core.servers.basehttp.get_internal_wsgi_application
方法獲取wsgi handler
;-
根據
ip address
和port
生成一個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
的方法,要了解這種方法,首先要了解一下WSGI
和uWSGI
協議。
-
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
的具體處理流程大致如下圖所示:
- 加載
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
之前,會先將project
的settings
路徑加到os
路徑中。 - 創建
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
,wsgiref
是Python
標準庫給出的WSGI
的參考實現。 - 處理
Request
第二步中說到的application
,在Django
中一般是django.core.handlers.wsgi.WSGIHandler
對象,WSGIHandler
繼承自django.core.handlers.base.BaseHandler
,這個是Django
處理request
的核心邏輯,它會創建一個WSGIRequest
實例,而WSGIRequest
是從http.HttpRequest
繼承而來。 - 返回
Response
上面提到的BaseHandler
中有個get_response
方法,該方法會先加載Django
項目的ROOT_URLCONF
,然后根據url
規則找到對應的view
方法(類),view
邏輯會根據request
實例生成并返回具體的response
。
在Django
返回結果之后,第二步中提到wsgiref.handlers.BaseHandler.run
方法會調用finish_response
結束請求,并將內容返回給用戶。
上面的兩張流程圖可以大致描述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則反過來。以下兩張圖可以更好地幫助你理解:
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的組成。
核心在于middleware(中間件),Django所有的請求/返回都由中間件來完成。
中間件,就是處理HTTP的request和reponse的,類似插件,比如有request中間件、View中間件、response中間件、exception中間件等,Middleware都需要在"project/settings.py"中MIDDLEWARE_CLASS的定義。大致的程序流程圖如下所示:
首先,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。
- 加載配置
Django的配置都在"Project/settings.py"中定義,可以是Django的配置,也可以是自定義的配置,并且都通過django.conf.settings訪問,非常方便。 - 啟動
最核心的動作是通過django.core.management.commands.runfcgi
的Command
來啟動,它運行django.core.servers.fastcgi
中的runfastcgi,runfastcgi使用了flup的WSGIServer來啟動fastcgi。而WSGIServer中攜帶了django.core.servers.fastcgi
的WSGIHandler類的一個實例,通過WSGIHandler來處理由Web服務器(比如Apache, Lighttpd等)傳過來的請求,此時才是真正進入Django的世界。 - 處理Request
當有HTTP請求來時,WSGIHandler就開始工作了,它從BaseHandler繼承而來。WSGIHandler為每個請求創建一個WSGIRequest實例,而WSGIRequest是從http.HttpRequest繼承而來。接下來就開始創建Response了。 - 創建Response
BaseHandler的get_response方法就是根據request創建response,而具體生成response的動作就是執行urls.py中對應的view函數了,這也是Django可以處理“友好URL”的關鍵步驟,每個這樣的函數都要返回一個Response實例。此時一般的做法是通過loader加載template并生成頁面內容,其中重要的就是通過ORM技術從數據庫中取出數據,并渲染到template中,從而生成具體的頁面了。 -
處理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
提供服務,request
由mod_python
創建的django.core.handlers.modpython.ModPythonHander
實例傳遞給Django
。 - 如果是其它服務器,則必須兼容
WSGI
,這樣,服務器將創建一個django.core.handlers.wsgi.WsgiHander
實例。
這兩個類都繼承自django.core.handlers.base.BaseHandler
,它包含對任何類型的request
來說都需要的公共代碼。
快準備處理器(Handler)
當上面其中一個處理器實例化后,緊接著發生了一系列的事情;
- 這個處理器(
handler
)導入你的Django
配置文件; - 這個處理器導入
Django
的自定義異常類; - 這個處理器調用它自己的
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_python
和WSGI 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的解析
假設沒有一個作用于request
的middleware
直接返回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
會使用Django
的database API
來創建、檢索、更新和刪除數據庫的某些東西,還會加載并渲染一個模板來呈現一些東西給最終用戶。
模板
Django
的模板系統有兩個部分:一部分是給設計師使用的混入少量其它東西的HTML
,另一部分是給程序員使用純```Python``。
從一個HTML作者的角度,Django的模板系統非常簡單,需要知道的僅有三個結構:
- 變量引用。在模板中是這樣:{{foo}}。
- 模板過濾。在上面的例子中使用過濾豎線,類似{{foo|bar}}。通常這用來格式化輸出(比如:運行Textile,格式化日期等等)。
- 模板標簽。類似{%bar%}。這是模板的“邏輯”實現的地方,你可以{%if foo%},{%for bar in foo%},等等,if和for都是模板標簽。
變量引用以一種非常簡單的方式工作。如果你只是要打印變量,只要{{foo}},模板系統就會輸出它。這里唯一的復雜情況是{{foo.bar}},這時模板系統按順序嘗試幾件事:
- 首先它嘗試一個字典的方式的查找,看看foo['bar']是否存在。如果存在,則它的值被輸出,這個過程也隨之結束。
- 如果字典查找失敗,模板系統嘗試屬性查找,看看foo.bar是否存在。同時它還檢查這個屬性是否可以被調用,如果可以,調用之。
- 如果屬性查找失敗,模板系統嘗試把它作為列表索引進行查找。
如果所有這些都失敗了,模板系統輸出配置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 找到。
實際上,上面介紹的所有模板結構都是某種形式的Nodes。變量查找由VariableNode處理,出于自然,過濾也應用在VariableNode上,標簽是各種類型的Nodes,純文本是一個TextNode。
一般來說,一個view渲染一個模板要經過下面的步驟,依次是:
- 加載需要渲染的模板。這是由django.template.loader.get_template完成的,它能利用這許多方法中的任意一個來定位需要的模板文件。get_template函數返回一個django.template.Template實例,其中包含經過解析的模板和用到的方法。
- 實例化一個Context用來渲染模板。如果用的是Context的子類django.template.RequestContext,那么附帶的上下文處理函數就會自動添加在view中沒有定義的變量。Context的構建器方法用一個鍵/值對的字典作為它唯一的參數,RequestContext則用HttpRequest的一個實例和一個字典。
- 調用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
會回到自己的異常處理機制中,它們有幾個層次:
- 如果
exception
是Http404
并且DEBUG
設置為True
,get_response
將執行view django.views.debug.technical_404_response
,傳入HttpRequest
和exception
作為參數。這個view
會展示URL resolver
試圖匹配的模式信息。 - 如果
DEBUG
是False
并且異常是Http404
,get_response
會調用URL resolver
的resolve_404
方法。這個方法查看URL
配置以判斷哪一個view
被指定用來處理404
錯誤。默認是django.views.defaults.page_not_found
,但可以在URL
配置中給handler404
變量賦值來更改。 - 對于任何其它類型的異常,如果
DEBUG
設置為True
,get_response
將執行view.django.views.debug.technical_500_response
,傳入HttpRequest
和exception
作為參數。這個view
提供了關于異常的詳細信息,包括traceback
,每一個層次stack
中的本地變量,HttpRequest
對象的詳細描述和所有無效配置的列表。 - 如果
DEBUG
是False
,get_response
會調用URL resolver
的resolve_500
方法,它和resolve_404
方法非常相似,這時默認的view
是django.views.defaults.server_error
,但可以在URL
配置中給handler500
變量賦值來更改。
此外,對于除了django.http.Http404
或 Python
內置的 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
中執行的任何東西來說,這是最后的調用。監聽這個信號的處理者會清空并釋放任何使用中的資源。比如,Django
的 request_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對象。
HttpRequest
HttpRequest對象表示來自某客戶端的一個單獨的HTTP請求。HttpRequest對象是Django自動創建的。
它的屬性有很多,可以參考DjangoBook,比較常用的有以下幾個:
-
method請求方法,如:
if request.method == 'POST': ... elif request.method == 'GET': ...
類字典對象GET、POST
COOKIES(字典形式)
-
user:
一個django.contrib.auth.models.User對象表示當前登錄用戶,若當前用戶尚未登錄,user會設為django.contrib.auth.models.AnonymousUser的一個實例。
可以將它們用is_authenticated()區分開:if request.user.is_authenticated(): ... else: ...
session(字典形式)
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。
-
構造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對象。
-
設置Headers
你可以使用字典語法添加,刪除 headers:>>> response = HttpResponse() >>> response['X-DJANGO'] = "It's the best." >>> del response['X-PHP'] >>> response['X-DJANGO'] "It's the best."
HttpResponse子類
當然,你也可以自己定義不包含在上表中的HttpResponse子類。