[[Djangoを読む]] #contents *はじめに [#j2fb036b] 前回はサーバが起動するまでを見てきました。それでは続いてリクエストが送られてきたときにどのようにして自分が書いたコードに到達するかを見ていくことにしましょう。 *django/core/servers/basehttp.py [#g9a8f1a5] 見るのは引き続き、basehttp.pyです。改めてrun関数を確認。 #code(Python){{ 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 [#y3301245] WSGIServerは標準ライブラリに用意されているクラスでwsgi.simple_serverモジュールにあります。作成しているのはそれではなくbasehttp.pyで継承して少し機能拡張したものですが同じとみなして問題ないでしょう。 WSGIServerはhttp.serverのHTTPServerを継承し、HTTPServerはsocketserverのTCPServerを継承し、TCPServerはBaseServerを継承し、とここまでたどるとserve_foreverメソッドが出てきます。 #code(Python){{ 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なのかで違うのでサブクラスで定義されています。 #code(Python){{ 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と言ってますが終了処理ではなくこのメソッドで処理が行われています。 #code(Python){{ 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 [#o4ce23d6] リクエストハンドラクラスはbasehttp.pyで定義されているWSGIRequestHandler、このクラスは、socketserver.BaseRequestHandler > socketserver.StreamRequestHandler > http.server.BaseHTTPRequestHandler > wsgiref.simple_server.WSGIRequestHandlerというような継承関係を持っています。で、BaseRequestHandlerに書かれている__init__メソッド。 #code(Python){{ 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で定義されています。 #code(Python){{ 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 [#qd5f6d43] コメントにあるようにServerHandlerとしてbasehttp.pyで定義されているものが使われますが少しメソッドをオーバーライドしているだけなので実質的にはwsgiref.simple_serverで定義されているものが使われます。ServerHandlerは、wsgiref.handlers.BaseHandler > wsgiref.handlers.SimpleHandler > wsgiref.simple_server.ServerHandlerという継承関係でrunはBaseHandlerに書かれています。 #code(Python){{ 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 [#mdc18df2] Djangoのコードに戻ってきました。というわけでWSGIHandlerの__call__です。 #code(Python){{ 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を取得した後、クライアントにレスポンスを返すためにいろいろやってますが今回はそこら辺はさくっと無視します。 **環境変数設定の確認 [#i2e20a95] 先に進む前に環境変数(実際には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 [#vc6f37cb] さて、リクエストが処理される際の環境設定について確認できたところでレスポンスを得るための処理に進みましょう。 get_responseメソッドはdjango/core/handlers/base.pyの方のBaseHandlerに記述されています。 #code(Python){{ 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 [#v05f93ea] _get_responseは長いのでまず前半。 #code(Python){{ 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に定義されています。 #code(Python){{ def get_resolver(urlconf=None): if urlconf is None: from django.conf import settings urlconf = settings.ROOT_URLCONF return RegexURLResolver(r'^/', urlconf) }} **リクエストに対する処理関数の決定(基本構造) [#m868c871] RegexURLResolverのresolveメソッド。 #code(Python){{ 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}) }} 初めの方は問題ないと思います。 #code(Python){{ 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を作る。その後、 #code(Python){{ for pattern in self.url_patterns: try: sub_match = pattern.resolve(new_path) }} url_patternsなんていつの間に設定されたんや。答えはresolveメソッドの下にあります。 #code(Python){{ @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の読み込み [#ga9e4ac1] mysite/urls.pyはチュートリアルでは以下のように定義しました。 mysite/urls.py #code(Python){{ 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は長いので今回関係のあるところだけ抜き出すと、 #code(Python){{ 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は以下のような感じ。 #code(Python){{ from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), ] }} で、url関数。 #code(Python){{ 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メソッドの動きが理解できるようになります。 **リクエストに対する処理関数の決定(具体例) [#k50badb4] というわけで、get_resolver関数が返したRegexURLResolverのresolveメソッドにて、 #code(Python){{ for pattern in self.url_patterns: try: sub_match = pattern.resolve(new_path) }} と書いてあると、RegexURLResolverオブジェクト(r'polls/')のresolveが呼び出されます。さらにその中でRegexURLPattern(r'^$', views.index)のresolveが呼び出されます。その結果、ResolveMatchオブジェクトが返されます。 #code(Python){{ 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/') #code(Python){{ # 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 [#k8abb6a9] というわけで_get_responseの続き。 #code(Python){{ # 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は出てきてないので無視、処理関数が呼ばれてレスポンスが返されます。 #code(Python){{ from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the polls index.") }} HttpResponseにはrenderは定義されてません。きっとテンプレートの時に出てくるのでしょう。かなり長かったですが以上でリクエストが来てから自分の書いたコードに到達するまでを確認できました。 *おわりに [#k540d554] 今回は標準モジュールのコード、Djangoのコード、自分の書いたコードといろいろなコードを行き来することになりました。特に継承関係があると呼ばれているメソッドがスーパークラスで定義されていたり、逆にサブクラスでオーバーライドされていたりと実際に呼ばれるものを探すのが大変になります。 また、プロパティが使われていたり、オブジェクト構造が動的に作られていたりするなど、徐々に複雑なコードになってきました。ただし、Djangoはビュー(他で言うController)、モデル、テンプレート(他だとView)が完全に分離されているのでまだ読みやすいようには思います。今回の場合、リクエストを処理するにあたってビューしか出てきておらず、テンプレートはなんかここら辺で呼ばれてそうだなーという箇所があるのみ、モデルに至っては全く絡んできていません。これらは次回以降出てくることでしょう。 あ、convert_exception_to_response忘れた。もうかなり長くなっているのでやっていることの要点だけまとめると以下の処理をしています。 -元々の関数を呼び出して例外(404とかもDjango的には例外として扱われています)が起きたらそれを処理、というラッパーを作成して返す -resolverを呼び出してステータスコードに対応するエラーハンドラを呼び出す(urls.pyに定義されてればそれを、なければデフォルトのハンドラを使う)