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

WSGIHandler

HTTP请求的处理是由wsgi或mod_python脚本处理的. 在我的机器上使用的WSGI, 这个脚本里面配置好Django运行环境后, 初始化了一个WSGI句柄的实例:

import django.core.handlers.wsgi
application = django.core.handlers.wsgi.WSGIHandler()

这个句柄将处理HTTP请求, 我们跟进去看这个WSGI句柄的初始化. WSGIHandler这个类没有__init__方法, 因此使用的是base.BaseHandler的初始化方法:

# django/core/handlers/base.py
class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
    response_fixes = [
        http.fix_location_header,
        http.conditional_content_removal,
        http.fix_IE_for_attach,
        http.fix_IE_for_vary,
    ]

    def __init__(self):
        self._request_middleware = self._view_middleware = self._response_middleware = self._exception_middleware = None

好吧, 这个里面貌似也没干啥… 我们继续看WSGIHandler的__call__方法. 这个方法会被调用上面的wsgi脚本中的语句调用.

根据WSGI的文档:

The Web framework/app is represented to the server as a Python callable. It can be
a class, an object, or a function. The arguments to __init__, __call__, or the function
must be as above: an environ object and a start_response callable.

参考看下WSGIHandler的__call__方法, 很符合这个要求, 也是两个外部参数.

# django/core/handlers/wsgi.py
class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest
    def __call__(self, environ, start_response):
        """ some code omitted here. """
        signals.request_started.send(sender=self.__class__)
        try:
            try:
                request = self.request_class(environ)
            except UnicodeDecodeError:
                response = http.HttpResponseBadRequest()
            else:
                response = self.get_response(request)

            """ some more code omitted. """
        finally:
            signals.request_finished.send(sender=self.__class__)

        try:
            status_text = STATUS_CODE_TEXT[response.status_code]
        except KeyError:
            status_text = 'UNKNOWN STATUS CODE'
        status = '%s %s' % (response.status_code, status_text)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append(('Set-Cookie', str(c.output(header=''))))
        start_response(status, response_headers)
        return response

这个方法一开始还做了些和中间件相关的事情, 这儿就省略了. 我们从信号这一段开始, 在处理完中间件相关的逻辑后, 此处放出一个request_started的信号, 我们可以利用这个信号来处理一些对每个HTTP请求都要处理的逻辑. 接下来我们开始处理请求. 这儿用了一个try-except-else语句块. 首先我们尝试将environ作为参数来生成一个WSGIRequest类, 如果这一步出了问题, 我们返回400, 否则我们用get_response(request)来获得一个HttpRequest对象. 接下来Django放出了一个request_finished信号, 然后返回这个HTTP请求. 我们继续看处理请求的这部分.

WSGIRequest

前面的代码中, 我们已经在WSGIHandler中将其request_class属性设为一个WSGIRequest的实例, 并用一个装有环境变量的字典environ初始化了一个WSGIRequest. 我们接下来看Django是怎么处理这个WSGIRequest的.

首先看这个类的初始化. 相关代码如下:

class WSGIRequest(http.HttpRequest):
    def __init__(self, environ):
        script_name = base.get_script_name(environ)
        path_info = force_unicode(environ.get('PATH_INFO', u'/'))
        if not path_info or path_info == script_name:
            """ some comments omitted here. """
            path_info = u'/'
        self.environ = environ
        self.path_info = path_info
        self.path = '%s%s' % (script_name, path_info)
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()
        self._post_parse_error = False

这段初始化基本上也是比较平淡的, 主要做了两件事, 一个是设置了request.META这个字典, 另外设置了request.method. 如果这一段成功了, 那么我们将用get_response来处理这个WSGIRequest.

get_response

这个函数的定义在django/core/handlers/base.py中, 是BaseHandler的一个方法, 代码如下:

class BaseHandler(object):
    # Changes that are always applied to a response (in this order).
    def get_response(self, request):
        "Returns an HttpResponse object for the given HttpRequest"
        """ Some code omitted here. """
        # Get urlconf from request object, if available.  Otherwise use default.
        urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF)
        resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
        try:
            callback, callback_args, callback_kwargs = resolver.resolve(
                    request.path_info)
            """ Some code omitted here. """
            # Actually, this line was in a try-except thing...   by xiaket.
            response = callback(request, *callback_args, **callback_kwargs)
            """ Some code omitted here. """
            return response
        except http.Http404, e:
            if settings.DEBUG:
                from django.views import debug
                return debug.technical_404_response(request, e)
            else:
                try:
                    callback, param_dict = resolver.resolve404()
                    return callback(request, **param_dict)
                except:
                    try:
                        return self.handle_uncaught_exception(request, resolver, sys.exc_info())
                    finally:
                        receivers = signals.got_request_exception.send(sender=self.__class__, request=request)
        """ Some code omitted here. """

原来的代码很长, 仔细分析, 除掉了一些我不太关心的异常处理后就变成了现在这个样子. 这个函数接受一个HttpRequest实例, 返回一个HttpRequest实例. 函数一开始就来匹配URL. 第7行这句很强大, 因为它提供了很多可能性. 例如, 你可以通过中间件来对于request中的IP做判断, 然后通过设置urlconf来决定使用什么样的URL匹配方式. 当然, 这个时候已经经过了中间件, request.user已经设置好了, 你也可以根据用户的不同来提供不同的urlconf. 从settings.ROOT_URLCONF中拿到了URL配置后, 我们尝试匹配URL, 然后在第11行拿第10行匹配得到的适当的view函数来处理这个URL. 到视图函数以后就没有太多好说了. 中间那层我们略过, 返回了response之后也没啥了, 到这儿来就交给WSGI了. Django处理HTTP请求的逻辑也就结束了.

挖个坑, 实际上Django有很多HTTP请求的细节是在中间件里面完成的, 这篇文章完全没有提及这一点, 我有时间再仔细分析下中间件的作用, 为强大的中间件们正名~

2009-12-25 16:07