django源码分析:HTTP请求过程(wsgi)

本文环境python3.5.2,django1.10.x系列
python manage.py runserver 命令启动本地调试服务器后,接着要分析一下django对请求request的整个处理过程,

在此感谢在django源码学习中 小屋子大侠 的帮助,
小屋子大侠 博文地址:https://blog.csdn.net/qq_33339479/article/category/6800733

一、创建WSGIServer

上一节讲到在使用runserver运行Django项目,在启动时会调用django.core.servers.basehttp中的run()方法,创建一个django.core.servers.basehttp.WSGIServer类的实例,之后调用其serve_forever()方法启动HTTP服务。run方法的源码如下:

def run(addr, port, wsgi_handler, ipv6=False, threading=False): 
  server_address = (addr, port)                                                         # 服务端监听的地址和端口
  if threading:                                                                         # 如果多线程运行
    httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})  # 创建WSGIServer服务器,生成一个
  else:
    httpd_cls = WSGIServer
  httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)                      # 实例化该类
  # Sets the callable application as the WSGI application that will receive requests
  httpd.set_app(wsgi_handler)                                                           # 设置客服务端的处理handler, 即application
  httpd.serve_forever()

如上,我们可以看到:在创建WSGIServer实例的时候会指定HTTP请求的Handler,上述代码使用WSGIRequestHandler。当用户的HTTP请求到达服务器时,WSGIServer会创建WSGIRequestHandler实例,使用其handler方法来处理HTTP请求(其实最终是调用wsgiref.handlers.BaseHandler中的run方法处理)。WSGIServer通过set_app方法设置一个可调用(callable)的对象作为application,上面提到的handler方法最终会调用设置的application处理request,并返回response。

其中,WSGIServer继承自wsgiref.simple_server.WSGIServer,而WSGIRequestHandler继承自wsgiref.simple_server.WSGIRequestHandler,wsgiref是Python标准库给出的WSGI的参考实现。

class WSGIServer(HTTPServer):                                                           # WSGIServer继承自HTTPServer,HTTPServer继承自TCPServer

    """BaseHTTPServer that implements the Python WSGI protocol"""

    application = None

    def server_bind(self):
        """Override server_bind to store the server name."""
        HTTPServer.server_bind(self)
        self.setup_environ()

    def setup_environ(self):                                                            # 设置env环境参数
        # Set up base environment
        env = self.base_environ = {}
        env['SERVER_NAME'] = self.server_name
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
        env['SERVER_PORT'] = str(self.server_port)
        env['REMOTE_HOST']=''
        env['CONTENT_LENGTH']=''
        env['SCRIPT_NAME'] = ''

    def get_app(self):                                                                  # 获得处理器handler
        return self.application

    def set_app(self,application):                                                      # 设置处理器handler
        self.application = application

二、处理Request

第一步中说到的application,即为django.core.management.commands.runserver中的get_handler()方法

def get_internal_wsgi_application():
    """
    Loads and returns the WSGI application as configured by the user in
    ``settings.WSGI_APPLICATION``. With the default ``startproject`` layout,
    this will be the ``application`` object in ``projectname/wsgi.py``.

    This function, and the ``WSGI_APPLICATION`` setting itself, are only useful
    for Django's internal server (runserver); external WSGI servers should just
    be configured to point to the correct application object directly.

    If settings.WSGI_APPLICATION is not set (is ``None``), we just return
    whatever ``django.core.wsgi.get_wsgi_application`` returns.
    """
    from django.conf import settings                                                   # 加载生成project时生成的setting文件
    app_path = getattr(settings, 'WSGI_APPLICATION')                                   # 检查settings配置文件中是否设置“WSGI_APPLICATION”
    if app_path is None:                                                               # 如果没有设置,就使用默认的application
        return get_wsgi_application()

    try:
        return import_string(app_path)                                                 # 如果有设置,就返回设置的application
    except ImportError as e:
        msg = (
            "WSGI application '%(app_path)s' could not be loaded; "
            "Error importing module: '%(exception)s'" % ({
                'app_path': app_path,
                'exception': e,
            })
        )
        six.reraise(ImproperlyConfigured, ImproperlyConfigured(msg),
                    sys.exc_info()[2])

当settings文件中未设置WSGI_APPLICATION,就会使用django默认的内部wsgi方法,其中,get_wsgi_application()方法就是调用此方法,该函数在django.core.wsgi.py文件中

def get_wsgi_application():
    """
    The public interface to Django's WSGI support. Should return a WSGI
    callable.

    Allows us to avoid making django.core.handlers.WSGIHandler public API, in
    case the internal WSGI implementation changes or moves in the future.
    """
    django.setup(set_prefix=False)                                                   # 加载django初始环境
    return WSGIHandler()                                                             # 返回WSGI处理器

此方法在加载django处理化环境后,返回一个WSGIHandler类,下面我们看看这个处理器中的代码

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest                                                 # 调用WSGIResquest对象

    def __init__(self, *args, **kwargs):
        super(WSGIHandler, self).__init__(*args, **kwargs)                      
        self.load_middleware()                                                  # 加载所有的中间件

    def __call__(self, environ, start_response):                                # 类实例化调用此方法
        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)    # 信号发送,向接受通知的注册者发送通知
        try:
            request = self.request_class(environ)                               # 实例化WSGIRequest类,获得request
        except UnicodeDecodeError:
            logger.warning(
                'Bad Request (UnicodeDecodeError)',
                exc_info=sys.exc_info(),
                extra={
                    'status_code': 400,
                }
            )
            response = http.HttpResponseBadRequest()
        else:
            response = self.get_response(request)                               # get_response方法处理request

        response._handler_class = self.__class__                                # response的_handler_class为当前处理类

        status = '%d %s' % (response.status_code, response.reason_phrase)       # 获取状态码和相应描述
        response_headers = [(str(k), str(v)) for k, v in response.items()]      # 获取返回头信息
        for c in response.cookies.values():                                     # 获取cookies
            response_headers.append((str('Set-Cookie'), str(c.output(header=''))))      # 将cookies加到返回头重
        start_response(force_str(status), response_headers)                     # 设置返回头信息                
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):      # 判断是否存在文件传输
            response = environ['wsgi.file_wrapper'](response.file_to_stream)                                # 通过wsgi的文件传输方法处理响应的文件传输        
        return response                                                         # 返回response

该类继承自base.BaseHandler类,其中load_middleware()和get_response(request) 方法均使用BaseHandler类中的方法
下面分析一下BaseHandler类中的代码

class BaseHandler(object):

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

    def load_middleware(self):
        """
        Populate middleware lists from settings.MIDDLEWARE (or the deprecated
        MIDDLEWARE_CLASSES).

        Must be called after the environment is fixed (see __call__ in subclasses).
        """
        self._request_middleware = []                                                # 列表承载各个middleware中相应的方法
        self._view_middleware = []
        self._template_response_middleware = []
        self._response_middleware = []
        self._exception_middleware = []

        if settings.MIDDLEWARE is None:                                              # 旧版使用settings.MIDDLEWARE_CLASSES,新版本全部使用settings.MMIDDLEWARE
            warnings.warn(                                                           # 此处主要是为了兼容旧版
                "Old-style middleware using settings.MIDDLEWARE_CLASSES is "
                "deprecated. Update your middleware and use settings.MIDDLEWARE "
                "instead.", RemovedInDjango20Warning
            )
            handler = convert_exception_to_response(self._legacy_get_response)
            for middleware_path in settings.MIDDLEWARE_CLASSES:
                mw_class = import_string(middleware_path)
                try:
                    mw_instance = mw_class()
                except MiddlewareNotUsed as exc:
                    if settings.DEBUG:
                        if six.text_type(exc):
                            logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
                        else:
                            logger.debug('MiddlewareNotUsed: %r', middleware_path)
                    continue

                if hasattr(mw_instance, 'process_request'):
                    self._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_template_response'):
                    self._template_response_middleware.insert(0, mw_instance.process_template_response)
                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)
        else:                                                                      # 新版处理方法
            handler = convert_exception_to_response(self._get_response)            # 将响应加异常处理,self._get_response方法为处理请求返回响应的最重要方法
            for middleware_path in reversed(settings.MIDDLEWARE):                  # 获取中间件路径
                middleware = import_string(middleware_path)                        # 获取中间件类
                try:
                    mw_instance = middleware(handler)                              # 初始化中间件类
                except MiddlewareNotUsed as exc:
                    if settings.DEBUG:
                        if six.text_type(exc):
                            logger.debug('MiddlewareNotUsed(%r): %s', middleware_path, exc)
                        else:
                            logger.debug('MiddlewareNotUsed: %r', middleware_path)
                    continue

                if mw_instance is None:
                    raise ImproperlyConfigured(
                        'Middleware factory %s returned None.' % middleware_path
                    )

                if hasattr(mw_instance, 'process_view'):                           # 判断中间件中是否定义各方法,有则取出放到各个列表中
                    self._view_middleware.insert(0, mw_instance.process_view)
                if hasattr(mw_instance, 'process_template_response'):
                    self._template_response_middleware.append(mw_instance.process_template_response)
                if hasattr(mw_instance, 'process_exception'):
                    self._exception_middleware.append(mw_instance.process_exception)

                handler = convert_exception_to_response(mw_instance)               # 同样,将初始化后的类包装异常处理

        # We only assign to this when initialization is complete as it is used
        # as a flag for initialization being complete.
        self._middleware_chain = handler                                           # 将最后一个中间件实例传给该属性

    def make_view_atomic(self, view):
        non_atomic_requests = getattr(view, '_non_atomic_requests', set())                    # 获取view是否有非事务属性
        for db in connections.all():                                                          # 遍历设置的数据库
            if db.settings_dict['ATOMIC_REQUESTS'] and db.alias not in non_atomic_requests:   # 如果数据库中有配置ATOMIC_REQUESTS属性为TRUE,每个请求一个事务并且该数据库不在非事务列表里
                view = transaction.atomic(using=db.alias)(view)                               # 处理view就用一个事务处理
        return view

    def get_exception_response(self, request, resolver, status_code, exception):
        return get_exception_response(request, resolver, status_code, exception, self.__class__)    # 返回异常响应

    def get_response(self, request):
        """Return an HttpResponse object for the given HttpRequest."""
        # Setup default url resolver for this thread
        set_urlconf(settings.ROOT_URLCONF)

        response = self._middleware_chain(request)                                             # 执行该handler的__call__方法,返回响应值

        # This block is only needed for legacy MIDDLEWARE_CLASSES; if
        # MIDDLEWARE is used, self._response_middleware will be empty.
        try:
            # Apply response middleware, regardless of the response
            for middleware_method in self._response_middleware:                                # 遍历中间件中的process_response方法,处理响应
                response = middleware_method(request, response)
                # Complain if the response middleware returned None (a common error).
                if response is None:
                    raise ValueError(
                        "%s.process_response didn't return an "
                        "HttpResponse object. It returned None instead."
                        % (middleware_method.__self__.__class__.__name__))
        except Exception:  # Any exception should be gathered and handled
            signals.got_request_exception.send(sender=self.__class__, request=request)
            response = self.handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())

        response._closable_objects.append(request)                                            # 

        # If the exception handler returns a TemplateResponse that has not
        # been rendered, force it to be rendered.
        if not getattr(response, 'is_rendered', True) and callable(getattr(response, 'render', None)):      # 查看响应中是否需要渲染,并可该方法可调用
            response = response.render()                                                                    # 返回响应

        if response.status_code == 404:
            logger.warning(
                'Not Found: %s', request.path,
                extra={'status_code': 404, 'request': request},
            )

        return response                                                                     # 返回响应

    def _get_response(self, request):
        """
        Resolve and call the view, then apply view, exception, and
        template_response middleware. This method is everything that happens
        inside the request/response middleware.
        """
        response = None

        if hasattr(request, 'urlconf'):
            urlconf = request.urlconf                                                      # 获取请求的urlconf,是否配置urlconf,如果没有,则默认使用django默认
            set_urlconf(urlconf)
            resolver = get_resolver(urlconf)                                               # 实际为初始化django.urls.resolver.py文件下的RegexURLResolver()类
        else:
            resolver = get_resolver()

        resolver_match = resolver.resolve(request.path_info)                               # 调用py文件下的RegexURLResolver.resolve()函数,根据路径获得处理函数
        callback, callback_args, callback_kwargs = resolver_match                          # 返回处理函数,参数
        request.resolver_match = resolver_match

        # Apply view middleware
        for middleware_method in self._view_middleware:                                    # 处理中间件中的‘process_view’函数
            response = middleware_method(request, callback, callback_args, callback_kwargs)     # 获取中间件处理后的响应值
            if response:
                break

        if response is None:
            wrapped_callback = self.make_view_atomic(callback)                             # 判断处理函数一个请求一个事务
            try:
                response = wrapped_callback(request, *callback_args, **callback_kwargs)    # 处理函数处理request请求,返回响应
            except Exception as e:
                response = self.process_exception_by_middleware(e, request)

        # Complain if the view returned None (a common error).
        if response is None:                                                               # 如果返回为空,则报错
            if isinstance(callback, types.FunctionType):    # FBV
                view_name = callback.__name__
            else:                                           # CBV
                view_name = callback.__class__.__name__ + '.__call__'

            raise ValueError(
                "The view %s.%s didn't return an HttpResponse object. It "
                "returned None instead." % (callback.__module__, view_name)
            )

        # If the response supports deferred rendering, apply template
        # response middleware and then render the response
        elif hasattr(response, 'render') and callable(response.render):                    # 判断响应中是否需要渲染并且可调用
            for middleware_method in self._template_response_middleware:                   # 如果有,则调用中间件中模版函数处理
                response = middleware_method(request, response)
                # Complain if the template response middleware returned None (a common error).
                if response is None:
                    raise ValueError(
                        "%s.process_template_response didn't return an "
                        "HttpResponse object. It returned None instead."
                        % (middleware_method.__self__.__class__.__name__)
                    )

            try:
                response = response.render()
            except Exception as e:
                response = self.process_exception_by_middleware(e, request)

        return response                                                                   # 返回响应值

    def process_exception_by_middleware(self, exception, request):
        """
        Pass the exception to the exception middleware. If no middleware
        return a response for this exception, raise it.
        """
        for middleware_method in self._exception_middleware:
            response = middleware_method(request, exception)
            if response:
                return response
        raise

    def handle_uncaught_exception(self, request, resolver, exc_info):
        """Allow subclasses to override uncaught exception handling."""
        return handle_uncaught_exception(request, resolver, exc_info)

    def _legacy_get_response(self, request):                                             # 旧版本中间件中使用的方法
        """
        Apply process_request() middleware and call the main _get_response(),
        if needed. Used only for legacy MIDDLEWARE_CLASSES.
        """
        response = None
        # Apply request middleware
        for middleware_method in self._request_middleware:                               # 遍历处理中间件中‘process_request’方法
            response = middleware_method(request)
            if response:
                break

        if response is None:
            response = self._get_response(request)                                       # 同样调用_get_response()方法
        return response

其中_get_response方法为处理请求,获得响应的最主要方法,下面讲一下此方法中的get_resolver()方法,其中主要是调用实际为初始化django.urls.resolver.py文件下的RegexURLResolver()类中的resolve()方法,下面分析一下resolve()方法

  def resolve(self, path):
        path = force_text(path)  # path may be a reverse_lazy object                     # 将路径强制转换成字符串
        tried = []                                                                       # 用于记录已解析对象(路径)
        match = self.regex.search(path)                                                  # 通过继承对象LocaleRegexProvider.regex方法来匹配路径,由于整个当前对象RegexURLResolver初始化时,传递的参数是'^/'
        if match:
            new_path = path[match.end():                                                 # 如果匹配到,则取后面匹配到的剩余的path地址
            for pattern in self.url_patterns:                                            # 遍历各个url_pattern
                try:
                    sub_match = pattern.resolve(new_path)                                # pattern就是<RegexURLResolver <RegexURLPattern list> >这个对象,pattern.resolve相当于RegexURLResolver.resolve
                except Resolver404 as e:                                                 # 如果匹配成功,就会返回django.urls.resolver.ResolverMatch的类
                    sub_tried = e.args[0].get('tried')
                    if sub_tried is not None:
                        tried.extend([pattern] + t for t in sub_tried)
                    else:
                        tried.append([pattern])
                else:
                    if sub_match:
                        # Merge captured arguments in match with submatch
                        sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
                        sub_match_dict.update(sub_match.kwargs)                          # 添加前面匹配的kwargs值

                        # If there are *any* named groups, ignore all non-named groups.
                        # Otherwise, pass all non-named arguments as positional arguments.
                        sub_match_args = sub_match.args
                        if not sub_match_dict: 
                            sub_match_args = match.groups() + sub_match.args

                        return ResolverMatch(                                            # 返回ResolverMath初始化
                            sub_match.func,
                            sub_match_args,
                            sub_match_dict,
                            sub_match.url_name,
                            [self.app_name] + sub_match.app_names,
                            [self.namespace] + sub_match.namespaces,
                        )
                    tried.append([pattern])
            raise Resolver404({'tried': tried, 'path': new_path})
        raise Resolver404({'path': path})

这里简化的讲了url是如何匹配获得处理函数以及url中的参数, 此时我们继续分析url中配置的view视图函数,举一个resrful风格的例子

扫描二维码关注公众号,回复: 2964318 查看本文章
from django.conf.urls import url    # 这里是重点

urlpatterns = [
    url(r'^detial/', view.DetailView.as_view(), name='detail'),
]

class DetailViewApiview):
    return Response({"detail""请求成功"})

下面主要就url函数功能,和as_view()函数进行分析

def url(regex, view, kwargs=None, name=None):                                             # url函数,其中regex是r'^detial/', view就是view.DetailView.as_view(), name是‘detail’
    if isinstance(view, (list, tuple)):                                                   # 判断view是否是列表或者元组,这是显然不是
        # For include(...) processing.
        urlconf_module, app_name, namespace = view
        return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
    elif callable(view):                                                                  # 判断view函数是否是可调用的
        return RegexURLPattern(regex, view, kwargs, name)                                 # 如果是,则初始化RegexURLPattern实例
    else:
        raise TypeError('view must be a callable or a list/tuple in the case of include().')

url函数主要url匹配,处理函数和name传给RegexURLPattern类,并初始化,然后通过该类进行请求url的匹配工作

下面分析一下as_view()方法,所有的处理函数继承自view函数,调用as_view()方法,然后调用内包函数view(),最后调用处理请求的dispath函数处理请求,获得返回值


class View(object):
    """
    Intentionally simple parent class for all views. Only implements
    dispatch-by-method and simple sanity checking.
    """

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']   # 定义请求方法

    def __init__(self, **kwargs):
        """
        Constructor. Called in the URLconf; can contain helpful extra
        keyword arguments, and other things.
        """
        # Go through keyword arguments, and either save their values to our
        # instance, or raise an error.
        for key, value in six.iteritems(kwargs):                                             # 初始化,将输入参数变为类属性
            setattr(self, key, value)

    @classonlymethod
    def as_view(cls, **initkwargs):
        """
        Main entry point for a request-response process.
        """
        for key in initkwargs:
            if key in cls.http_method_names:                                                 # 判断输入参数是否与请求方法重名
                raise TypeError("You tried to pass in the %s method name as a "
                                "keyword argument to %s(). Don't do that."
                                % (key, cls.__name__))
            if not hasattr(cls, key):                                                        # 判断输入参数是否已经存在
                raise TypeError("%s() received an invalid keyword %r. as_view "
                                "only accepts arguments that are already "
                                "attributes of the class." % (cls.__name__, key))

        def view(request, *args, **kwargs):
            self = cls(**initkwargs)
            if hasattr(self, 'get') and not hasattr(self, 'head'):                            # 判断是否有‘get’没有‘head’,则head请求等同于get请求
                self.head = self.get
            self.request = request                                                            # 将输入的参数转变为属性
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)                                    # 调用处理函数
        view.view_class = cls                                                                 # 为view函数赋值
        view.view_initkwargs = initkwargs

        # take name and docstring from class
        update_wrapper(view, cls, updated=())                                                 # 更新view中的参数值,将cls的属性赋给view

        # and possible attributes set by decorators
        # like csrf_exempt from dispatch
        update_wrapper(view, cls.dispatch, assigned=())                                       # 同上
        return view

    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        # defer to the error handler. Also defer to the error handler if the
        # request method isn't on the approved list.
        if request.method.lower() in self.http_method_names:                               # 取请求方法的小写,
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)  # 判断view类中是否存在此方法,如果存在,则取出此方法
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)                                           # 返回handler处理request的响应值

    def http_method_not_allowed(self, request, *args, **kwargs):                           # 请求方法不被允许,处理函数
        logger.warning(
            'Method Not Allowed (%s): %s', request.method, request.path,
            extra={'status_code': 405, 'request': request}
        )
        return http.HttpResponseNotAllowed(self._allowed_methods())

    def options(self, request, *args, **kwargs):
        """
        Handles responding to requests for the OPTIONS HTTP verb.
        """
        response = http.HttpResponse()
        response['Allow'] = ', '.join(self._allowed_methods())
        response['Content-Length'] = '0'
        return response

    def _allowed_methods(self):                                                            # 返回当前类允许的类方法
        return [m.upper() for m in self.http_method_names if hasattr(self, m)]

本章节主要讲解url请求时,如果进行匹配,并且如何通过wsgi处理请求并返回响应值

猜你喜欢

转载自blog.csdn.net/Victor_Monkey/article/details/82017795
今日推荐