Djangoを読む
はじめに †
Djangoプロジェクト(とアプリ)を作成したのでいよいよフレームワークの中身に入っていきます。
と言ってもいきなりフルセットのアプリを追っかけるのはつらいので、チュートリアルにそってまずは単純なビューについて動作を見ていきましょう。
チュートリアルの通りに書いていくと以下の3つのファイルを作成することになります。
polls/view.py
1
2
3
4
|
| from django.http import HttpResponse
def index(request):
return HttpResponse("Hello, world. You're at the polls index.")
|
polls/urls.py
1
2
3
4
5
6
7
|
| from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
|
mysite/urls.py
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),
]
|
で、manage.pyを使って開発サーバを起動と。
$ python manage.py runserver
django/core/management/commands/runserver.py †
それでは挙げたのとは逆順にサーバ起動から見ていきましょう。実行されるのは前回でわかったようにrunserver.pyにあるCommandクラスです。handleメソッドは、オプション処理を全部無視すると非常にシンプルです。
1
2
|
| def handle(self, *args, **options):
self.run(**options)
|
runメソッド
1
2
3
4
5
6
7
8
9
10
|
-
|
!
| 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もシステムチェックとかをしている部分は飛ばして、コアとなる部分、
1
2
3
4
5
6
7
8
9
10
11
|
-
!
| 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が何者かとファイルの上の方を見てみると、
1
|
| from django.core.servers.basehttp import get_internal_wsgi_application, run
|
と、basehttpに定義されている関数なことがわかります。紛らわしい。
django/core/servers/basehttp.py †
get_internal_wsgi_application †
では改めてget_handlerメソッドを、と言いたいところですが、get_handlerメソッドは一行書いてあるだけです。
1
2
3
4
5
|
-
|
!
| 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を見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
-
|
|
|
|
|
|
|
|
|
|
!
-
| 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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
-
!
-
!
| class Settings(BaseSettings):
def __init__(self, settings_module):
for setting in dir(global_settings):
if setting.isupper():
setattr(self, setting, getattr(global_settings, setting))
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に書かれているので、そちらが利用されます。
1
|
| WSGI_APPLICATION = 'mysite.wsgi.application'
|
import_stringはdjango/utils/module_loading.pyに定義されていて、最後のピリオドの前までをモジュールとしてインポート、最後のピリオドの後をモジュール内の属性として返す関数です。つまり、mysite.wsgiモジュールのapplication属性が返されます。というわけで、mysite/wsgi.pyに進む、
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| -
|
|
|
|
|
|
!
| """
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
-
|
|
|
|
|
!
| 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です。
1
2
3
4
|
| class WSGIHandler(base.BaseHandler):
def __init__(self, *args, **kwargs):
super(WSGIHandler, self).__init__(*args, **kwargs)
self.load_middleware()
|
BaseHandler.load_middleware †
baseは同じフォルダにあるdjango/core/handlers/base.pyです。__init__は見ないでもいいとしてload_middlewareは見ておく必要があるでしょう。
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
35
36
37
|
-
|
|
|
|
!
-
!
-
!
-
|
!
| 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)
self._middleware_chain = handler
|
convert_exception_to_responseは同じフォルダにあるexception.pyに定義されています。詳しくはまた後で。
settingsに定義されているミドルウェアを一つずつ構築し、handlerに再代入しています。大体予測できますがミドルウェア定義に行ってみましょう。ちなみに、settingsに定義されているミドルウェアは以下の通りです。
1
2
3
4
5
6
7
8
9
|
| 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を見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
|
-
| 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?そのうち廃止されるってこと?(上に挙げたミドルウェアは全部これを継承している)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
| 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 †
さて、話をdjango/core/servers/basehttp.pyに戻して、run関数です。
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:
httpd.daemon_threads = True
httpd.set_app(wsgi_handler)
httpd.serve_forever()
|
スレッドを使うかどうかの違いはありますが使われるのはWSGIServerクラスです。で、WSGIServerはファイルの上の方で定義されていて標準ライブラリのwsgiref.simple_server.WSGIServerを継承したクラスです。リクエストハンドリングについては次回で見ていきます。
django/utils/autoreload.py †
最後に、後で見るといったautoloadです。
1
2
3
4
5
6
7
8
9
10
11
12
|
| 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へ。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
| 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へ。
1
2
3
4
5
6
7
8
9
10
|
| 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を見てみます。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
| 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) elif change == I18N_MODIFIED:
reset_translations()
time.sleep(1)
|
これ以上追いかけるのはやめますが、何をやっているかをまとめると、
- 親プロセスはRUN_MAIN環境変数を定義して自分と同じ引数で子プロセスを起動
- 子プロセスは元々の関数をスレッドで実行。一方でファイルの更新がないか確認
- ファイルが更新されてたらステータス3で終了
- それを検出した親プロセスは子プロセスを実行し直す→更新したファイルがリロードされたことになる
なんというか力技ですね:-)
おわりに †
今回はDjangoテストサーバの起動について見てきました。個々のやっていることは単純ですがあちこち飛び回って少しややこしく感じました。まあそれでもそのファイル内を見れば外部のファイルはどこにあるのかわかる分Railsよりは楽ですね(笑)
Railsバッシングばかりしている気がしますが、次回はリクエストを受け付けてのハンドリング、初めに挙げたチュートリアルコードのurls.pyだとかviews.pyが呼び出される部分について見ていきます。