Djangoを読む
はじめに †
前回はサーバが起動するまでを見てきました。それでは続いてリクエストが送られてきたときにどのようにして自分が書いたコードに到達するかを見ていくことにしましょう。
django/core/servers/basehttp.py †
見るのは引き続き、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は標準ライブラリに用意されているクラスでwsgi.simple_serverモジュールにあります。作成しているのはそれではなくbasehttp.pyで継承して少し機能拡張したものですが同じとみなして問題ないでしょう。
WSGIServerはhttp.serverのHTTPServerを継承し、HTTPServerはsocketserverのTCPServerを継承し、TCPServerはBaseServerを継承し、とここまでたどるとserve_foreverメソッドが出てきます。
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:
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なのかで違うのでサブクラスで定義されています。
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と言ってますが終了処理ではなくこのメソッドで処理が行われています。
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__メソッド。
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で定義されています。
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(): return
handler = ServerHandler(
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
)
handler.request_handler = self 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に書かれています。
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"""
try:
self.setup_environ()
self.result = application(self.environ, self.start_response)
self.finish_response()
except:
try:
self.handle_error()
except:
self.close()
raise
|
applicationは前回どのように作られているのかを追いかけたWSGIHandlerです。
django/core/handlers/wsgi.py †
Djangoのコードに戻ってきました。というわけでWSGIHandlerの__call__です。
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に記述されています。
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."""
set_urlconf(settings.ROOT_URLCONF)
response = self._middleware_chain(request)
response._closable_objects.append(request)
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は長いのでまず前半。
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に定義されています。
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メソッド。
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) 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:
sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
sub_match_dict.update(sub_match.kwargs)
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})
|
初めの方は問題ないと思います。
1
2
3
4
5
6
|
| def resolve(self, path):
path = force_text(path) tried = []
match = self.regex.search(path)
if match:
new_path = path[match.end():]
|
regexはさっき設定していたr'^/'なんだろうなと想像できます。で、マッチした部分を取り除いたnew_pathを作る。その後、
1
2
3
|
| for pattern in self.url_patterns:
try:
sub_match = pattern.resolve(new_path)
|
url_patternsなんていつの間に設定されたんや。答えはresolveメソッドの下にあります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
-
!
-
!
| @cached_property
def url_patterns(self):
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
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は長いので今回関係のあるところだけ抜き出すと、
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:
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は以下のような感じ。
1
2
3
4
5
6
7
|
| from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
]
|
で、url関数。
1
2
3
4
5
6
7
8
9
|
-
!
| def url(regex, view, kwargs=None, name=None):
if isinstance(view, (list, tuple)):
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メソッドにて、
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オブジェクトが返されます。
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:
kwargs = match.groupdict()
args = () if kwargs else match.groups()
kwargs.update(self.default_args)
return ResolverMatch(self.callback, args, kwargs, self.name)
|
で、それをRegexURLResolverのresolveメソッドが受け取り、自分の情報とマージして返します。このRegexURLResolverのresolveメソッドが受け取り返すは二回発生します(r'^/'とr'^polls/')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| -
!
-
|
!
| sub_match_dict = dict(match.groupdict(), **self.default_kwargs)
sub_match_dict.update(sub_match.kwargs)
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の続き。
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
| -
!
-
!
-
!
-
|
!
-
!
-
!
| 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)
if response is None:
elif hasattr(response, 'render') and callable(response.render):
for middleware_method in self._template_response_middleware:
response = middleware_method(request, response)
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は出てきてないので無視、処理関数が呼ばれてレスポンスが返されます。
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に定義されてればそれを、なければデフォルトのハンドラを使う)