标签:,, 发表于 Django开发,Python开发 分类. 发表评论

中间件的调用

如果你看了我写的前一篇讨论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里面没啥新鲜的东西, 主要做了三件事情:

  • 禁止某些User-Agent的访问(我瞬间很邪恶地想起可以禁止IE的访问…).
  • URL重写, 在URL后面添加/和在前面添加www, 这个是可以配置的.
  • ETag处理.

这个不细分析了, 有兴趣的童鞋可以自行围观代码(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更是很厚道地没有定义这个方法. 好吧, 那就这样结束吧~

2009-12-27 17:00