[[Djangoを読む]] #contents *はじめに [#p24a089a] Djangoプロジェクト(とアプリ)を作成したのでいよいよフレームワークの中身に入っていきます。 と言ってもいきなりフルセットのアプリを追っかけるのはつらいので、[[チュートリアル>https://docs.djangoproject.com/ja/1.10/intro/tutorial01/#write-your-first-view]]にそってまずは単純なビューについて動作を見ていきましょう。 チュートリアルの通りに書いていくと以下の3つのファイルを作成することになります。 polls/view.py #code(Python){{ from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the polls index.") }} polls/urls.py #code(Python){{ from django.conf.urls import url from . import views urlpatterns = [ url(r'^$', views.index, name='index'), ] }} 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), ] }} で、manage.pyを使って開発サーバを起動と。 $ python manage.py runserver *django/core/management/commands/runserver.py [#vf0ab17c] それでは挙げたのとは逆順にサーバ起動から見ていきましょう。実行されるのは前回でわかったようにrunserver.pyにあるCommandクラスです。handleメソッドは、オプション処理を全部無視すると非常にシンプルです。 #code(Python){{ def handle(self, *args, **options): self.run(**options) }} runメソッド #code(Python){{ def run(self, **options): """ Runs the server, using the autoreloader if needed """ use_reloader = options['use_reloader'] if use_reloader: autoreload.main(self.inner_run, None, options) else: self.inner_run(None, **options) }} オートリローダーを使う、使わないの違いはありますがともかく、inner_runが呼び出されています。オートリローダーはオートリローダーでおもしろいので後で見ます。 inner_runもシステムチェックとかをしている部分は飛ばして、コアとなる部分、 #code(Python){{ def inner_run(self, *args, **options): try: handler = self.get_handler(*args, **options) run(self.addr, int(self.port), handler, ipv6=self.use_ipv6, threading=threading) except socket.error as e: # 省略 except KeyboardInterrupt: if shutdown_message: self.stdout.write(shutdown_message) sys.exit(0) }} get_handlerに進む前に、 またrunが出てきていますが、このrunは先ほどのrunメソッドではありません。Pythonの場合、メソッド呼び出しなら「self.run(...)」になります。というわけでこのrunが何者かとファイルの上の方を見てみると、 #code(Python){{ from django.core.servers.basehttp import get_internal_wsgi_application, run }} と、basehttpに定義されている関数なことがわかります。紛らわしい。 *django/core/servers/basehttp.py [#cba4c610] **get_internal_wsgi_application [#a9e99c7c] では改めてget_handlerメソッドを、と言いたいところですが、get_handlerメソッドは一行書いてあるだけです。 #code(Python){{ def get_handler(self, *args, **options): """ Returns the default WSGI handler for the runner. """ return get_internal_wsgi_application() }} 結局、basehttp.pyに集約されているわけですね。というわけでget_internal_wsgi_applicationを見てみましょう。 #code(Python){{ 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 app_path = getattr(settings, 'WSGI_APPLICATION') if app_path is None: return get_wsgi_application() try: return import_string(app_path) except ImportError as e: # 省略 }} 飛ばしましたが、前回も出てきたsettings、今度はファイルがちゃんとあるので読み込まれます。その際、django.conf.settingsオブジェクトの属性(global_settings)に「プロジェクトフォルダにあるsettings」の内容がマージされます。そんなからくりでプロジェクトのsettingsで設定した内容がDjangoの実行時に使われることになります。うん、まあちゃんと見たほうがいいか。 django/conf/__init__.py #code(Python){{ class Settings(BaseSettings): def __init__(self, settings_module): # update this dict from global settings (but only for ALL_CAPS settings) for setting in dir(global_settings): if setting.isupper(): setattr(self, setting, getattr(global_settings, setting)) # store the settings module in case someone later cares self.SETTINGS_MODULE = settings_module mod = importlib.import_module(self.SETTINGS_MODULE) self._explicit_settings = set() for setting in dir(mod): if setting.isupper(): setting_value = getattr(mod, setting) setattr(self, setting, setting_value) self._explicit_settings.add(setting) }} わかってる人にはわかってるけど、dirで属性一覧が取得できるとか参考になりますね。 話を戻して、WSGI_APPLICATIONはプロジェクトフォルダのsettingsに書かれているので、そちらが利用されます。 #code(Python){{ WSGI_APPLICATION = 'mysite.wsgi.application' }} import_stringはdjango/utils/module_loading.pyに定義されていて、最後のピリオドの前までをモジュールとしてインポート、最後のピリオドの後をモジュール内の属性として返す関数です。つまり、mysite.wsgiモジュールのapplication属性が返されます。というわけで、mysite/wsgi.pyに進む、 #code(Python){{ """ WSGI config for mysite project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings") application = get_wsgi_application() }} たらいまわしされてる気分ですが、結局、get_wsgi_applicationが呼ばれます。 django/coreの方のwsgi.py #code(Python){{ import django from django.core.handlers.wsgi import WSGIHandler 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) return WSGIHandler() }} うんざりしてきました。今度は、django/core/handlersのwsgi.pyです。 #code(Python){{ class WSGIHandler(base.BaseHandler): def __init__(self, *args, **kwargs): super(WSGIHandler, self).__init__(*args, **kwargs) self.load_middleware() }} **BaseHandler.load_middleware [#n3643480] baseは同じフォルダにあるdjango/core/handlers/base.pyです。__init__は見ないでもいいとしてload_middlewareは見ておく必要があるでしょう。 #code(Python){{ 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 = [] self._view_middleware = [] self._template_response_middleware = [] self._response_middleware = [] self._exception_middleware = [] if settings.MIDDLEWARE is None: # 省略 else: handler = convert_exception_to_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: # 省略 continue 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 }} convert_exception_to_responseは同じフォルダにあるexception.pyに定義されています。詳しくはまた後で。 settingsに定義されているミドルウェアを一つずつ構築し、handlerに再代入しています。大体予測できますがミドルウェア定義に行ってみましょう。ちなみに、settingsに定義されているミドルウェアは以下の通りです。 #code(Python){{ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] }} reversedしているので、下から先に構築されます。 どれでもいいのでdjango/contrib/sessions/middleware.pyを見てみましょう。 #code(Python){{ class SessionMiddleware(MiddlewareMixin): def __init__(self, get_response=None): self.get_response = get_response engine = import_module(settings.SESSION_ENGINE) self.SessionStore = engine.SessionStore def process_request(self, request): session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) request.session = self.SessionStore(session_key) def process_response(self, request, response): # 省略 }} MiddlewareMixinはdjango/utils/deprecation.pyに定義されています。・・・deprecation?そのうち廃止されるってこと?(上に挙げたミドルウェアは全部これを継承している) #code(Python){{ class MiddlewareMixin(object): def __init__(self, get_response=None): self.get_response = get_response super(MiddlewareMixin, self).__init__() def __call__(self, request): response = None if hasattr(self, 'process_request'): response = self.process_request(request) if not response: response = self.get_response(request) if hasattr(self, 'process_response'): response = self.process_response(request, response) return response }} 書いてあるままですが、 +process_requestが定義されてたらそれを呼ぶ +process_requestが何も返さなかったらコンストラクタで受け取ったget_responseを呼ぶ +process_responseが定義されてたらそれを呼ぶ ここで、get_responseという名前ですが、これは__call__メソッドが定義されたミドルウェアオブジェクトです。つまり、 ラップして作られた先頭のミドルウェアの__call__が呼ばれる 次のミドルウェアの__call__が呼ばれる ・・・ BaseHandlerの_get_responseが呼ばれる(これは関数) のようにミドルウェアが連続実行されていくという仕組みになっているのですね。これでようやくhandlerが作られるところが終わりました。_get_responseは後ほど見ます。 **run [#i3129093] さて、話をdjango/core/servers/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クラスです。で、WSGIServerはファイルの上の方で定義されていて標準ライブラリのwsgiref.simple_server.WSGIServerを継承したクラスです。リクエストハンドリングについては次回で見ていきます。 *django/utils/autoreload.py [#f09c4ad7] 最後に、後で見るといったautoloadです。 #code(Python){{ def main(main_func, args=None, kwargs=None): if args is None: args = () if kwargs is None: kwargs = {} if sys.platform.startswith('java'): reloader = jython_reloader else: reloader = python_reloader wrapped_main_func = check_errors(main_func) reloader(wrapped_main_func, args, kwargs) }} まあCPythonということでpython_reloaderへ。 #code(Python){{ def python_reloader(main_func, args, kwargs): if os.environ.get("RUN_MAIN") == "true": thread.start_new_thread(main_func, args, kwargs) try: reloader_thread() except KeyboardInterrupt: pass else: try: exit_code = restart_with_reloader() if exit_code < 0: os.kill(os.getpid(), -exit_code) else: sys.exit(exit_code) except KeyboardInterrupt: pass }} RUN_MAINという謎の環境変数が出てきました。定義されてないはずなのでrestart_with_reloaderへ。 #code(Python){{ def restart_with_reloader(): while True: args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv if sys.platform == "win32": args = ['"%s"' % arg for arg in args] new_environ = os.environ.copy() new_environ["RUN_MAIN"] = 'true' exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ) if exit_code != 3: return exit_code }} RUN_MAIN出てきました。それにしても力技感あふれるコードです。 次にreload_threadを見てみます。 #code(Python){{ def reloader_thread(): ensure_echo_on() if USE_INOTIFY: fn = inotify_code_changed else: fn = code_changed while RUN_RELOADER: change = fn() if change == FILE_MODIFIED: sys.exit(3) # force reload elif change == I18N_MODIFIED: reset_translations() time.sleep(1) }} これ以上追いかけるのはやめますが、何をやっているかをまとめると、 +親プロセスはRUN_MAIN環境変数を定義して自分と同じ引数で子プロセスを起動 +子プロセスは元々の関数をスレッドで実行。一方でファイルの更新がないか確認 +ファイルが更新されてたらステータス3で終了 +それを検出した親プロセスは子プロセスを実行し直す→更新したファイルがリロードされたことになる なんというか力技ですね:-) *おわりに [#rc91fc8f] 今回はDjangoテストサーバの起動について見てきました。個々のやっていることは単純ですがあちこち飛び回って少しややこしく感じました。まあそれでもそのファイル内を見れば外部のファイルはどこにあるのかわかる分Railsよりは楽ですね(笑) Railsバッシングばかりしている気がしますが、次回はリクエストを受け付けてのハンドリング、初めに挙げたチュートリアルコードのurls.pyだとかviews.pyが呼び出される部分について見ていきます。