1. 本書の対象読者とゴール

1.1. Django や FastAPI でアプリは作れるが、内部の流れは曖昧な人へ

あなたは Django の manage.py runserver を叩けばローカルでアプリが動くことを知っています。 FastAPI なら uvicorn main:app --reload と打てば、ブラウザに JSON が返ってくることも知っています。 チュートリアルどおりにルーティングを書き、テンプレートを配置し、データベースに接続すれば、たしかに「動く」アプリケーションは完成します。

しかし、こんな問いを投げかけられたらどうでしょうか。

「ブラウザがリクエストを送ってから、あなたのビュー関数が呼ばれるまでに、何が起きていますか?」

この問いに対して、自信を持って答えられる方は意外と少ないのではないでしょうか。 ブラウザが HTTP リクエストを組み立て、TCP コネクションを通じてバイト列がサーバに届き、サーバプロセスがそのバイト列をパースし、 WSGI や ASGI といったインタフェースを通じてフレームワークに渡され、ミドルウェアチェーンを順に通過し、URL ルーティングによってビュー関数が特定され、ようやくあなたの書いたコードが実行される—— この一連の流れは、フレームワークが丁寧に隠してくれているからこそ、普段は意識する必要がありません。

本書が対象とするのは、まさにこの「隠された部分」に漠然とした不安を感じている開発者の方です。

注釈

本書の想定読者は次のような方です:

  • Django や FastAPI でアプリを作ったことはあるが、フレームワークの内部がよくわからない

  • LLM が生成したコードを動かせているが、トラブルが起きると原因を追えない

  • HTTP や WSGI・ASGI といった言葉は知っているが、自分の言葉では説明できない

いずれかひとつでも心当たりがあれば、本書はあなたのために書かれたものです。

要するに、「なんとなく動く」から「なぜ動くかわかる」へのステップアップを目指している Python 開発者の方が対象です。

なお、Python の基本的な文法——関数、クラス、デコレータの使い方——は理解していることを前提としています。 一方で、ネットワークやサーバの知識は必要ありません。 TCP ソケットとは何か、HTTP リクエストはどんな構造をしているのかといった話題は、すべて本書の中でゼロから積み上げていきます。

1.2. なぜ内部理解が必要なのか

「フレームワークを使えば動くのだから、内部を知らなくてもいいのでは?」

これは自然な疑問です。 実際、多くのプロジェクトでは内部構造を意識しなくてもアプリケーションを完成させることができます。 では、なぜ本書はわざわざ「内部を理解しよう」と訴えるのでしょうか。

理由はシンプルです。うまくいっている間は内部を知らなくても困りません。困るのは、うまくいかなくなったときです。そしてそのタイミングは、たいてい最もまずい瞬間——本番障害の最中や締め切り直前——にやってきます。

警告

LLM の時代になって、この問題はさらに深刻になりました。 ChatGPT や GitHub Copilot にコードを書かせれば、驚くほど短時間で動くものができあがります。 しかし、生成されたコードが「なぜその書き方になっているのか」を説明できないまま本番に投入するのは、地図を持たずに知らない街を運転するようなものです。 晴れた日にまっすぐな道を走っている間は問題ありませんが、霧が出て道に迷ったとき、自分がどこにいるのかすらわからなくなります。

たとえば、本番環境でアプリケーションが突然 502 Bad Gateway を返すようになったとします。 ログを見ると、Gunicorn のワーカーがタイムアウトしています。 しかし、そもそも Gunicorn がどういう仕組みでリクエストを処理しているのかがわからなければ、タイムアウトの設定を変えるべきなのか、ワーカー数を増やすべきなのか、それともアプリケーション側のコードに問題があるのか、判断がつきません。

あるいは、Django アプリに WebSocket を導入しようとして、ASGI 対応が必要だと言われたとします。 ASGI とは何なのか、なぜ WSGI では WebSocket が扱えないのか、その理由がわからなければ、設定をコピペしてもどこかで行き詰まります。

内部を理解するということは、フレームワークのソースコードをすべて暗記するということではありません。 「リクエストがどこを通って、どう処理されて、レスポンスになるのか」という流れを、自分の頭の中に地図として持つことです。 この地図があれば、エラーが発生したときに「この段階で問題が起きているはずだ」と仮説を立てられます。仮説が立てば、次の手が見えます。

重要

本書が目指すのは、あなたの頭の中にその「地図」を描くことです。 地図を持っている開発者は、エラーが起きたときに「自分がどこにいるのか」を把握できます。 そこから原因の絞り込みが始まります。

1.3. トラブルシューティングに強い開発者とは何か

経験豊富な開発者がトラブルに直面したとき、彼らの行動を観察していると、ある共通のパターンがあることに気づきます。 闇雲にコードを修正したり、Stack Overflow の回答を片っ端から試したりしません。 まず、問題がどのレイヤーで起きているのかを切り分けます

「これはネットワークの問題か、サーバの問題か、アプリケーションの問題か」——この最初の問いに答えられるだけで、調査範囲は劇的に狭まります。

  • ネットワークの問題なら curl でリクエストを送って確認する

  • サーバの問題ならプロセスやログを調べる

  • アプリケーションの問題ならデバッガやプリントデバッグで追いかける

レイヤーを意識できる開発者は、このように的確に手を打てます。

逆に、レイヤーの区別がつかない開発者は、すべてが一枚岩に見えてしまいます。 「動かない」としか認識できず、どこから手をつければいいのかわかりません。 知識がないから見えないのであって、見る力がないわけではありません。

Tip

ひとつひとつの概念が、見える世界を広げてくれます。

  • TCP ソケットという概念を知っていれば → HTTP サーバがその上に成り立っていることがわかる

  • WSGI という仕様を知っていれば → Gunicorn とアプリケーションの境界がどこにあるのかがわかる

概念の積み重ねが、トラブルシューティングの「視野」を広げます。

本書を読み終えたとき、あなたは次のことができるようになります。

  • TCP ソケットから HTTP リクエストが届くまでの流れを自分の言葉で語れる

  • WSGI と ASGI の違いを仕様レベルで説明できる

  • Django や FastAPI の内部でリクエストがビューに届くまでの経路を追える

  • Gunicorn・Uvicorn・Nginx それぞれの役割と責務の境界を把握できる

  • トラブルが発生したときに問題のレイヤーを切り分けて仮説を立てられる

これらは一度身につければ、フレームワークやツールが変わっても応用がきく知識です。Django から FastAPI に移行しても、まったく新しいフレームワークが登場しても、HTTP と WSGI/ASGI という土台は変わらないからです。

次節では、ブラウザに URL を入力してからレスポンスが返るまでの全体像を一気に俯瞰します。細部に踏み込む前に地図全体を眺めておくことで、各章で学ぶことの位置づけが見えやすくなります。

1.4. ブラウザからレスポンスまでの全体像

1.4.1. URL を開いたときに起きることの俯瞰

ブラウザのアドレスバーに https://example.com/users/42/ と入力して Enter を押します。 ほんの一瞬でページが表示されます。 この「一瞬」の裏側では、驚くほど多くのステップが順番に実行されています。

細かい仕組みは2 章HTTP は何をやりとりしているのか)以降で掘り下げますので、ここではまず「どんな登場人物がいて、どの順番でバトンを渡しているのか」を頭に入れてください。 全体の流れを先に知っておくことで、各章で学ぶ個別の技術が地図のどこに位置するのかがわかるようになります。

大まかな流れは次のとおりです。

ブラウザ
  ↓  DNS 解決・TCP 接続・HTTP リクエスト送信
Web サーバ(Nginx など)
  ↓  リバースプロキシとして転送
アプリケーションサーバ(Gunicorn / Uvicorn)
  ↓  WSGI / ASGI インタフェースで呼び出し
フレームワーク(Django / FastAPI)
  ↓  ミドルウェア → URL ルーティング
ビュー関数
  ↓  ビジネスロジック・DB / 外部 API 問い合わせ
レスポンス生成
  ↓  逆順にバトンを返していく
ブラウザ
  ↓  HTML 描画・表示
diagram

この図に登場する各コンポーネントを、ひとつずつ見ていきましょう。

1.4.2. ブラウザ

すべてはブラウザから始まります。

アドレスバーに URL を入力すると、ブラウザはまず DNS(Domain Name System) に問い合わせて、ドメイン名(example.com)を IP アドレス(たとえば 93.184.216.34)に変換します。 人間にとって読みやすい名前を、コンピュータが通信できる住所に翻訳する作業です。

IP アドレスがわかると、次にブラウザはその IP アドレスのポート番号 443(HTTPS の場合)または 80(HTTP の場合)に向けて TCP コネクションを確立します。 TCP は「信頼性のある通信路」を確保するためのプロトコルで、データが順番どおりに欠損なく届くことを保証してくれます。 この接続確立の過程は「3ウェイハンドシェイク」と呼ばれ、クライアントとサーバが互いに通信の準備ができていることを確認し合います。

HTTPS の場合は、TCP コネクションの上にさらに TLS ハンドシェイクが行われ、暗号化された通信路が構築されます。

こうして通信路が確保されると、ブラウザはようやく HTTP リクエストを送信します。 ブラウザは単に URL を送っているのではなく、厳密に定められた形式のテキストデータを送っているのです。

1.4.3. HTTP リクエスト

ブラウザが送信する HTTP リクエストは、人間にも読めるテキスト形式のプロトコルです。 実際のリクエストは、たとえば次のような見た目をしています。

GET /users/42/ HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Connection: keep-alive

1行目はリクエストラインと呼ばれ、メソッド(GET)、パス(/users/42/)、プロトコルバージョン(HTTP/1.1)の3つの要素で構成されています。 2行目以降はヘッダーで、リクエストに関する付加情報をキーと値のペアで伝えます。

ヘッダー名

役割

Host

どのドメインに対するリクエストかを示す

User-Agent

ブラウザの種類や OS 情報を伝える

Accept

ブラウザが受け取れるコンテンツの形式を伝える

Accept-Language

優先する言語を伝える

POST リクエストの場合は、ヘッダーの後に空行を挟んでボディ(本文)が続きます。 フォームの入力内容や JSON データがここに格納されます。

注釈

重要なのは、HTTP リクエストが単なるテキストの塊であるという点です。 魔法のような特別なバイナリプロトコルではありません。 この事実は2 章HTTP は何をやりとりしているのか)で実際にソケットを使って HTTP リクエストを送受信するときに、実感として理解できるはずです。

1.4.4. Web サーバ / アプリケーションサーバ

HTTP リクエストがネットワークを経由してサーバ側に届くと、まず受け取るのはWeb サーバです。 本番環境では、Nginx や Caddy といったソフトウェアがこの役割を担います。

Web サーバの主な仕事は次の2つです。

  1. CSS や画像などの静的ファイルを直接返す — Python のアプリケーションサーバに比べて桁違いに高速です

  2. アプリケーションの処理が必要なリクエストをアプリケーションサーバに転送する(リバースプロキシ)

アプリケーションサーバは、GunicornUvicorn といったソフトウェアです。

サーバ名

対応インタフェース

特徴

Gunicorn

WSGI

複数ワーカープロセスで並行処理

uWSGI

WSGI

高機能・多設定オプション

Uvicorn

ASGI

非同期 I/O ベース、高効率

注意

開発中に python manage.py runserveruvicorn main:app --reload で起動するサーバは、これらの本番用サーバの簡易版です。 開発には便利ですが、本番環境で使うには性能やセキュリティの面で不十分です。その理由はVol.3「「Webサーバ」という言葉の混乱を解く」で詳しく解説します。

1.4.5. WSGI / ASGI

アプリケーションサーバとフレームワークの間には、インタフェース仕様が存在します。 それが WSGI(Web Server Gateway Interface)ASGI(Asynchronous Server Gateway Interface) です。

なぜインタフェース仕様が必要なのでしょうか。 もし Gunicorn が Django の内部構造を直接知っていなければ動かないとしたら、Gunicorn は Django 専用のサーバになってしまいます。 逆に、Django が Gunicorn の API を直接呼んでいたら、Django は Gunicorn なしでは動きません。

WSGI は、この結合を断ち切るための取り決めです。

コラム: WSGI という「共通言語」

WSGI を一言で例えるなら「コンセント規格」のようなものです。 日本のコンセントの形が統一されているから、どのメーカーのプラグでも差し込めます。 同じように、「サーバはこういう形式でアプリケーションを呼び出す。アプリケーションはこういう形式でレスポンスを返す」という約束事さえ守れば、サーバとアプリケーションは自由に組み合わせられます。

Gunicorn の上で Django を動かすことも、Flask を動かすことも、Bottle を動かすことも、すべて WSGI という共通規格があるからこそ実現できています。

ASGI は、WSGI を非同期処理や WebSocket に対応させるために策定された新しい仕様です。 WSGI は「1リクエストに対して1レスポンスを同期的に返す」というモデルしか扱えないため、WebSocket のような双方向通信や、非同期 I/O を活かした並行処理には対応できません。 ASGI はそれらを可能にする拡張仕様です。

WSGI の詳細は4 章WSGI が生まれた背景)で、ASGI の詳細はVol.2「なぜ ASGI が必要になったのか」で、それぞれ仕様を読み解きながら実装します。

1.4.6. フレームワーク

WSGI や ASGI を通じてリクエスト情報を受け取ったフレームワークは、まずミドルウェアを順番に通します。

ミドルウェアとは、リクエストがビュー関数に届く前(および、レスポンスがクライアントに返される前)に実行される処理のことです。 認証の確認、CSRF トークンの検証、セッション情報の読み込み、リクエストログの記録など、アプリケーション全体に共通する横断的な処理がここで行われます。

ミドルウェアを通過すると、次は URL ルーティングです。 リクエストされたパス(/users/42/)を見て、どのビュー関数を呼び出すべきかを決定します。

  • Django であれば urlpatterns に定義されたパターンと照合します

  • FastAPI であれば @app.get("/users/{user_id}") のようなデコレータで登録されたルートと照合します

URL パターンが一致すると、パスに含まれるパラメータ(この例では 42)が抽出され、ビュー関数に引数として渡されます。 一致するパターンがなければ、フレームワークは 404 Not Found レスポンスを生成します。

1.4.7. ビュー

ビュー関数は、開発者であるあなたが書くコードの中心です。 リクエストの内容を受け取り、必要な処理を行い、レスポンスを生成して返します。

Django であれば、次のようなコードです。

from django.http import JsonResponse
from .models import User

def user_detail(request, user_id):
    user = User.objects.get(id=user_id)
    return JsonResponse({
        "id": user.id,
        "name": user.name,
        "email": user.email,
    })

FastAPI であれば、次のようになります。

from fastapi import FastAPI
from .models import User
from .database import get_db

app = FastAPI()

@app.get("/users/{user_id}")
async def user_detail(user_id: int):
    db = get_db()
    user = db.query(User).filter(User.id == user_id).first()
    return {"id": user.id, "name": user.name, "email": user.email}

どちらのコードも、やっていることの本質は同じです。 パスから受け取った user_id をもとにデータベースからユーザ情報を取得し、辞書(JSON)として返しています。 フレームワークの文法は異なりますが、「リクエストを受け取り、処理して、レスポンスを返す」という構造は変わりません。

1.4.8. DB や外部 API

ビュー関数の中で行われる処理のうち、多くの場合で最も時間がかかるのがデータベースへの問い合わせ外部 API の呼び出しです。

先ほどの例では User.objects.get(id=user_id) がデータベースに SQL クエリを発行しています。 この処理は、Python のコードがデータベースサーバに対して TCP 接続を通じてクエリを送り、結果が返ってくるまで待つという I/O 操作です。 CPU が計算しているわけではなく、ネットワーク越しにデータが届くのを待っているだけの時間が大半を占めます。

Tip

この「待ち時間」の扱い方が、WSGI と ASGI の大きな違いにつながります。

  • WSGI(同期モデル): データベースの応答を待っている間、そのワーカーは他のリクエストを処理できない

  • ASGI(非同期モデル): 待っている間に他のリクエストの処理を進めることができる

Vol.3「なぜ Web 開発で並行処理が重要なのか」で解説する並行処理モデルの話に直結するポイントです。

外部 API の呼び出し(決済サービス、メール送信サービス、他のマイクロサービスなど)も同様に I/O バウンドな処理であり、同じ考え方が当てはまります。

1.4.9. レスポンス返却

ビュー関数がレスポンスを生成すると、バトンは来た道を逆に辿って戻っていきます。

まず、フレームワークのミドルウェアが逆順に実行されます。 レスポンスヘッダーの追加、Cookie の設定、レスポンスログの記録などがここで行われます。

次に、フレームワークは WSGI または ASGI の仕様に従って、レスポンスをアプリケーションサーバに返します。 アプリケーションサーバは、受け取ったレスポンスを HTTP レスポンスの形式に整えてクライアントに送り返します。 本番環境でリバースプロキシを経由している場合は、Web サーバを経由してクライアントに届きます。

HTTP レスポンスもリクエストと同様にテキスト形式です。

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 52

{"id": 42, "name": "Taro Yamada", "email": "[email protected]"}

1行目の ステータスライン にはプロトコルバージョンとステータスコード(200 OK)が含まれ、続くヘッダーでレスポンスの属性を伝え、空行の後にボディが続きます。

ブラウザはこのレスポンスを受け取り、Content-Type に応じて処理を行います。 text/html であれば HTML をパースして画面を描画し、application/json であれば JSON として解釈します。

ここまでが、あなたがアドレスバーに URL を入力してから画面にデータが表示されるまでの全体像です。 本書では、この流れの各ステップを章ごとに深掘りしていきます。 次項では、この流れに登場するコンポーネント(役者)を一人ずつ取り上げ、それぞれの責務と境界を明確にします。

1.5. 役者を整理する

前項では、ブラウザに URL を入力してからレスポンスが返るまでの全体像を時系列で追いました。 本項では視点を変えて、この流れに登場する**コンポーネント(役者)**を一人ずつ取り上げ、それぞれの責務と境界を明確にします。

Web アプリケーションの世界では、ひとつのリクエストを処理するために複数のソフトウェアが連携しています。 しかし、それぞれの役割が曖昧なままだと、障害が起きたときに「誰の仕事でミスが起きたのか」を特定できません。 俳優の名前と役柄を知らずに映画を観ても筋が追えないように、コンポーネントの責務を知らずにシステムを運用しても、何が起きているのかわからないのです。

ここで整理する役者は次の6つです。

  1. クライアント

  2. リバースプロキシ

  3. Python の Web サーバ

  4. フレームワーク

  5. アプリケーションコード

  6. ミドルウェア

diagram

1.5.1. クライアント

クライアントとは、サーバに対してリクエストを送る側のことです。 最も身近なクライアントはブラウザですが、クライアントはブラウザだけではありません。

代表的なクライアントには次のものがあります。

クライアント

主な用途

ブラウザ(Chrome、Firefox など)

一般ユーザによる Web 閲覧

curl コマンド

デバッグ・動作確認

Python の requestshttpx

テストコードやバッチ処理

スマートフォンアプリ

バックエンド API との通信

JavaScript の fetch()

ブラウザからの非同期リクエスト

Tip

デバッグの場面では curl がブラウザより便利なことが多いです。 リクエストヘッダーを自由に設定でき、レスポンスの生のテキストをそのまま確認できるからです。 たとえば curl -v http://localhost:8000/users/42/ と叩くだけで、リクエスト・レスポンスの詳細が全て表示されます。

クライアントの責務はシンプルです。HTTP の仕様に従ってリクエストを組み立て、サーバに送信し、返ってきたレスポンスを受け取る——それだけです。クライアントは、サーバの内部で何が起きているかを知りませんし、知る必要もありません。クライアントにとってサーバは「リクエストを送ったらレスポンスが返ってくるブラックボックス」です。

本書では2 章HTTP は何をやりとりしているのか)で、Python の socket モジュールを使って最も原始的な HTTP クライアントを自作します。 そうすることで、ブラウザや curl が裏で何をしているのかを体感できます。

1.5.2. リバースプロキシ

本番環境の構成図を見ると、クライアントとアプリケーションサーバの間に NginxCaddy といったソフトウェアが挟まっていることがほとんどです。 これがリバースプロキシです。

「プロキシ」とは「代理」という意味です。 通常のプロキシ(フォワードプロキシ)がクライアント側の代理として動くのに対し、リバースプロキシはサーバ側の代理として動きます。 クライアントから見ると、リバースプロキシがあたかもサーバそのものであるかのように振る舞います。

リバースプロキシの責務は多岐にわたります。

  • 静的ファイルの配信: CSS・JavaScript・画像を直接ファイルシステムから返します。Python のアプリケーションサーバを経由させるより桁違いに高速です

  • リクエストの振り分け: パスやホスト名に応じてバックエンドを切り替えます。ロードバランシングにも使われます

  • TLS の終端: HTTPS の暗号化・復号化をリバースプロキシが担当し、アプリケーションサーバを暗号化処理から解放します

  • レスポンスの圧縮・バッファリング: インフラレベルの最適化を引き受けます

警告

開発環境ではリバースプロキシを使わないことが多いため、その存在を意識する機会は少ないかもしれません。 しかし、本番環境で 502 や 504 のエラーに遭遇したとき、それがリバースプロキシのレイヤーで起きているのかアプリケーションのレイヤーで起きているのかを区別できることは、トラブルシューティングにおいて決定的に重要です。 Vol.3「開発環境と本番環境は何が違うのか」で改めて詳しく取り上げます。

1.5.3. Python の Web サーバ

リバースプロキシから転送されたリクエスト(あるいは開発環境ではクライアントから直接届いたリクエスト)を受け取るのが、Python の Web サーバ(アプリケーションサーバ)です。 代表的なものとして GunicornuWSGIUvicorn があります。

このコンポーネントの責務は次のとおりです。

  1. ネットワークから届いた生のバイト列を受け取る

  2. HTTP リクエストとしてパースする

  3. WSGI または ASGI のインタフェースに従ってフレームワークを呼び出す

  4. フレームワークから返されたレスポンスを HTTP レスポンスのバイト列に変換して返す

Gunicorn は WSGI サーバの代表格です。 マスタープロセスが複数のワーカープロセスを管理し、各ワーカーがリクエストを処理するというプリフォーク(pre-fork)モデルを採用しています。 ワーカーの数を調整することで、サーバの並行処理能力を制御できます。

Uvicorn は ASGI サーバです。 Python の asyncio を活用した非同期 I/O ベースのサーバで、ひとつのプロセスで多数のリクエストを効率的に捌くことができます。 FastAPI や Django の ASGI モードで使われます。

重要

ここで重要なのは、Python の Web サーバはフレームワークの中身を知らないという点です。 Gunicorn は Django のモデルやビューの構造を知りません。 ただ「WSGI の仕様に従って呼び出せる callable」を受け取り、それを呼び出すだけです。 この疎結合こそが、サーバとフレームワークを自由に組み合わせられる理由です。

4 章WSGI が生まれた背景)とVol.2「なぜ ASGI が必要になったのか」で、この仕組みを仕様レベルで理解します。

1.5.4. フレームワーク

DjangoFastAPI といったフレームワークは、Web アプリケーション開発の中心的な存在です。 しかし、ここまでの説明でわかるように、フレームワークはリクエスト処理の流れ全体から見ると「中間の一区間」を担当しているに過ぎません。

フレームワークの責務は次のとおりです。

  1. WSGI / ASGI を通じて受け取ったリクエスト情報を開発者が扱いやすいオブジェクトに変換する

  2. 適切なビュー関数に届ける

  3. ビュー関数が返した結果を WSGI / ASGI の形式に戻して返す

この過程で、フレームワークは多くの便利な機能を提供してくれます。

  • URL パターンと関数のマッピング(ルーティング)

  • リクエストオブジェクトとレスポンスオブジェクトの抽象化

  • テンプレートエンジンとの連携

  • ORM(Object-Relational Mapping)によるデータベース操作

  • フォームやバリデーションの処理

  • 認証・認可の仕組み

フレームワーク

思想

特徴

Django

バッテリー同梱(batteries included)

Web 開発に必要な機能をほぼすべて内蔵

FastAPI

軽量 ASGI フレームワーク

型ヒントと Pydantic、自動 API ドキュメント生成

どちらのフレームワークも、その内部では前項で説明した WSGI / ASGI の仕様に従って動いています。 Vol.2「Django を WSGI 視点で見る」と Vol.2「FastAPI を ASGI 視点で見る」で、コードを追いながら確認します。

1.5.5. アプリケーションコード

アプリケーションコードとは、開発者であるあなたが書くコードのことです。 ビュー関数、モデル定義、シリアライザ、ビジネスロジック——これらがアプリケーションコードに該当します。

ここまで紹介してきた他の役者たち(リバースプロキシ、Python の Web サーバ、フレームワーク)は、基本的に既存のソフトウェアをそのまま使います。 設定はしますが、中身を書き換えることはほとんどありません。 それに対して、アプリケーションコードは完全にあなたの領域です。

フレームワークの中でアプリケーションコードが占める位置を正確に理解することは、とても大切です。 Django を例にとると、あなたが書く views.py の関数は、Django のリクエスト処理パイプラインの中のごく一部分です。 その関数が呼ばれる前に、Django は URL の解決、ミドルウェアの実行、リクエストオブジェクトの構築といった多くの前処理を行っています。 関数が値を返した後も、レスポンスオブジェクトの加工やミドルウェアの後処理が実行されます。

Tip

この認識があると、問題の切り分けが格段にしやすくなります。

  • ビュー関数にたどり着くにエラーが出ている → URL の設定やミドルウェアの問題

  • ビュー関数のでエラーが出ている → アプリケーションコードの問題

  • レスポンスは正しく返しているのにクライアントにエラーが出る → サーバやリバースプロキシの問題

1.5.6. ミドルウェア

ミドルウェアは、これまでに紹介した役者たちとは少し性質が異なります。 独立したソフトウェアではなく、フレームワークの中に組み込まれた処理の層です。 しかし、リクエスト処理の流れを理解するうえで欠かせない存在なので、独立した役者として取り上げます。

ミドルウェアの基本的な考え方は「玉ねぎの皮」に例えるとわかりやすいでしょう。 リクエストは玉ねぎの外側から内側に向かって各層を通過し、中心のビュー関数に到達します。 ビュー関数がレスポンスを返すと、今度は内側から外側に向かって各層を逆順に通過していきます。

リクエスト →  [ミドルウェアA] → [ミドルウェアB] → [ミドルウェアC] → ビュー
レスポンス ←  [ミドルウェアA] ← [ミドルウェアB] ← [ミドルウェアC] ←

Django のデフォルト設定には、セキュリティミドルウェア、セッションミドルウェア、認証ミドルウェア、CSRF ミドルウェアなどが含まれています。 これらは settings.pyMIDDLEWARE リストに定義されており、リストの上から順にリクエストを処理し、下から順にレスポンスを処理します。

FastAPI(Starlette)にもミドルウェアの仕組みがあり、CORS の設定やリクエストログの記録などに使われます。 ASGI ミドルウェアは WSGI ミドルウェアとは仕組みが異なりますが、「ビューの前後に横断的な処理を挟む」という発想は共通しています。

警告

ミドルウェアが重要なのは、ビュー関数が呼ばれる前にリクエストを書き換えたり、リクエストを拒否したりできるからです。 たとえば、認証ミドルウェアがリクエストのセッション情報を確認し、ログインしていないユーザのリクエストを 403 Forbidden で返す—— この処理は、あなたのビュー関数が実行される前に完了しています。 ビュー関数に処理が届かないのに「ビューにバグがあるのでは」と疑ってしまうのは、ミドルウェアの存在を意識できていない典型的な例です。

WSGI ミドルウェアの仕組みは5 章WSGI の上に何が必要になるのか)で、ASGI ミドルウェアの仕組みはVol.2「最小の ASGI HTTP アプリ」で、それぞれ自作しながら理解を深めます。


ここまでで、Web アプリケーションの世界に登場する主要な役者を紹介しました。 クライアント、リバースプロキシ、Python の Web サーバ、フレームワーク、アプリケーションコード、ミドルウェア—— この6つの役者が、それぞれの責務を果たしながらバトンを渡し合うことで、ひとつのリクエストが処理されています。

次項では、役者を把握した先にある実践的な力——「問題が起きたとき、どの層の仕事で失敗したのか」を特定する視点について考えます。

1.6. なぜ「どの層の責務か」が重要なのか

前項で6つの役者を紹介しました。 しかし、役者の名前と役割を知っているだけでは十分ではありません。 本当に力を発揮するのは、問題が起きたときに「どの役者の仕事で失敗したのか」を特定できる力です。

本項では、Web アプリケーション開発で誰もが遭遇する4つの場面——404エラー、500エラー、タイムアウト、ログの確認——を取り上げ、それぞれが「どの層で起きうるのか」を考えます。 同じエラーコードでも、発生した層が違えば原因はまったく異なり、対処法も変わります。

diagram

1.6.1. 404 はどこで発生するか

ブラウザに「404 Not Found」が表示されました。 これは「ページが見つからない」という意味ですが、どこで「見つからない」と判断されたのかによって、原因と対処は根本的に異なります。

1. リバースプロキシの層で 404 が返るケース

Nginx の設定で特定のパスだけをアプリケーションサーバに転送し、それ以外は静的ファイルとして処理するように構成している場合を考えてみましょう。 /api/ で始まるパスはアプリケーションサーバに転送するが、/images/logo.png は Nginx が直接配信する—— このような設定で静的ファイルが実際には存在しなければ、Nginx が 404 を返します。 このとき、アプリケーションサーバには一切リクエストが届いていません。 Django や FastAPI のログを見ても何も記録されていないのは当然です。

2. フレームワークの層で 404 が返るケース(最も一般的)

リクエストはアプリケーションサーバを経由してフレームワークに届いたものの、URL ルーティングの段階で一致するパターンが見つからなかった場合です。 Django であれば urlpatterns に該当するエントリがない、FastAPI であれば対応する @app.get()@app.post() が定義されていない、という状況です。 Django は独自の 404 ページを表示し、開発モードでは「試した URL パターンの一覧」まで教えてくれます。

3. アプリケーションコードの層で意図的に 404 を返すケース

URL パターンには一致したが、ビュー関数の中でデータベースを検索した結果、該当するレコードが存在しなかった場合です。 Django であれば get_object_or_404() を使うケース、FastAPI であれば raise HTTPException(status_code=404) を明示的に投げるケースがこれに当たります。

重要

同じ「404」でも、原因は「Nginx の設定ミス」「URL パターンの定義漏れ」「データベースにレコードが存在しない」とまったく異なります。 層の区別がつかなければ、Nginx の設定を見るべきなのに Django の urls.py を睨み続ける、あるいはデータベースの問題なのにルーティングの設定を疑う、という見当違いの調査に時間を費やしてしまいます。

1.6.2. 500 はどこで発生するか

「500 Internal Server Error」は、開発者にとって最も不安になるエラーコードでしょう。 「サーバ内部で何かがおかしい」ということだけはわかりますが、何がおかしいのかはこのステータスコードだけでは判断できません。

各層での発生パターンを整理します。

主な原因

確認場所

アプリケーションコード

未処理の例外(AttributeError, KeyError 等)

Django 黄色エラーページ、スタックトレース

フレームワーク

ミドルウェア設定の不正、ASGI 仕様違反

フレームワークのログ

Python Web サーバ

ワーカーのクラッシュ、import エラー

Gunicorn/Uvicorn のログ

リバースプロキシ

Lua スクリプト等のエラー(稀)

Nginx エラーログ

Tip

500 エラーに直面したとき、まず確認すべきは「どの層のログにエラーが記録されているか」です。

  • アプリケーションのログにスタックトレースがある → アプリケーションコードの問題

  • Gunicorn のログにワーカーのクラッシュが記録されている → サーバの問題

  • Nginx のエラーログに記録されている → リバースプロキシの問題

ログの出力場所が、問題の層を教えてくれます。

1.6.3. タイムアウトはどこで起きるか

リクエストを送ったのにいつまでも応答が返らず、最終的に「504 Gateway Timeout」や「502 Bad Gateway」が表示される。 タイムアウトの問題は、層の区別がつかないと特に混乱しやすいトラブルです。 なぜなら、タイムアウトの設定は複数の層にそれぞれ存在し、最も短い設定が先に発動するからです。

各層のタイムアウト設定を確認しておきましょう。

タイムアウト設定

デフォルト値

ブラウザ(クライアント)

ブラウザ依存 / curl --max-time

数分程度

Nginx(リバースプロキシ)

proxy_read_timeout

60 秒

Gunicorn(Web サーバ)

--timeout

30 秒

アプリコード

DB タイムアウト、requests.get(..., timeout=)

設定次第

典型的な混乱シナリオ

あるバッチ的な処理を行うエンドポイントがあり、処理に 45 秒かかるとします。 Gunicorn のタイムアウトは 30 秒、Nginx の proxy_read_timeout は 60 秒です。

この場合、Gunicorn が 30 秒でワーカーを強制終了し、Nginx はバックエンドが切断されたことを検知して 502 を返します。 開発者が Nginx のエラーログを見ると「upstream prematurely closed connection」と書いてあります。

これを見て「Nginx の問題だ」と思って Nginx の設定をいじっても解決しません。 根本原因は Gunicorn のタイムアウト設定にあるからです。

このように、タイムアウトの問題は各層の設定値の関係を把握していないと、原因の特定が非常に困難になります。

Vol.3「開発環境と本番環境は何が違うのか」では、本番環境のタイムアウト設計について具体的に解説します。

1.6.4. ログはどこに出るか

ここまで繰り返し「ログを確認する」と書いてきましたが、そもそもどのログを見ればよいのかがわからなければ意味がありません。 各層にはそれぞれ独自のログがあり、出力先も形式も異なります。

ログの出力先

記録される内容

Nginx(リバースプロキシ)

/var/log/nginx/access.log / error.log

全リクエストのステータス、応答時間、バックエンドへの接続失敗

Gunicorn(Web サーバ)

標準出力・標準エラー出力

ワーカーの起動・終了・強制終了、import エラー

Uvicorn(Web サーバ)

標準出力

アクセスログ、起動・終了

Django(フレームワーク)

settings.pyLOGGING で設定

未処理例外のスタックトレース、django.request ロガー

FastAPI/Uvicorn

標準出力 + Python logging

アクセスログ + アプリ内ロガーの出力

Tip

トラブルが発生したとき、外側の層から順に確認するのが効果的なアプローチです。

  1. まず Nginx のアクセスログ でステータスコードと応答時間を確認する

  2. 502 や 504 なら Gunicorn / Uvicorn のログ でワーカーの異常を確認する

  3. 最後に アプリケーションのログ でスタックトレースを探す

この順番で調査するだけで、見当違いの方向に時間を費やすリスクを大幅に減らせます。


重要

**同じ症状でも、発生した層によって原因と対処はまったく異なります。**そして、層を見分けるためには、各層の責務と境界を知っていなければなりません。本書のこの後の章では、各層の内部を実際にコードで実装しながら理解していきます。そこで得た知識は、ここで示したようなトラブルシューティングの場面で力を発揮します。

1.7. 本書で扱う技術と扱わない技術

本書は「フレームワークの向こう側」と銘打っていますが、Web に関わるすべてを網羅するわけではありません。 深く潜るためには、潜る場所を選ぶ必要があります。 あれもこれもと手を広げると、どの技術も中途半端な理解に終わってしまいます。

本項では、本書が何を深く扱い、何に軽く触れ、何を意図的に扱わないのかを明示します。 読み始める前にスコープを把握しておくことで、「この話はこの本には載っていないのだな」と迷わずに済みますし、本書の外で学ぶべきことが何かも見えてきます。

1.7.1. 扱う: Django, FastAPI, Werkzeug, Bottle, WSGI, ASGI, Gunicorn, uWSGI, Uvicorn

本書が最も紙幅を割いて深く掘り下げるのは、Python の Web アプリケーションがリクエストを受け取ってからレスポンスを返すまでの内部構造です。 この領域に関わる技術を、下の層から順に解説します。

まず土台となるのが WSGIASGI です。この2つのインタフェース仕様は、本書の背骨と言える存在です。 4 章WSGI が生まれた背景)で WSGI の仕様を読み解き、自分の手で WSGI アプリケーションを実装します。 Vol.2「なぜ ASGI が必要になったのか」で ASGI の仕様に進み、WSGI との違いを構造的に理解します。 仕様そのものを扱うので、特定のフレームワークに依存しない普遍的な知識が身につきます。

WSGI サーバとして GunicornuWSGI を、ASGI サーバとして Uvicorn を取り上げます。 それぞれのアーキテクチャの違い、プロセスモデル、設定の勘所をVol.3「「Webサーバ」という言葉の混乱を解く」で比較します。 「なぜ Gunicorn が必要なのか」「Uvicorn 単体で本番運用してよいのか」「Gunicorn + Uvicorn の構成は何を解決するのか」といった、実務で必ず直面する疑問に答えます。

フレームワーク層では、DjangoFastAPI を二本柱として扱います。 WerkzeugBottle は、WSGI フレームワークの内部構造を理解するための教材として5 章WSGI の上に何が必要になるのか)で取り上げます。

注釈

Bottle はフレームワーク全体が1ファイルに収まっているという特徴があります。 ルーティングからレスポンス生成までの流れを一望できる貴重な存在で、「フレームワークとは結局何をしているのか」を理解するために最適な題材です。

1.7.2. 軽く触れる: Flask, Starlette, Nginx

本書で深く掘り下げはしないものの、文脈の中で繰り返し登場する技術があります。

Flask は、Werkzeug の上に構築された WSGI フレームワークです。 本書では Flask そのものの使い方や機能を解説することはしませんが、5 章WSGI の上に何が必要になるのか)で Werkzeug を掘り下げる際に「Werkzeug の上に Flask がどう乗っているか」という位置づけの説明をします。 Flask を使ったことがある方は、Werkzeug を理解することで Flask の挙動がより明確に見えるようになるはずです。

Starlette は、FastAPI の基盤となっている ASGI フレームワークです。 FastAPI の内部を追いかけると必然的に Starlette のコードに行き着くため、Vol.2「FastAPI を ASGI 視点で見る」で要所に触れます。 ただし、Starlette 自体の全機能を網羅的に解説することはしません。FastAPI の内部理解に必要な範囲に限定します。

Nginx は、リバースプロキシの代表としてVol.3「開発環境と本番環境は何が違うのか」を中心に登場します。 本書の主眼はあくまで「Python のアプリケーションサーバから内側」にあり、Nginx はその外側の境界を示すために登場するという位置づけです。

1.7.3. 深追いしない: フロントエンド, Kubernetes, 高度なネットワーク

本書が意図的に扱わない領域も明確にしておきます。

領域

扱わない理由

フロントエンド技術(HTML, CSS, React, Vue.js)

本書のスコープはサーバサイドのみ

コンテナオーケストレーション(Docker, Kubernetes)

アプリの「外側のインフラ」に属する技術

高度なネットワーク(TCP 輻輳制御、HTTP/2・HTTP/3 フレーム構造)

ネットワークエンジニアリングの教科書を目指していない

データベースの内部構造(クエリ最適化、インデックス構造)

それだけで一冊の本になる世界

注釈

これらの領域を扱わないのは重要ではないからではなく、本書の焦点を絞るためです。 本書で得たサーバサイドの内部理解は、これらの領域を学ぶときにも確かな土台になるはずです。


このように線引きをすることで、本書は「HTTP リクエストが Python のアプリケーションに届き、処理され、レスポンスとして返っていく」という一本の道筋に集中できます。 この道筋を深く理解することが、本書のゴールです。 扱わない領域について学びたくなったときは、その領域に特化した書籍やドキュメントを参照してください。 本書で得たサーバサイドの内部理解は、それらの学習においても確かな土台になるはずです。

1.8. 学び方の方針

本書はここから先、TCP ソケット、HTTP、WSGI、ASGI、フレームワーク内部、本番デプロイと、多くの技術領域を横断していきます。 その旅に出る前に、本書が採用している学び方の方針を共有しておきます。 この方針を理解しておくことで、各章の意図がつかみやすくなり、学習の効果も高まるはずです。

1.8.1. 抽象化を一段ずつ剥がして学ぶ

ソフトウェアの世界は、抽象化の層が幾重にも積み重なってできています。 ブラウザのアドレスバーに URL を入力するだけでページが表示されるのは、DNS、TCP、HTTP、TLS、WSGI/ASGI、フレームワーク、ORM といった無数の抽象化がそれぞれの複雑さを隠してくれているからです。

抽象化はすばらしい仕組みです。 すべての層を常に意識していたら、誰も Web アプリケーションなど作れません。 しかし、抽象化に完全に身を委ねてしまうと、その層が破綻したときに手も足も出なくなります。

本書のアプローチは、一度に全部を剥がすのではなく、一段ずつ剥がしていくというものです。

各章で積み上げていく順番は次のとおりです。

  1. 2 章HTTP は何をやりとりしているのか): Python の socket モジュールだけで TCP 通信を体験する

  2. 3 章まずは 1 リクエストだけ処理するサーバを作る): その上に自分の手で HTTP サーバを組み立てる

  3. 4 章WSGI が生まれた背景): 自作サーバの限界を感じたところで WSGI という仕様に出会い、なぜこの仕様が生まれたのかを実感する

  4. 5 章WSGI の上に何が必要になるのか): WSGI の上に構築された Werkzeug や Flask の内部を覗く

Tip

このように下の層から一段ずつ積み上げていくことで、各層がなぜ存在するのか、どんな問題を解決しているのかが自然と腑に落ちます。 いきなり Django の内部コードを読んでも「何が何だかわからない」となりがちですが、ソケットから積み上げた方は「ああ、ここで WSGI の environ を組み立てているんだな」と読めるようになります。

急がば回れ——この言葉がこれほど当てはまる学習領域もなかなかありません。

1.8.2. 最小実装を書く意味

本書のサンプルコードは、意図的に最小限に絞っています。 エラーハンドリングを省略していることもありますし、本番では使えないような素朴な実装もあります。 これは手抜きではなく、学習上の明確な理由があります。

最小実装は、本質を浮き彫りにします。

たとえば、4 章WSGI が生まれた背景)で書く WSGI アプリケーションは次のようなコードです。

def app(environ, start_response):
    start_response("200 OK", [("Content-Type", "text/plain")])
    return [b"Hello, World!"]

たった3行です。しかし、この3行には WSGI の本質がすべて詰まっています。

  • callable であること

  • environstart_response を受け取ること

  • ステータスコードとヘッダーを start_response で返すこと

  • ボディをバイト列のイテラブルとして返すこと

この3行を理解していれば、Django の WSGIHandler が何をしているのかを読み解く土台ができあがります。

注釈

本書が採用しているのは、次の学習サイクルです。

  1. 最小限のコードで概念の核心をつかむ

  2. そのコードを動かして動作を確認する

  3. 「この実装では何が足りないか」を考える

  4. 本番の実装がなぜ複雑になっているのかを理解する

最小実装を書くときに大切なのは、「これは学習用のコードである」と意識することです。 最小実装をそのまま本番に持ち込んではいけませんが、最小実装を通じて得た理解は本番のコードを読むときに確実に役立ちます。

各章のサンプルコードは、すべてそのまま実行できる完全なコードとして掲載しています。読むだけでなく、実際にファイルに保存して動かしてみてください。 動かしてみて初めて気づくことが必ずあります。 「あれ、Content-Type を指定しないとどうなるんだろう」「POST でボディを送ったらどう見えるんだろう」—— こうした疑問が湧いたら、コードを書き換えて試してみてください。その試行錯誤こそが、最も深い理解を生み出します。

1.8.3. ソースコードを読む姿勢

本書の後半では、Django や FastAPI の内部コードを追いかける場面が出てきます。 オープンソースのフレームワークのソースコードを読むことは、多くの開発者にとってハードルが高いと感じられる作業かもしれません。 何千ものファイル、何万行ものコード、複雑な継承関係——圧倒されてしまうのは当然です。

しかし、ソースコードを読むのに、すべてを理解する必要はありません。 本書が勧めるのは、**「問いを持って読む」**というアプローチです。

たとえば、「Django はリクエストを受け取ってからビュー関数を呼び出すまでに、どんな処理をしているのだろう?」という問いを持ってコードを追いかけるとします。 この場合、Django のテンプレートエンジンの実装や ORM のクエリビルダーの中身は読む必要がありません。 WSGIHandler.__call__ から始めて、get_response メソッドを追い、ミドルウェアチェーンを辿り、URL リゾルバに到達する——この一本の道筋だけを追えばよいのです。

問いが具体的であればあるほど、読むべきコードの範囲は狭まります。

  • 「WSGI の environ はどこで組み立てられるのか」

  • 「URL パターンのマッチングはどのクラスが担当しているのか」

  • 「ミドルウェアはどの順番で実行されるのか」

これらの問いはそれぞれ、ソースコードの中のごく限られた箇所を読むだけで答えが得られます。

ソースコードを読むときに役立つ実践的なテクニックをいくつか紹介します。

  • GitHub 上でファイルを開いて関数名やクラス名で検索する

  • IDE の「定義へジャンプ」機能を使って呼び出し元から呼び出し先へ飛ぶ

  • print()breakpoint() を仕込む — 実際にどのコードが実行されているかを確認する

Tip

特に breakpoint() は強力です。 処理の途中で止めて変数の中身を覗くことができます。 「このコードが実行されているとき、environ の中身はどうなっているんだろう?」という疑問を、数秒で解決できます。

本書のVol.2「Django を WSGI 視点で見る」やVol.2「FastAPI を ASGI 視点で見る」では、こうした読み方の実例を示しながら進めます。 一度この「問いを持って読む」スタイルを身につけると、本書で取り上げないフレームワークやライブラリに対しても同じ方法で内部を探索できるようになります。 ドキュメントに書いていないことでも、ソースコードは必ず答えを持っています。


重要

本書の学び方には、3つの方針があります。

  1. 抽象化を一段ずつ剥がして各層の存在意義を体感すること

  2. 最小限のコードで概念の核心をつかんで手を動かして確かめること

  3. 問いを持ってソースコードを読み自分で答えを見つける力を養うこと

この方針を頭の片隅に置いて、次の2 章HTTP は何をやりとりしているのか)へ進んでください。 最初の目的地は、すべての通信の土台である TCP ソケットの世界です。