1. 程式人生 > >[Django高階]理解django中的中介軟體機制和執行順序 [Django高階]理解django中的中介軟體機制和執行順序

[Django高階]理解django中的中介軟體機制和執行順序 [Django高階]理解django中的中介軟體機制和執行順序

[Django高階]理解django中的中介軟體機制和執行順序

原文來自 Understanding Django Middlewares, 這篇文章從整體上介紹了django中中介軟體定義,作用,和怎麼樣自己寫中介軟體 –orangleliu。

注:middleware 和中介軟體在下面文章中含義相同,不完全翻譯了

假設你已經閱讀了 Django官方文件middleware部分 . 下面會盡可能詳盡的介紹文件中提到的知識,但是還是希望你熟悉 middleware

基本的概念。

這篇文章中我們將討論下面內容:

  • 什麼是 middleware
  • 什麼時候使用 middleware
  • 我們寫 middleware 必須要記住的東西
  • 寫一些 middlewares 來理解中介軟體的工作過程和要點

什麼是 middleware

Middlewares 是修改 Django request 或者 response 物件的鉤子. 下面是Django 文件中的一段描述。

Middleware is a framework of hooks into Django’s request/response processing. It’s a
light, low-level “plugin” system for globally altering Django’s input or output.
  • 1

什麼時候使用 middleware

如果你想修改請求,例如被傳送到view中的HttpRequest物件。 或者你想修改view返回的HttpResponse物件,這些都可以通過中介軟體來實現。

可能你還想在view執行之前做一些操作,這種情況就可以用 middleware來實現。

Django 提供了一些預設的 middleware,例如:
AuthenticationMiddleware

大家可能頻繁在view使用request.user

吧。 Django想在每個view執行之前把user設定為request的屬性,於是就用了一箇中間件來實現這個目標。所以Django提供了可以修改request 物件的中介軟體 AuthenticationMiddleware

Django 這樣修改request物件的:

https://github.com/django/django/blob/master/django/contrib/auth/middleware.py#L22
  
  • 1

例如你有一個應用,它的使用者是不同時區的人們。你想讓他們在訪問任何頁面的時候都能顯示正確的時區,想讓所有的views中都能得到使用者自己的timezone資訊。 這種情況下可以用session來解決,所以你可以像下面新增一個 middleware:

class TimezoneMiddleware(object):
    def process_request(self, request):
        # Assuming user has a OneToOneField to a model called Profile
        # And Profile stores the timezone of the User.
        request.session['timezone'] = request.user.profile.timezone
  
  • 1
  • 2
  • 3
  • 4
  • 5

TimezoneMiddleware 是依賴於 request.user的,request.user 是通過AuthenticationMiddleware來設定的。 所以在
settings.MIDDLEWARE_CLASSES配置中,TimezoneMiddleware 一定要在 AuthenticationMiddleware 之後。

下面的例子可以得到關於中介軟體順序的更多體會。

使用middleware時應該記住的東西

  • middlewares 的順序非常重要
  • 一個middleware只需要繼承 object 類
  • 一個middleware可以實現一些方法並且不需要實現所有的方法
  • 一個middleware可以實現 process_request(方法) 但是不可以實現 process_response(方法) 和 process_view 方法。 這些都很常見,Django提供了很多middlewares可以做到。
  • 一個middleware可以實現 process_response 方法,但是不需要實現 process_request 方法

AuthenticationMiddleware 只實現了對請求的處理,並沒有處理響應. 參照文件

GZipMiddleware 只實現了對響應的處理,並沒有實現對請求和view的處理 參見文件

寫一些 middlewares

首先確認下你有一個Django專案,需要一個url和一個view,並且可以進入這個view。下面我們會對request.user做幾個測試,確認許可權設定好了,並可以在view中正確列印 request.user 的資訊。

在任意一個app中建立middleware.py檔案。

我有一個叫做books的app,所以檔案的位置是 books/middleware.py

class BookMiddleware(object):
    def process_request(self, request):
        print "Middleware executed"
  
  • 1
  • 2
  • 3

MIDDLEWARE_CLASSES 中新增這個中介軟體

MIDDLEWARE_CLASSES = (
    'books.middleware.BookMiddleware',
    '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',
)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

對任意的一個url傳送請求, 下面的資訊將會列印在runserver的控制檯。

Middleware executed
  
  • 1

修改 BookMiddleware.process_request 如下

class BookMiddleware(object):
    def process_request(self, request):
        print "Middleware executed"
        print request.user
  
  • 1
  • 2
  • 3
  • 4

再次訪問一個url,將會引起一個錯誤。

'WSGIRequest' object has no attribute 'user'
  
  • 1

這是因為request物件還沒有設定user屬性呢。

現在我們改變下 middlewares的順序,BookMiddleware 放在 AuthenticationMiddleware 之後。

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'books.middleware.BookMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

訪問一個url,runserver控制檯列印如下

Middleware executed
<username>
  
  • 1
  • 2

這說明middlewares處理request的順序跟 settings.MIDDLEWARE_CLASSES 中列出的順序是一致的。

你可以進一步證實,middleware.py新增另外一個middleware

class AnotherMiddleware(object):
    def process_request(self, request):
        print "Another middleware executed"
  
  • 1
  • 2
  • 3

把它也加到 MIDDLEWARE_CLASSES

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'books.middleware.BookMiddleware',
    'books.middleware.AnotherMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

現在的輸出是:

Middleware executed
<username>
Another middleware executed
  
  • 1
  • 2
  • 3

在process_request方法中返回HttpResponse,把BookMiddleware改成下面這樣:

class BookMiddleware(object):
    def process_request(self, request):
        print "Middleware executed"
        print request.user
        return HttpResponse("some response")
  
  • 1
  • 2
  • 3
  • 4
  • 5

嘗試下任何一個url,會得到如下輸出:

Middleware executed
<username>
  
  • 1
  • 2

你會注意到下面2個事情:

  • 不管你訪問哪個url,自己寫的view 處理方法都不執行了,只有 “some response”這樣一種響應。
  • AnotherMiddleware.process_request 不在被執行

所以如果 Middleware的process_request方法中返回了HttpResponse物件,那麼它之後的中介軟體將被略過, view中的處理方法也被略過。
所以在實際的專案中很少會這麼幹(不過也有些專案會,例如做代理)

註釋掉 "return HttpResponse("some response")",兩個 middleware 才能正常的處理請求。

使用 process_response

給這兩個middleware新增 process_response方法

class AnotherMiddleware(object):
    def process_request(self, request):
        print "Another middleware executed"

    def process_response(self, request, response):
        print "AnotherMiddleware process_response executed"
        return response

class BookMiddleware(object):
    def process_request(self, request):
        print "Middleware executed"
        print request.user
        return HttpResponse("some response")
        #self._start = time.time()

    def process_response(self, request, response):
        print "BookMiddleware process_response executed"
        return response
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

訪問一些url,得到如下的輸出

Middleware executed
<username>
Another middleware executed
AnotherMiddleware process_response executed
BookMiddleware process_response executed
  
  • 1
  • 2
  • 3
  • 4
  • 5

AnotherMiddleware.process_response()BookMiddleware.process_response() 之前執行 而 AnotherMiddleware.process_request()BookMiddleware.process_request()之後執行. 所以process_response() 執行的順序跟 process_request正好相反. process_response() 執行的順序是從最後一箇中間件執行,到倒數第二個,然後直到第一個中介軟體.

process_view

Django 按順序執行中介軟體 process_view() 的方法,從上到下。 類似process_request()方法執行的順序。

所以如果任何一個 process_view() 返回了HttpResponse物件,那麼在它後面process_view()將會被省略,不會被執行。

				<script>
					(function(){
						function setArticleH(btnReadmore,posi){
							var winH = $(window).height();
							var articleBox = $("div.article_content");
							var artH = articleBox.height();
							if(artH > winH*posi){
								articleBox.css({
									'height':winH*posi+'px',
									'overflow':'hidden'
								})
								btnReadmore.click(function(){
									if(typeof window.localStorage === "object" && typeof window.csdn.anonymousUserLimit === "object"){
										if(!window.csdn.anonymousUserLimit.judgment()){
											window.csdn.anonymousUserLimit.Jumplogin();
											return false;
										}else if(!currentUserName){
											window.csdn.anonymousUserLimit.updata();
										}
									}
									
									articleBox.removeAttr("style");
									$(this).parent().remove();
								})
							}else{
								btnReadmore.parent().remove();
							}
						}
						var btnReadmore = $("#btn-readmore");
						if(btnReadmore.length>0){
							if(currentUserName){
								setArticleH(btnReadmore,3);
							}else{
								setArticleH(btnReadmore,1.2);
							}
						}
					})()
				</script>
				</article>