Djangoを読む

はじめに

前回はサーバが起動するまでを見てきました。それでは続いてリクエストが送られてきたときにどのようにして自分が書いたコードに到達するかを見ていくことにしましょう。

django/core/servers/basehttp.py

見るのは引き続き、basehttp.pyです。改めてrun関数を確認。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 
 
 
 
 
 
 
 
-
|
|
|
|
|
!
 
 
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
    server_address = (addr, port)
    if threading:
        httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
    else:
        httpd_cls = WSGIServer
    httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
    if threading:
        # ThreadingMixIn.daemon_threads indicates how threads will behave on an
        # abrupt shutdown; like quitting the server by the user or restarting
        # by the auto-reloader. True means the server will not wait for thread
        # termination before it quits. This will make auto-reloader faster
        # and will prevent the need to kill the server manually if a thread
        # isn't terminating correctly.
        httpd.daemon_threads = True
    httpd.set_app(wsgi_handler)
    httpd.serve_forever()

WSGIServer

WSGIServerは標準ライブラリに用意されているクラスでwsgi.simple_serverモジュールにあります。作成しているのはそれではなくbasehttp.pyで継承して少し機能拡張したものですが同じとみなして問題ないでしょう。

WSGIServerはhttp.serverのHTTPServerを継承し、HTTPServerはsocketserverのTCPServerを継承し、TCPServerはBaseServerを継承し、とここまでたどるとserve_foreverメソッドが出てきます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
-
|
|
|
|
!
 
 
-
|
|
|
!
 
 
 
 
 
 
 
 
 
 
 
def serve_forever(self, poll_interval=0.5):
    """Handle one request at a time until shutdown.
 
    Polls for shutdown every poll_interval seconds. Ignores
    self.timeout. If you need to do periodic tasks, do them in
    another thread.
    """
    self.__is_shut_down.clear()
    try:
        # XXX: Consider using another file descriptor or connecting to the
        # socket to wake this up instead of polling. Polling reduces our
        # responsiveness to a shutdown request and wastes cpu at all other
        # times.
        with _ServerSelector() as selector:
            selector.register(self, selectors.EVENT_READ)
 
            while not self.__shutdown_request:
                ready = selector.select(poll_interval)
                if ready:
                    self._handle_request_noblock()
 
                self.service_actions()
    finally:
        self.__shutdown_request = False
        self.__is_shut_down.set()

selectorとなっているのはselectorsモジュールで定義されているものです。selectorsモジュールはselectorsモジュールでコードリーディング的におもしろいのですが省略します。

_handle_request_noblock。get_requestはTCPなのかUDPなのかで違うのでサブクラスで定義されています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 
-
|
|
|
|
!
 
 
 
 
 
 
 
 
 
 
def _handle_request_noblock(self):
    """Handle one request, without blocking.
 
    I assume that selector.select() has returned that the socket is
    readable before this function was called, so there should be no risk of
    blocking in get_request().
    """
    try:
        request, client_address = self.get_request()
    except OSError:
        return
    if self.verify_request(request, client_address):
        try:
            self.process_request(request, client_address)
        except:
            self.handle_error(request, client_address)
            self.shutdown_request(request)

process_requestおよびprocess_requestから呼び出されるfinish_request。finishと言ってますが終了処理ではなくこのメソッドで処理が行われています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 
-
|
|
|
!
 
 
 
 
 
 
def process_request(self, request, client_address):
    """Call finish_request.
 
    Overridden by ForkingMixIn and ThreadingMixIn.
 
    """
    self.finish_request(request, client_address)
    self.shutdown_request(request)
 
def finish_request(self, request, client_address):
    """Finish one request by instantiating RequestHandlerClass."""
    self.RequestHandlerClass(request, client_address, self)

というわけで、インスタンス作成時に渡したリクエストハンドラクラスに処理が移譲されています。

WSGIRequestHandler

リクエストハンドラクラスはbasehttp.pyで定義されているWSGIRequestHandler、このクラスは、socketserver.BaseRequestHandler > socketserver.StreamRequestHandler > http.server.BaseHTTPRequestHandler > wsgiref.simple_server.WSGIRequestHandlerというような継承関係を持っています。で、BaseRequestHandlerに書かれている__init__メソッド。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 
 
 
 
 
 
 
 
 
def __init__(self, request, client_address, server):
    self.request = request
    self.client_address = client_address
    self.server = server
    self.setup()
    try:
        self.handle()
    finally:
        self.finish()

handleメソッドはWSGIRequestHandlerに定義されています。parse_requestはBaseHTTPRequestHandlerで定義されています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
def handle(self):
    """Copy of WSGIRequestHandler, but with different ServerHandler"""
 
    self.raw_requestline = self.rfile.readline(65537)
    if len(self.raw_requestline) > 65536:
        self.requestline = ''
        self.request_version = ''
        self.command = ''
        self.send_error(414)
        return
 
    if not self.parse_request():  # An error code has been sent, just exit
        return
 
    handler = ServerHandler(
        self.rfile, self.wfile, self.get_stderr(), self.get_environ()
    )
    handler.request_handler = self      # backpointer for logging
    handler.run(self.server.get_app())

ServerHandler

コメントにあるようにServerHandlerとしてbasehttp.pyで定義されているものが使われますが少しメソッドをオーバーライドしているだけなので実質的にはwsgiref.simple_serverで定義されているものが使われます。ServerHandlerは、wsgiref.handlers.BaseHandler > wsgiref.handlers.SimpleHandler > wsgiref.simple_server.ServerHandlerという継承関係でrunはBaseHandlerに書かれています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 
-
|
|
|
|
!
 
 
 
 
 
 
 
-
!
 
def run(self, application):
    """Invoke the application"""
    # Note to self: don't move the close()!  Asynchronous servers shouldn't
    # call close() from finish_response(), so if you close() anywhere but
    # the double-error branch here, you'll break asynchronous servers by
    # prematurely closing.  Async servers must return from 'run()' without
    # closing if there might still be output to iterate over.
    try:
        self.setup_environ()
        self.result = application(self.environ, self.start_response)
        self.finish_response()
    except:
        try:
            self.handle_error()
        except:
            # If we get an error handling an error, just give up already!
            self.close()
            raise   # ...and let the actual server figure it out.

applicationは前回どのように作られているのかを追いかけたWSGIHandlerです。

django/core/handlers/wsgi.py

Djangoのコードに戻ってきました。というわけでWSGIHandlerの__call__です。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 
 
 
 
 
-
!
 
 
 
 
 
 
 
 
 
 
 
 
 
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)
    except UnicodeDecodeError:
        # 省略
        response = http.HttpResponseBadRequest()
    else:
        response = self.get_response(request)
 
    response._handler_class = self.__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():
        response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
    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)
    return response

request_classはすぐ上にありますがWSGIRequestです。responseを取得した後、クライアントにレスポンスを返すためにいろいろやってますが今回はそこら辺はさくっと無視します。

環境変数設定の確認

先に進む前に環境変数(実際にはOSの環境変数ではなく辞書オブジェクト)の設定について確認しておきましょう。環境変数は以下の個所で設定されています。

wsgiref.simple_server.WSGIServer.sever_bind
→setup_environ
  base_environを設定
http.server.BaseHTTPRequestHandler.parse_request
  環境変数ではないがリクエストのパスなどを設定
wsgiref.simple_server.WSGIRequestHandler.get_environ
  serverのbase_environをコピー
  parse_requestの結果などを環境変数に設定
wsgiref.handlers.BaseHandler.setup_environ
  OSの環境変数を読み込み
  add_cgi_varsメソッド呼び出し
  →SimpleHandlerでオーバーライドされていて引数で渡されたbase_env(WSGIRequestHandler.get_environの戻り値)をマージ

これらがリクエストを処理する際に設定されている環境変数の設定元になります。

get_response

さて、リクエストが処理される際の環境設定について確認できたところでレスポンスを得るための処理に進みましょう。 get_responseメソッドはdjango/core/handlers/base.pyの方のBaseHandlerに記述されています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 
-
!
 
 
 
-
|
|
!
 
 
-
|
!
 
 
 
 
 
 
 
 
 
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)
 
    # This block is only needed for legacy MIDDLEWARE_CLASSES; if
    # MIDDLEWARE is used, self._response_middleware will be empty.
    # ということらしいので省略
 
    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

_middleware_chainは前回見たsettingsに基づくミドルウェアの処理です。基本的な処理を追いかけるという点では個々のミドルウェアを確認する必要はないのでチェーンの最後に設定されている_get_responseに進みます。

_get_responseその1

_get_responseは長いのでまず前半。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
-
|
|
|
!
 
 
 
 
 
 
 
 
 
 
 
 
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
        set_urlconf(urlconf)
        resolver = get_resolver(urlconf)
    else:
        resolver = get_resolver()
 
    resolver_match = resolver.resolve(request.path_info)
    callback, callback_args, callback_kwargs = resolver_match
    request.resolver_match = resolver_match

urlconfは設定されていないはずなので引数なしでget_resolverが呼ばれます。get_resolverはdjango/urls/resolvers.pyに定義されています。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
 
 
 
 
 
def get_resolver(urlconf=None):
    if urlconf is None:
        from django.conf import settings
        urlconf = settings.ROOT_URLCONF
    return RegexURLResolver(r'^/', urlconf)

リクエストに対する処理関数の決定(基本構造)

RegexURLResolverのresolveメソッド。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 
 
 
 
 
 
 
 
 
 
-
!
 
-
!
 
 
-
|
!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
def resolve(self, path):
    path = force_text(path)  # path may be a reverse_lazy object
    tried = []
    match = self.regex.search(path)
    if match:
        new_path = path[match.end():]
        for pattern in self.url_patterns:
            try:
                sub_match = pattern.resolve(new_path)
            except Resolver404 as e:
                # 省略
            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)
 
                    # 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(
                        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})

初めの方は問題ないと思います。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
 
 
 
 
 
 
def resolve(self, path):
    path = force_text(path)  # path may be a reverse_lazy object
    tried = []
    match = self.regex.search(path)
    if match:
        new_path = path[match.end():]

regexはさっき設定していたr'^/'なんだろうなと想像できます。で、マッチした部分を取り除いたnew_pathを作る。その後、

Everything is expanded.Everything is shortened.
  1
  2
  3
 
 
 
for pattern in self.url_patterns:
    try:
        sub_match = pattern.resolve(new_path)

url_patternsなんていつの間に設定されたんや。答えはresolveメソッドの下にあります。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 
 
-
!
 
 
 
-
!
 
 
 
 
 
 
 
@cached_property
def url_patterns(self):
    # urlconf_module might be a valid set of patterns, so we default to it
    patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
    try:
        iter(patterns)
    except TypeError:
        # 省略
    return patterns
 
@cached_property
def urlconf_module(self):
    if isinstance(self.urlconf_name, six.string_types):
        return import_module(self.urlconf_name)
    else:
        return self.urlconf_name

というわけで、url_patternsの実体はメソッドでプロパティとして呼べるようになっています。url_patterns中でさらにurlconf_moduleを参照しており、これもプロパティとして設定されています。これにより、mysite/urls.pyが読み込まれることになります。

urls.pyの読み込み

mysite/urls.pyはチュートリアルでは以下のように定義しました。

mysite/urls.py

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
 
 
 
 
 
 
 
from django.conf.urls import include, url
from django.contrib import admin
 
urlpatterns = [
    url(r'^polls/', include('polls.urls')),
    url(r'^admin/', admin.site.urls),
]

url、また、includeを追っかける必要があります。それぞれ、django/conf/urls/__init__.pyに書かれています。

includeは長いので今回関係のあるところだけ抜き出すと、

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 
 
-
!
-
!
 
 
 
 
 
 
def include(arg, namespace=None, app_name=None):
    if isinstance(arg, tuple):
        # 省略
    else:
        # No namespace hint - use manually provided namespace
        urlconf_module = arg
 
    if isinstance(urlconf_module, six.string_types):
        urlconf_module = import_module(urlconf_module)
    patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module)
 
    return (urlconf_module, app_name, namespace)

ということで指定されたモジュール(polls.urls)を読み込んで返しています。polls/url.pyは以下のような感じ。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
 
 
 
 
 
 
 
from django.conf.urls import url
 
from . import views
 
urlpatterns = [
    url(r'^$', views.index, name='index'),
]

で、url関数。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 
 
-
!
 
 
 
 
 
def url(regex, view, kwargs=None, name=None):
    if isinstance(view, (list, tuple)):
        # For include(...) processing.
        urlconf_module, app_name, namespace = view
        return RegexURLResolver(regex, urlconf_module, kwargs, app_name=app_name, namespace=namespace)
    elif callable(view):
        return RegexURLPattern(regex, view, kwargs, name)
    else:
        raise TypeError('view must be a callable or a list/tuple in the case of include().')

view(第2引数)がタプルなのかcallableなのかによって返すオブジェクトが変わっています。タプルの場合というのはすぐ上で見たようなincludeが使われている場合です。

さて、というわけで以上をまとめると、resolverというのは実は以下のような階層構造を持っていることがわかります。

RegexURLResolver(r'^/')
  RegexURLResolver(r'^polls/') # mysite/urls.py
    RegexURLPattern(r'^$', views.index) # polls/urls.py

これがわかるとresolveメソッドの動きが理解できるようになります。

リクエストに対する処理関数の決定(具体例)

というわけで、get_resolver関数が返したRegexURLResolverのresolveメソッドにて、

Everything is expanded.Everything is shortened.
  1
  2
  3
 
 
 
for pattern in self.url_patterns:
    try:
        sub_match = pattern.resolve(new_path)

と書いてあると、RegexURLResolverオブジェクト(r'polls/')のresolveが呼び出されます。さらにその中でRegexURLPattern(r'^$', views.index)のresolveが呼び出されます。その結果、ResolveMatchオブジェクトが返されます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 
 
 
 
-
|
|
!
 
-
!
 
class RegexURLPattern(LocaleRegexProvider):
    def resolve(self, path):
        match = self.regex.search(path)
        if match:
            # If there are any named groups, use those as kwargs, ignoring
            # non-named groups. Otherwise, pass all non-named arguments as
            # positional arguments.
            kwargs = match.groupdict()
            args = () if kwargs else match.groups()
            # In both cases, pass any extra_kwargs as **kwargs.
            kwargs.update(self.default_args)
            return ResolverMatch(self.callback, args, kwargs, self.name)

で、それをRegexURLResolverのresolveメソッドが受け取り、自分の情報とマージして返します。このRegexURLResolverのresolveメソッドが受け取り返すは二回発生します(r'^/'とr'^polls/')

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
-
!
 
 
-
|
!
 
 
 
 
 
 
 
 
 
 
 
# Merge captured arguments in match with submatch
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
sub_match_dict.update(sub_match.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(
    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,
)

これでようやく、リクエストパスに対する処理関数が取得できました。

_get_responseその2

というわけで_get_responseの続き。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
-
!
 
 
 
 
 
 
 
 
 
 
 
-
!
-
!
-
|
!
 
 
-
!
-
!
 
 
 
 
 
 
    # Apply view middleware
    for middleware_method in self._view_middleware:
        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)
        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 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:
                # 省略
 
        try:
            response = response.render()
        except Exception as e:
            response = self.process_exception_by_middleware(e, request)
 
    return response

make_view_atomicメソッドで何やらDB関連の処理をしていますがまだDBは出てきてないので無視、処理関数が呼ばれてレスポンスが返されます。

Everything is expanded.Everything is shortened.
  1
  2
  3
  4
 
 
 
 
from django.http import HttpResponse
 
def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

HttpResponseにはrenderは定義されてません。きっとテンプレートの時に出てくるのでしょう。かなり長かったですが以上でリクエストが来てから自分の書いたコードに到達するまでを確認できました。

おわりに

今回は標準モジュールのコード、Djangoのコード、自分の書いたコードといろいろなコードを行き来することになりました。特に継承関係があると呼ばれているメソッドがスーパークラスで定義されていたり、逆にサブクラスでオーバーライドされていたりと実際に呼ばれるものを探すのが大変になります。

また、プロパティが使われていたり、オブジェクト構造が動的に作られていたりするなど、徐々に複雑なコードになってきました。ただし、Djangoはビュー(他で言うController)、モデル、テンプレート(他だとView)が完全に分離されているのでまだ読みやすいようには思います。今回の場合、リクエストを処理するにあたってビューしか出てきておらず、テンプレートはなんかここら辺で呼ばれてそうだなーという箇所があるのみ、モデルに至っては全く絡んできていません。これらは次回以降出てくることでしょう。

あ、convert_exception_to_response忘れた。もうかなり長くなっているのでやっていることの要点だけまとめると以下の処理をしています。

  • 元々の関数を呼び出して例外(404とかもDjango的には例外として扱われています)が起きたらそれを処理、というラッパーを作成して返す
  • resolverを呼び出してステータスコードに対応するエラーハンドラを呼び出す(urls.pyに定義されてればそれを、なければデフォルトのハンドラを使う)

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2017-03-05 (日) 14:26:42 (2601d)