Django中的中间件

2009-12-27 17:00

中间件的调用

如果你看了我写的前一篇讨论Django处理HTTP请求的文章, 我在文中基本没提到中间件. 好吧, 我故意遗漏了这些, 因为中间件相对比较独立. 而且每个中间件之间也相对比较独立. 顺口说句, 松耦合就这个好处...

言归正传, 中间件的调用是在WSGIHandler被调用的开头进行的:

# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
initLock = Lock()
request_class = WSGIRequest

def __call__(self, environ, start_response):
from django.conf import settings

# Set up middleware if needed. We couldn't do this earlier, because
# settings weren't available.
if self._request_middleware is None:
self.initLock.acquire()
# Check that middleware is still uninitialised.
if self._request_middleware is None:
self.load_middleware()
self.initLock.release()

显然, 关键的语句是倒数第二行里面的load_middleware方法, 我们继续跟进去看:

# django/core/handlers/base.py 
class BaseHandler(object):
def load_middleware(self):
""" some code omitted. """
self._view_middleware = []
self._response_middleware = []
self._exception_middleware = []

request_middleware = []
for middleware_path in settings.MIDDLEWARE_CLASSES:
""" Exception handling is removed from these code. """
dot = middleware_path.rindex('.')
mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
mod = import_module(mw_module)
mw_class = getattr(mod, mw_classname)
mw_instance = mw_class()

if hasattr(mw_instance, 'process_request'):
request_middleware.append(mw_instance.process_request)
if hasattr(mw_instance, 'process_view'):
self._view_middleware.append(mw_instance.process_view)
if hasattr(mw_instance, 'process_response'):
self._response_middleware.insert(0, mw_instance.process_response)
if hasattr(mw_instance, 'process_exception'):
self._exception_middleware.insert(0, mw_instance.process_exception)

# We only assign to this when initialization is complete as it is used
# as a flag for initialization being complete.
self._request_middleware = request_middleware

整个这段代码的核心意思是对于每个settings.MIDDLEWARE_CLASSES里指定的中间件, 我们探测里面有什么方法. 如果有process_request, process_view, process_response, process_exception这四个方法, 我们将其添加到BaseHandler的四个列表里去. 按照Django处理HTTP请求的顺序, 前两个列表都是在django.core.handlers.base.BaseHandler模块的get_response方法中应用的, 第三个process_response方法是在WSGIHandler的结尾处应用的, 最后一个方法是在视图函数处理HTTP请求出现异常时才被应用的.

用django-admin.py startproject创建的新项目里, 默认添加了下面这些中间件:

MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
)

csrf这篇文章就不管了, 我们接下来分析剩余三个中间件到底执行了哪些操作. 我们主要分析process_request和process_response这两个字典.

process_request

CommonMiddleware里面没啥新鲜的东西, 主要做了三件事情:

这个不细分析了, 有兴趣的童鞋可以自行围观代码(django/middleware/common.py), 我们接下来看SessionMiddleware. 这个中间件的process_request方法里面只有几行代码:

class SessionMiddleware(object):
def process_request(self, request):
engine = import_module(settings.SESSION_ENGINE)
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME, None)
request.session = engine.SessionStore(session_key)

这一段详细的理解可以参考Django文档中有关会话的部分. 这部分在我手头的项目中应用不多, 于是很不厚道地也不准备细看了.

AuthenticationMiddleware中的代码依然简洁:

# django/contrib.auth.middleware.py
class AuthenticationMiddleware(object):
def process_request(self, request):
assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
request.__class__.user = LazyUser()
return None

除掉判断会话中间件是否已被载入的那段, 关键的语句是第5行, 这句设置了request这个WSGIRequest实例的类属性user, 将其设置为LazyUser, 而这个东西的定义为:

class LazyUser(object):
def __get__(self, request, obj_type=None):
if not hasattr(request, '_cached_user'):
from django.contrib.auth import get_user
request._cached_user = get_user(request)
return request._cached_user

这段代码使用了描述器(descriptor), 和上面那段代码的作用和起来所实现的功能是, 如果用request.user来请求user这个对象, 实际上请求的是_cached_user这个对象. 如果这个对象为空, 那么用get_user方法来获得当前请求的user:

# django/contrib/auth/__init__.py
def get_user(request):
from django.contrib.auth.models import AnonymousUser
try:
user_id = request.session[SESSION_KEY]
backend_path = request.session[BACKEND_SESSION_KEY]
backend = load_backend(backend_path)
user = backend.get_user(user_id) or AnonymousUser()
except KeyError:
user = AnonymousUser()
return user

这个函数的逻辑是, 从会话信息里面查找到当前用户信息, 查不到的话就将当前用户设置为匿名用户(未注册用户). 至此, 这个纠结的AuthenticationMiddleware处理request的部分就告一段落了.

process_response

CommonMiddleware中的这个方法是在处理ETag; SessionMiddleware的这个方法只是用来处理会话信息的保存, 谢天谢地. 而AuthenticationMiddleware更是很厚道地没有定义这个方法. 好吧, 那就这样结束吧~