天天看點

了解Django中間件

什麼是中間件

中間件是用于修改Django 請求或響應對象的鈎子的。放置來自Django docs的中間件 的定義。

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.           

複制

何時使用中間件

如果要修改請求(即發送到視圖的HttpRequest對象),則可以使用中間件。或者,您可能想要修改從視圖傳回的HttpResponse對象。這些都可以通過使用中間件來實作。

您可能要在視圖執行之前執行操作。在這種情況下,您将使用中間件。

Django提供了一些預設的中間件。例如:AuthenticationMiddleware

很多時候,您會在視圖内部使用request.user。Django希望在執行任何視圖之前先設定使用者屬性

request

。Django采用中間件方法來完成此任務。是以Django提供了AuthenticationMiddleware,可以修改請求對象。

然後Django修改請求對象,如下所示:

from django.contrib import auth
from django.contrib.auth import load_backend
from django.contrib.auth.backends import RemoteUserBackend
from django.core.exceptions import ImproperlyConfigured
from django.utils.deprecation import MiddlewareMixin
from django.utils.functional import SimpleLazyObject


def get_user(request):
    if not hasattr(request, '_cached_user'):
        request._cached_user = auth.get_user(request)
    return request._cached_user


class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        assert hasattr(request, 'session'), (
            "The Django authentication middleware requires session middleware "
            "to be installed. Edit your MIDDLEWARE setting to insert "
            "'django.contrib.sessions.middleware.SessionMiddleware' before "
            "'django.contrib.auth.middleware.AuthenticationMiddleware'."
        )
        request.user = SimpleLazyObject(lambda: get_user(request))


class RemoteUserMiddleware(MiddlewareMixin):
    """
    Middleware for utilizing Web-server-provided authentication.
    If request.user is not authenticated, then this middleware attempts to
    authenticate the username passed in the ``REMOTE_USER`` request header.
    If authentication is successful, the user is automatically logged in to
    persist the user in the session.
    The header used is configurable and defaults to ``REMOTE_USER``.  Subclass
    this class and change the ``header`` attribute if you need to use a
    different header.
    """

    # Name of request header to grab username from.  This will be the key as
    # used in the request.META dictionary, i.e. the normalization of headers to
    # all uppercase and the addition of "HTTP_" prefix apply.
    header = "REMOTE_USER"
    force_logout_if_no_header = True

    def process_request(self, request):
        # AuthenticationMiddleware is required so that request.user exists.
        if not hasattr(request, 'user'):
            raise ImproperlyConfigured(
                "The Django remote user auth middleware requires the"
                " authentication middleware to be installed.  Edit your"
                " MIDDLEWARE setting to insert"
                " 'django.contrib.auth.middleware.AuthenticationMiddleware'"
                " before the RemoteUserMiddleware class.")
        try:
            username = request.META[self.header]
        except KeyError:
            # If specified header doesn't exist then remove any existing
            # authenticated remote-user, or return (leaving request.user set to
            # AnonymousUser by the AuthenticationMiddleware).
            if self.force_logout_if_no_header and request.user.is_authenticated:
                self._remove_invalid_user(request)
            return
        # If the user is already authenticated and that user is the user we are
        # getting passed in the headers, then the correct user is already
        # persisted in the session and we don't need to continue.
        if request.user.is_authenticated:
            if request.user.get_username() == self.clean_username(username, request):
                return
            else:
                # An authenticated user is associated with the request, but
                # it does not match the authorized user in the header.
                self._remove_invalid_user(request)

        # We are seeing this user for the first time in this session, attempt
        # to authenticate the user.
        user = auth.authenticate(request, remote_user=username)
        if user:
            # User is valid.  Set request.user and persist user in the session
            # by logging the user in.
            request.user = user
            auth.login(request, user)

    def clean_username(self, username, request):
        """
        Allow the backend to clean the username, if the backend defines a
        clean_username method.
        """
        backend_str = request.session[auth.BACKEND_SESSION_KEY]
        backend = auth.load_backend(backend_str)
        try:
            username = backend.clean_username(username)
        except AttributeError:  # Backend has no clean_username method.
            pass
        return username

    def _remove_invalid_user(self, request):
        """
        Remove the current authenticated user in the request which is invalid
        but only if the user is authenticated via the RemoteUserBackend.
        """
        try:
            stored_backend = load_backend(request.session.get(auth.BACKEND_SESSION_KEY, ''))
        except ImportError:
            # backend failed to load
            auth.logout(request)
        else:
            if isinstance(stored_backend, RemoteUserBackend):
                auth.logout(request)


class PersistentRemoteUserMiddleware(RemoteUserMiddleware):
    """
    Middleware for Web-server provided authentication on logon pages.
    Like RemoteUserMiddleware but keeps the user authenticated even if
    the header (``REMOTE_USER``) is not found in the request. Useful
    for setups when the external authentication via ``REMOTE_USER``
    is only expected to happen on some "logon" URL and the rest of
    the application wants to use Django's authentication mechanism.
    """
    force_logout_if_no_header = False           

複制

同樣,您可能有一個可以與不同時區的使用者一起使用的應用程式。您想在向使用者顯示任何頁面時使用使用者的時區。您想在所有視圖中通路使用者的時區。在這種情況下,在會話中添加它是有益的。是以,您可以添加這樣的中間件:

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中。是以,我們編寫的TimezoneMiddleware必須在Django的在元組設定.MIDDLEWARE_CLASSES中提供AuthenticationMiddleware之後進行。

在接下來的示例中,我們将對中間件的順序有更多的了解。

#使用中間件時要記住的事情

  • 中間件的順序很重要。
  • 中間件隻需要從類 object擴充即可。
  • 中間件可以自由實作某些方法,而不能實作其他方法。
  • 中間件可以實作process_request,但不能實作process_response和process_view。實際上,這是非常普遍的,許多的Django提供的中間件都可以做到這一點。
  • 中間件可以實作process_response,但不能實作process_request。

AuthenticationMiddleware僅實作process_request,不實作process_response。你可以在這裡檢查

GZipMiddleware僅實作process_response,而不實作process_request或process_view。你可以在這裡看到

#編寫一些中間件

確定您有一個帶有URL和視圖的Django的項目,并且能夠通路該視圖。由于我們将嘗試使用進行多種操作

request.user

,是以請確定為您正确設定了身份驗證,并

request.user

在此視圖中顯示正确的内容。

在任何應用程式中建立一個檔案middleware.py。

我有一個所謂的應用

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

複制

向任何網址提出請求。這應該在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'           

複制

發生這種情況是因為

user

尚未設定屬性

request

現在更改中間件的順序,刹車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',
)           

複制

向任何網址提出請求。這應該在

runserver

控制台上列印

Middleware executed
<username>           

複制

這表明

process_request

已在中間件上按設定中列出的順序執行了該指令.MIDDLEWARE_CLASSES

您可以進一步驗證。在您的middleware.py中添加另一個中間件類AnotherMiddleware(對象):

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

複制

現在嘗試任何網址,您的輸出将是:

Middleware executed
<username>           

複制

您會注意到兩件事:

  • 您的視圖将不再執行,并且無論您嘗試使用哪個網址,都将看到“一些響應”。
  • AnotherMiddleware.process_request将不再執行。

是以,如果中間件的process_request()傳回HttpResponse對象,則繞過任何後續中間件的process_request。同時檢視執行被繞過。

注釋“ return HttpResponse(“一些響應”)”,是以兩個中間件的process_request繼續執行。

#使用process_response

将方法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           

複制

嘗試一些網址。輸出将是

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按照在MIDDLEWARE_CLASSES中自上而下定義的順序應用中間件的process_view()。這是process_request()遵循的順序。

同樣,如果任何process_view()傳回HttpResponse對象,則随後的process_view()調用将被忽略和不執行。