天天看點

django中的中間件機制和執行順序

這篇文章中我們将讨論下面内容:

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

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.           

如果你想修改請求,例如被傳送到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           

例如你有一個應用,它的使用者是不同時區的人們。你想讓他們在通路任何頁面的時候都能顯示正确的時區,想讓所有的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           

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"           

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

對任意的一個url發送請求, 下面的資訊将會列印在runserver的控制台。

Middleware executed           

修改 

BookMiddleware.process_request

 如下

class BookMiddleware(object):
    def process_request(self, request):
        print "Middleware executed"
        print request.user           

再次通路一個url,将會引起一個錯誤。

'WSGIRequest' object has no attribute 'user'           

這是因為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',
)           

通路一個url,runserver控制台列印如下

Middleware executed
<username>           

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

你可以進一步證明,middleware.py添加另外一個middleware

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

把它也加到 

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

現在的輸出是:

Middleware executed
<username>
Another middleware executed           

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

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

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

Middleware executed
<username>           

你會注意到下面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           

通路一些url,得到如下的輸出

Middleware executed
<username>
Another middleware executed
AnotherMiddleware process_response executed
BookMiddleware process_response executed           

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()

将會被省略,不會被執行。

小蟒蛇