djangoソース分析:HTTP要求プロセス(wsgi)

48074 ワード

本環境python 3.5.2,django1.10.xシリーズpython manage.py runserverコマンドでローカルデバッグサーバを起動したら、djangoのリクエストリクエストリクエストに対する処理手順全体を分析します.
ここで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リクエストがサーバに到着すると、WSDGIServerはWSDGIRequestHandlerインスタンスを作成し、そのhandlerメソッドを使用してHTTPリクエストを処理します(最終的にはwsgiref.handlers.BaseHandlerのrunメソッド処理を呼び出します).WSGIServer経由set_appメソッドは、上記のhandlerメソッドが最終的に設定したアプリケーション処理requestを呼び出し、responseを返す呼び出し可能なオブジェクトをアプリケーションとして設定します.
ここで、WSDGIServerは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の処理
最初のステップで述べたアプリケーションは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ファイルにWSDI_が設定されていない場合APPLICATIONでは、djangoのデフォルトの内部wsgiメソッドが使用されます.get_wsgi_アプリケーション()メソッドは、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   >    ,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スタイルの例を挙げる
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でリクエストを処理して応答値を返す方法について説明します.