(d1-entry-and-startup)= (Django の入り口と WSGIHandler)= # 第1章 入口と起動:`get_wsgi_application` / `WSGIHandler` / `django.setup` Vol.1 では、TCP ソケットから HTTP を組み立て、WSGI と ASGI という2つのインタフェース仕様を読み解き、最小の WSGI フレームワークと最小の ASGI アプリケーションを自分の手で書きました。 `application(environ, start_response)` という callable が、サーバとフレームワークを疎結合につなぐ約束ごとだということも確かめました。 本書(Vol.2)では、その約束ごとの上で **Django、Starlette、FastAPI が実際に何をしているのか**を、フレームワークの**実物のソースコードを読みながら**追いかけます。 最初の題材は Django です。 `runserver` でも Gunicorn でも、Django は Vol.1 で書いた `application(environ, start_response)` と**まったく同じ形**で WSGI サーバにつながります。では、その callable は**いつ、何を初期化して**生まれ、1リクエストごとに**何を**回しているのでしょうか。 この章では、その「起動の中身」と「応答側の最適化」まで、ソースで開いていきます。 ```{tip} ここでは Django の実際のソースコードを引用します。 引用ブロックの先頭には `# django/core/handlers/wsgi.py — Django 5.2` のように**出典**を記します。 省略は `# ...(中略)...` で示します。 手元で同じコードを開いて照らし合わせたい場合は、{ref}`付録A <付録A フレームワークのソースを手元で読む準備>` の手順でソースを用意してください。 本書の基準バージョンは Django 5.2.15(LTS)です。 ``` この章で学ぶことは次のとおりです。 - `wsgi.py` が Django と WSGI サーバをどうつなぐか - `get_wsgi_application()` が**なぜ薄いラッパ**なのか - `django.setup()` が起動時に何を初期化するか(`apps.populate()` の3フェーズ) - WSGI callable の本体 `WSGIHandler.__call__` が、リクエストの一生をどう回すか - 応答側の最適化 `wsgi.file_wrapper` が何を肩代わりするか - `environ` 辞書がどこで Django の `request` オブジェクトに渡されるか ## 1.1 wsgi.py:Django と WSGI サーバの接点 Django プロジェクトを `django-admin startproject myproject` で生成すると、`myproject/wsgi.py` というファイルが自動的に作られます。 ```python # myproject/wsgi.py(startproject が自動生成するコード) import os from django.core.wsgi import get_wsgi_application os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings") application = get_wsgi_application() ``` このファイルはわずか数行ですが、WSGI サーバと Django を接続する唯一の入り口です。 Gunicorn で Django を起動するとき `gunicorn myproject.wsgi:application` と指定しますが、これは「`myproject/wsgi.py` モジュールの中にある `application` という名前の WSGI callable を使え」という意味でした(Vol.1「WSGI が生まれた背景」を参照)。 `wsgi.py` が担っている責務は次の二つです。 - **設定モジュールの宣言**: `DJANGO_SETTINGS_MODULE` 環境変数を設定し、Django がどの設定ファイルを読み込むべきかを宣言します。この環境変数が未設定のまま Django を起動すると `ImproperlyConfigured` 例外が発生します。 - **WSGI callable の生成**: `get_wsgi_application()` を呼び出して WSGI callable を生成し、モジュールレベル変数 `application` に束縛します。 `setdefault` を使っているのは、すでに環境変数として設定済みなら上書きしないためです。 コンテナや systemd など、外側で `DJANGO_SETTINGS_MODULE` を渡す運用にも対応できます。 ## 1.2 get_wsgi_application:なぜ薄いラッパか `application` の実体を返す `get_wsgi_application()` は、Django のソースでは驚くほど短い関数です。 ```{code-block} python :linenos: # django/core/wsgi.py — Django 5.2 import django from django.core.handlers.wsgi import WSGIHandler def get_wsgi_application(): """ The public interface to Django's WSGI support. Return a WSGI callable. Avoids making django.core.handlers.WSGIHandler a public API, in case the internal WSGI implementation changes or moves in the future. """ django.setup(set_prefix=False) return WSGIHandler() ``` やっていることは二つだけです。 1. `django.setup(set_prefix=False)` で Django を初期化する。 2. `WSGIHandler()` のインスタンスを生成して返す。 docstring が明言しているとおり、この関数は **`WSGIHandler` を公開 API にしないための薄いラッパ**です。 "in case the internal WSGI implementation changes or moves in the future"。将来 WSGI 実装が変わっても利用者のコードを壊さないよう、入り口を `get_wsgi_application()` という名前に固定しているわけです。 `wsgi.py` が `from django.core.handlers.wsgi import WSGIHandler` と書かずに済むのは、この一段のラッパがあるからです。 「なぜこの関数がわざわざ存在するのか」がソースの docstring から分かる。これが実物を読む価値です。 引数の `set_prefix=False` にも理由があります。 `setup()` は既定ではスクリプトプレフィックス(URL の前置パス)を設定しますが、WSGI 経由では**この時点では設定しません**。 プレフィックスはリクエストごとに `WSGIHandler.__call__` の中で設定されるからです(後述)。 `FORCE_SCRIPT_NAME` の設定やサブパスへのマウントに対応するため、プレフィックスの確定をリクエスト単位まで遅らせているのです。 ## 1.3 django.setup が起動時にやること `django.setup()` を「初期化処理」の一語で済ませず、ソースを開きましょう。 これは Django プロセスの**起動時に一度だけ**走る、最も重要な前払い処理です。 ```{code-block} python :linenos: # django/__init__.py — Django 5.2 def setup(set_prefix=True): """ Configure the settings (this happens as a side effect of accessing the first setting), configure logging and populate the app registry. Set the thread-local urlresolvers script prefix if `set_prefix` is True. """ from django.apps import apps from django.conf import settings from django.urls import set_script_prefix from django.utils.log import configure_logging configure_logging(settings.LOGGING_CONFIG, settings.LOGGING) if set_prefix: set_script_prefix( "/" if settings.FORCE_SCRIPT_NAME is None else settings.FORCE_SCRIPT_NAME ) apps.populate(settings.INSTALLED_APPS) ``` `setup()` の本体は三つです。 ログ設定(`configure_logging`)、スクリプトプレフィックスの設定(`set_prefix=True` のときだけ)、そして本命の **`apps.populate(settings.INSTALLED_APPS)`** です。 `get_wsgi_application()` が `set_prefix=False` で呼んでいたので、WSGI 経由ではログ設定とアプリレジストリの構築だけが走ります。 ### 1.3.1 apps.populate:アプリレジストリを組み立てる3フェーズ `apps.populate()` は、`INSTALLED_APPS` に並べたアプリを読み込み、モデルを登録し、各アプリの起動フックを呼ぶ。Django が「使える状態」になるための中核です。 ```{code-block} python :linenos: # django/apps/registry.py — Django 5.2(Apps.populate) def populate(self, installed_apps=None): """ Load application configurations and models. Import each application module and then each model module. It is thread-safe and idempotent, but not reentrant. """ if self.ready: return with self._lock: if self.ready: return if self.loading: # Prevent reentrant calls to avoid running AppConfig.ready() # methods twice. raise RuntimeError("populate() isn't reentrant") self.loading = True # Phase 1: initialize app configs and import app modules. for entry in installed_apps: if isinstance(entry, AppConfig): app_config = entry else: app_config = AppConfig.create(entry) # ...(中略:ラベル重複チェック)... self.app_configs[app_config.label] = app_config app_config.apps = self # ...(中略:アプリ名の重複チェック)... self.apps_ready = True # Phase 2: import models modules. for app_config in self.app_configs.values(): app_config.import_models() self.clear_cache() self.models_ready = True # Phase 3: run ready() methods of app configs. for app_config in self.get_app_configs(): app_config.ready() self.ready = True self.ready_event.set() ``` 3つのフェーズがはっきり分かれています。 1. **Phase 1(アプリ設定の初期化)**: `INSTALLED_APPS` の各エントリから `AppConfig` を生成し(`AppConfig.create`)、アプリモジュールをインポートします。ラベルやアプリ名の重複はここで `ImproperlyConfigured` として弾かれます。 2. **Phase 2(モデルのインポート)**: 各アプリの `import_models()` でモデルモジュールを読み込み、モデルクラスをレジストリに登録します。`models_ready` が立つまで、モデルの参照は安全ではありません。 3. **Phase 3(`ready()` の実行)**: 各 `AppConfig.ready()` を呼びます。シグナルの接続やシステムチェックの登録など、「全モデルが出そろった後でやりたい起動処理」をアプリがここに書きます。 フェーズを分けているのは、**初期化に順序があるから**です。 「アプリを全部認識してからモデルを読む」「モデルを全部読んでから `ready()` を呼ぶ」という段取りがあるからこそ、`ready()` の中で他アプリのモデルを安全に参照できます。 かつての Django は「モデルのインポートの副作用」に初期化を依存させていましたが、明示的なアプリレジストリ(`AppConfig`/`apps.populate`)へ刷新され、この順序が保証されるようになりました。 ```{figure} /figures/fig-d1-apps-populate.jpg :name: fig-d1-apps-populate :align: center :width: 100% 図1-1 apps.populate の3フェーズ ``` Phase 1(`apps_ready`)→ Phase 2(`models_ready`)→ Phase 3(`ready()`)の順に初期化が進み、各フェーズの完了でフラグが立ちます。この順序保証によって、Phase 3 で他アプリのモデルを安全に参照できます。 ```{note} docstring の "It is thread-safe and idempotent, but not reentrant" は実装にそのまま現れています。 - **idempotent(冪等)**: 冒頭の `if self.ready: return` で、二度目以降の呼び出しは即座に戻ります。`setup()` が複数経路から呼ばれても初期化は一度きりです。 - **thread-safe(スレッド安全)**: `with self._lock:` で囲み、WSGI callable を初期化する前にスレッドを作るサーバでも二重初期化しません。 - **not reentrant(再入不可)**: `self.loading` 中に再び `populate()` が呼ばれると `RuntimeError("populate() isn't reentrant")`。`ready()` の中からモデルのインポートを誘発して `populate()` を再入させる、といった事故を防ぎます。 ``` ```{important} `populate()` はプロセスの生存期間中に一度だけ完了します。 Gunicorn のワーカーが `fork` される前にこの処理が走るため、各ワーカーは初期化済みの状態で起動します。 裏を返せば、**設定やモデル定義の変更はプロセスを再起動しないと反映されません**。「`settings.py` を変えたのに効かない」の多くは、ワーカーの再起動忘れです。 ``` この起動シーケンスを図にすると次のようになります。 ```{d2} :caption: 図1-2 get_wsgi_application から apps.populate までの起動シーケンス :name: fig-d1-startup :align: center :width: 100% direction: down wsgi: "wsgi.py\napplication = get_wsgi_application()" { style.fill: "#f1f5f9"; style.stroke: "#64748b" } gwa: "get_wsgi_application()" { style.fill: "#fffbeb"; style.stroke: "#d97706" a1: "django.setup(set_prefix=False)" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } a2: "WSGIHandler()" { style.fill: "#dcfce7"; style.stroke: "#16a34a" } a1 -> a2 } populate: "apps.populate(INSTALLED_APPS)" { style.fill: "#eff6ff"; style.stroke: "#2563eb" p1: "Phase 1\nAppConfig 生成・app import" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } p2: "Phase 2\nimport_models()" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } p3: "Phase 3\nAppConfig.ready()" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } p1 -> p2 -> p3 } wsgi -> gwa.a1 gwa.a1 -> populate.p1: "configure_logging 後" populate.p3 -> gwa.a2: "ready 完了" ``` ## 1.4 WSGIHandler の骨格 `get_wsgi_application()` が返す `WSGIHandler` のインスタンスこそが、Django の WSGI アプリケーション本体です。 ```{code-block} python :linenos: # django/core/handlers/wsgi.py — Django 5.2 class WSGIHandler(base.BaseHandler): request_class = WSGIRequest def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.load_middleware() ``` 短いですが、重要な情報が二つ詰まっています。 - **`request_class = WSGIRequest`**: このハンドラがリクエストを表現するのに使うクラスです。`environ` 辞書を受け取って Django の `HttpRequest` に仕立てます(本章末で受け渡しだけ確認し、解剖は次章(第2章)「HttpRequest と HttpResponse の解剖」)。 - **`__init__` で `self.load_middleware()` を呼ぶ**: 親クラス `BaseHandler` の `load_middleware()` を呼び、`settings.MIDDLEWARE` からミドルウェアチェーンを構築します。これは**インスタンス生成時に一度だけ**実行されます。つまりミドルウェアの連結はリクエストごとではなく、サーバ起動時に済ませてあるのです。 `WSGIHandler` が継承している `BaseHandler` は、WSGI と ASGI に**共通する処理**を集めた基底クラスです。 ```{code-block} python :linenos: # django/core/handlers/base.py — Django 5.2 class BaseHandler: _view_middleware = None _template_response_middleware = None _exception_middleware = None _middleware_chain = None def load_middleware(self, is_async=False): ... def get_response(self, request): ... ``` `load_middleware()` がミドルウェアを連結して `_middleware_chain` を作り、`get_response()` がそのチェーンを実行します。 この二つの中身は第4章「ミドルウェアチェーンの構築と実行」で詳しく追います。 ここでは「`__init__` がチェーンを組み立て、`__call__` がそれを呼ぶ」という骨格だけ押さえておきましょう。 `django.setup()`(アプリ初期化)も `load_middleware()`(チェーン構築)も、**起動時に前払いし、リクエスト時は使い回す**。この設計は本書を通して何度も再会します。 図1-3は、その「前払い」と「使い回し」の対応関係を整理したものです。 左列が起動時に一度だけ走る前払い、右列がリクエストごとに使う処理で、各前払いが対応する使い回し先へつながっています。 ```{d2} :caption: 図1-3 起動時に前払いした準備を、リクエストごとに使い回す対応関係 :name: fig-d1-prepay-reuse :align: center :width: 100% direction: right prepay: "起動時に一度だけ(前払い)" { style.fill: "#fffbeb"; style.stroke: "#d97706" setup: "django.setup()\napps.populate でアプリ初期化" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } middleware: "load_middleware()\nミドルウェアチェーンを構築" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } handler: "WSGIHandler()\nWSGI callable を生成" { style.fill: "#dcfce7"; style.stroke: "#16a34a" } } reuse: "リクエストごと(使い回す)" { style.fill: "#eff6ff"; style.stroke: "#2563eb" call: "WSGIHandler.__call__\n1 リクエストを処理" { style.fill: "#dcfce7"; style.stroke: "#16a34a" } get_response: "get_response()\n構築済みチェーンを実行" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } registry: "アプリレジストリ参照\nモデル・設定を使う" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } } prepay.handler -> reuse.call: "生成済みを呼ぶ" prepay.middleware -> reuse.get_response: "構築済みチェーンを回す" prepay.setup -> reuse.registry: "初期化済みを参照" ``` ## 1.5 `__call__` がリクエストの一生を回す WSGI callable の本体は `__call__` メソッドです。 Vol.1 で関数として書いた `application(environ, start_response)` を、Django はクラスの `__call__` として実装しています。 ```{code-block} python :linenos: # django/core/handlers/wsgi.py — Django 5.2 def __call__(self, environ, start_response): set_script_prefix(get_script_name(environ)) signals.request_started.send(sender=self.__class__, environ=environ) request = self.request_class(environ) response = self.get_response(request) response._handler_class = self.__class__ status = "%d %s" % (response.status_code, response.reason_phrase) response_headers = [ *response.items(), *(("Set-Cookie", c.output(header="")) for c in response.cookies.values()), ] start_response(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, response.block_size ) return response ``` リクエストが届くたびに、この `__call__` が一回呼ばれます。 処理を順に読み解きましょう。 1. **`set_script_prefix(get_script_name(environ))`**: リクエストごとにスクリプトプレフィックスを設定します。`get_wsgi_application()` で `set_prefix=False` にしていたのは、ここでリクエスト単位に設定するためです。 2. **`signals.request_started.send(...)`**: 「リクエスト処理が始まった」というシグナルを送ります(WHY は後述)。 3. **`request = self.request_class(environ)`**: `environ` 辞書を `WSGIRequest` オブジェクトに変換します。生の辞書アクセスが `request.method` や `request.path` といった属性アクセスに化けるのはここです。 4. **`response = self.get_response(request)`**: ミドルウェアチェーンと URL 解決とビューを通過させ、`HttpResponse` を得ます。Django の機能の大半はこの一行の内側にあります(第3章「URL 解決の全行程」、第4章「ミドルウェアチェーンの構築と実行」、第5章「ビューの呼ばれ方」)。 5. **ステータスとヘッダーの組み立て**: `response.status_code` と `response.reason_phrase` から `"200 OK"` のようなステータス行を作り、ヘッダーのリストを組み立てます。ここで `Set-Cookie` ヘッダーを**クッキーの数だけ個別に**展開しているのが目を引きます。Vol.1 で「ヘッダーは名前が重複しうる」と学んだことの実例です。 6. **`start_response(status, response_headers)`**: WSGI の約束ごとに従い、ステータスとヘッダーをサーバへ渡します。 7. **`wsgi.file_wrapper` の分岐**: レスポンスがファイル送信なら、サーバが提供する `wsgi.file_wrapper` を使って効率的に送ります(次節で開きます)。 8. **`return response`**: バイト列のイテラブルを返します。 ### 1.5.1 request_started シグナルは何のためか 2番目のステップ `signals.request_started.send(...)` は、Vol.1 で自作したサーバには無かった仕掛けです。 何がこのシグナルを待ち受けているのか、`django.db` のソースに答えがあります。 ```{code-block} python :linenos: # django/db/__init__.py — Django 5.2 def reset_queries(**kwargs): for conn in connections.all(initialized_only=True): conn.queries_log.clear() signals.request_started.connect(reset_queries) def close_old_connections(**kwargs): for conn in connections.all(initialized_only=True): conn.close_if_unusable_or_obsolete() signals.request_started.connect(close_old_connections) signals.request_finished.connect(close_old_connections) ``` `request_started` には **`reset_queries`(クエリログのクリア)** と **`close_old_connections`(古い DB 接続の回収)** が接続されています。 `close_old_connections` は `request_finished`(リクエスト終了)にも接続されています。 つまり Django は「リクエストの境界で DB 接続を点検し、寿命切れの接続を捨てる」段取りを、シグナル経由で組み込んでいるのです。 `CONN_MAX_AGE` で接続を使い回す運用でも、古くなった接続がリクエストをまたいで腐らないのはこの仕掛けのおかげです。 自作サーバに無かった「フレームワークが面倒を見てくれているもの」の一例です。 この `__call__` の流れを図にすると次のようになります。 ```{d2} :caption: 図1-4 WSGIHandler.\_\_call\_\_ の処理シーケンス :name: fig-d1-call :align: center :width: 100% direction: down server: "WSGI サーバ\n(Gunicorn など)" { style.fill: "#f1f5f9"; style.stroke: "#64748b" } call: "WSGIHandler.__call__(environ, start_response)" { style.fill: "#fffbeb"; style.stroke: "#d97706" s1: "① set_script_prefix" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } s2: "② request_started\nreset_queries / close_old_connections" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } s3: "③ WSGIRequest(environ)" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } s4: "④ get_response(request)\nミドルウェア→URL解決→ビュー" { style.fill: "#dcfce7"; style.stroke: "#16a34a" } s5: "⑤ status / headers 組み立て\nSet-Cookie を個別展開" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } s6: "⑥ start_response" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } s7: "⑦ wsgi.file_wrapper 分岐" { style.fill: "#ffffff"; style.stroke: "#9ca3af" } s1 -> s2 -> s3 -> s4 -> s5 -> s6 -> s7 } server -> call.s1: "environ, start_response" call.s7 -> server: "バイト列イテラブルを return" ``` ## 1.6 wsgi.file_wrapper:応答側の最適化 `__call__` の末尾の分岐を、もう少し開きましょう。 ```{code-block} python :linenos: # django/core/handlers/wsgi.py — Django 5.2(__call__ 末尾の再掲) 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, response.block_size ) return response ``` この分岐が成立する条件は二つです。 - **`response.file_to_stream` が `None` でない**: レスポンスがファイルを送るもの(`FileResponse`)であること。`FileResponse` は送信対象のファイルオブジェクトを `file_to_stream` 属性に持ちます。 - **`environ["wsgi.file_wrapper"]` が存在する**: WSGI サーバが `wsgi.file_wrapper` を提供していること。 両方そろうと、Django は `response.file_to_stream`(ファイル)と `response.block_size`(読み出しブロックサイズ)を `wsgi.file_wrapper` に渡し、その戻り値をレスポンスとして返します。 なぜわざわざサーバの機能に委ねるのでしょうか。 `wsgi.file_wrapper` は PEP 3333(WSGI 仕様)がサーバ側に求める**オプション機能**で、「ファイルを送るならサーバの効率的な手段を使ってよい」という約束ごとです。 多くのサーバは内部で `sendfile(2)` のような**ゼロコピー送信**(カーネル内でファイルからソケットへ直接転送し、アプリのメモリを経由しない)を使います。 Django がバイト列を一塊ずつ `read()` してイテレートするより、サーバに丸投げした方が速いのです。 「アプリは何を送るかだけ決め、どう送るかはサーバに任せる」。WSGI の疎結合が応答側にも効いている好例です。 ```{note} `wsgi.file_wrapper` が無いサーバ(提供は任意)でも壊れません。 その場合 `FileResponse` 自身がイテラブルとして振る舞い、`block_size` ごとに読み出して送ります。 「あれば使う、無ければ自前で送る」という安全な分岐になっています。 ``` ## 1.7 environ はどこで request になるのか `__call__` の3番目のステップ `self.request_class(environ)` を、受け渡しの部分だけ覗いておきましょう。 `request_class` は `WSGIRequest` でした。 ```{code-block} python :linenos: # django/core/handlers/wsgi.py — Django 5.2 class WSGIRequest(HttpRequest): def __init__(self, environ): script_name = get_script_name(environ) path_info = get_path_info(environ) or "/" self.environ = environ self.path_info = path_info self.path = "%s/%s" % (script_name.rstrip("/"), path_info.replace("/", "", 1)) self.META = environ # ...(中略:content_type の解析・本文ストリームの準備)... self.method = environ["REQUEST_METHOD"].upper() ``` ここで起きているのは、Vol.1 で見た `environ` 辞書の各キーを、Django が使いやすい属性へ写し替える作業です。 - `environ["REQUEST_METHOD"]` → `request.method` - パス情報 → `request.path` と `request.path_info` - `environ` 辞書そのもの → `request.META` 注目すべきは、`self.META = environ` と**辞書をそのまま参照している**点です。 Django の `request.META` は、WSGI の `environ` の別名にすぎません。 `request.META["HTTP_USER_AGENT"]` のようにアクセスしているのは、Vol.1 で見た `environ` のキーそのものなのです。 ```{note} `# ...(中略)...` で畳んだ `content_type` の解析やボディストリームの準備、そして `request.GET`、`request.POST`、`request.body`、`request.COOKIES` がどう作られるかは、次章(第2章)「HttpRequest と HttpResponse の解剖」で扱います。 本章は「`environ` を `WSGIRequest` に渡すところ」までが守備範囲です。 ``` ## 1.8 自作 MiniHandler で骨格を再現する ここまで読んだ「起動時に一度だけ前払いし、リクエストごとに request を作ってチェーンを回す」という骨格を、最小のコードで再現してみましょう。 本物の `WSGIHandler` の構造だけを取り出した教材です。 ```{literalinclude} ../../../src/docs-framework/part2/d1_mini_handler.py :language: python :caption: d1_mini_handler.py:WSGIHandler の骨格の最小再現 :name: code-d1-mini-handler ``` 本物との対応は次のとおりです。 - **`__init__` の `self.setup()` と `build_chain()`**: 起動時に一度だけ走ります。本物の `django.setup()`(アプリ初期化)と `load_middleware()`(チェーン構築)にあたります。 - **`build_chain` の `reversed(middleware)`**: ミドルウェアを内側から畳んで一本の callable にします。「先頭ほど外側」という玉ねぎ構造(第4章「ミドルウェアチェーンの構築と実行」で本物を読みます)の最小形です。 ```{figure} /figures/fig-d1-mini-onion.jpg :name: fig-d1-mini-onion :align: center :width: 100% 図1-5 reversed で畳む玉ねぎ構造とリクエストの往復 ``` `settings.MIDDLEWARE` の並び順(M1・M2・M3)が `reversed` で M1 最外・M3 最内の入れ子になります。リクエストは外側から内側へ降り、ビューを通って逆順に外へ戻ります(下り M1→M3、上り M3→M1)。 - **`__call__` の流れ**: ① プレフィックス相当の前処理 → ② `environ` を `MiniRequest` へ → ③ `request_started` 相当のフック → ④ チェーン実行 → ⑤⑥ status/headers 組み立てと `start_response` → ⑦ `wsgi.file_wrapper` の分岐、と本物の順序をなぞります。 - **`MiniRequest` の `self.META = environ`**: 本物と同じく `environ` を別名参照するだけ。GET/POST/body の解剖は持ちません(第2章へ送る線引きも本物と揃えています)。 このハンドラは正真正銘の WSGI callable なので、`wsgiref` でそのまま動きます。 本物の `WSGIHandler.__call__` と並べて読むと、「どこまでを起動時に済ませ、どこからをリクエストごとにやるか」の線引きが体感できるはずです。 ## 1.9 トラブルシューティングの観点 入口と起動でつまずくときの典型と、ソースを知っているとどう効くかを挙げておきます。 ```{table} 表1-1 起動と入口まわりの典型的なつまずき :name: tbl-d1-troubles :widths: auto | 症状 | よくある原因 | 確認の勘所 | |---|---|---| | `ImproperlyConfigured: settings are not configured` | `DJANGO_SETTINGS_MODULE` 未設定 | `wsgi.py` の `setdefault` が効いているか、外側で環境変数を渡していないか | | `gunicorn myproject.wsgi:application` で `Failed to find application` | モジュールパスか `application` 名の誤り | `wsgi.py` に `application = get_wsgi_application()` があるか | | 設定やモデルの変更が反映されない | `apps.populate()` は冪等=プロセス起動時に一度きり | ワーカーの再起動(リロード)が必要 | | `RuntimeError: populate() isn't reentrant` | `ready()` 等から初期化を再入させた | `AppConfig.ready()` の中でモデルインポートやマイグレーションを誘発していないか | | `AppRegistryNotReady` | `populate()` 完了前にモデルを参照 | モジュールトップでのモデルインポートを `ready()` 内へ移す | | 大きなファイル配信でメモリを食う | `wsgi.file_wrapper` 非対応 or `FileResponse` 未使用 | `FileResponse` を返しているか、サーバが `wsgi.file_wrapper` を提供するか | ``` いずれも「`apps.populate()` は一度しか初期化しない/フェーズに順序がある/`environ` をそのまま `META` にしている」という**ソースの事実**を知っていれば、原因の層を素早く切り分けられます。 ここまで Django の入口を、`get_wsgi_application()` の薄いラッパから `django.setup()`→`apps.populate()` の3フェーズ起動、そして `WSGIHandler.__call__` がリクエストの一生を回し `wsgi.file_wrapper` で応答を最適化するまで、実物のソースで追いました。 最後に `environ` が `WSGIRequest` へ渡される瞬間を確認し、その解剖を次章へ送りました。 次章(第2章)「HttpRequest と HttpResponse の解剖」では、その `WSGIRequest` と、ビューが返す `HttpResponse`、すなわちリクエストとレスポンスの2オブジェクトが、生の `environ`/バイト列との間で**何を肩代わりしている**のかを解剖します。