第1章 本書の対象読者とゴール
1.1 Django や FastAPI でアプリは作れるが、内部の流れは曖昧な人へ
あなたは Django の manage.py runserver を叩けばローカルでアプリが動くことを知っています。
FastAPI なら uvicorn main:app --reload と打てば、ブラウザに JSON が返ってくることも知っています。
チュートリアルどおりにルーティングを書き、テンプレートを配置し、データベースに接続すれば、たしかに「動く」アプリケーションは完成します。
しかし、こんな問いを投げかけられたらどうでしょうか。
「ブラウザがリクエストを送ってから、あなたのビュー関数が呼ばれるまでに、何が起きていますか?」
この問いに対して、自信を持って答えられる人は意外と少ないものです。 ブラウザが HTTP リクエストを組み立て、TCP コネクションを通じてバイト列がサーバに届き、サーバプロセスがそのバイト列をパースします。 そこから WSGI や ASGI といったインタフェースを通じてフレームワークに渡され、ミドルウェアチェーンを順に通過し、URL ルーティングによってビュー関数が特定され、ようやくあなたの書いたコードが実行されます。 この一連の流れは、フレームワークが丁寧に隠してくれているからこそ、普段は意識する必要がありません。
図1-1 HTTP リクエストがビュー関数に届くまでの流れ
本書が対象とするのは、まさにこの「隠された部分」に漠然とした不安を感じている開発者の方です。
「なんとなく動く」から「なぜ動くかわかる」へのステップアップを目指している Python 開発者の方が対象です。ここまでの説明に少しでも心当たりがあれば、本書はあなたのために書かれたものです。
なお、Python の基本的な文法(関数、クラス、デコレータの使い方)は理解していることを前提としています。 一方で、ネットワークやサーバの知識は必要ありません。 TCP ソケットとは何か、HTTP リクエストはどんな構造をしているのかといった話題は、すべて本書の中でゼロから積み上げていきます。
1.2 なぜ内部理解が必要なのか
「フレームワークを使えば動くのだから、内部を知らなくてもいいのでは?」
これは自然な疑問です。 実際、多くのプロジェクトでは内部構造を意識しなくてもアプリケーションを完成させられます。 では、なぜ本書はわざわざ「内部を理解しよう」と訴えるのでしょうか。
理由はシンプルです。うまくいっている間は内部を知らなくても困りません。困るのは、うまくいかなくなったときです。そしてそのタイミングは、たいてい最もまずい瞬間(本番障害の最中や締め切り直前)にやってきます。
警告
LLM の時代になって、この問題はさらに深刻になりました。 ChatGPT や GitHub Copilot にコードを書かせれば、短時間で動くものができあがります。 しかし、生成されたコードが「なぜその書き方になっているのか」を説明できないまま本番に投入すると、障害が起きたときに原因をたどれません。 正常に動いている間は問題になりませんが、いざ動かなくなったとき、どこから手をつければよいのかがわからなくなります。
たとえば、本番環境でアプリケーションが突然 502 Bad Gateway を返すようになったとします。 ログを見ると、Gunicorn のワーカーがタイムアウトしています。 しかし、そもそも Gunicorn がどういう仕組みでリクエストを処理しているのかがわからなければ、打つべき手の判断がつきません。タイムアウトの設定を変えるべきなのか、ワーカー数を増やすべきなのか、それともアプリケーション側のコードに問題があるのか——どこから手をつければよいのかが見えてこないのです。
あるいは、Django アプリに WebSocket を導入しようとして、ASGI 対応が必要だと言われたとします。 ASGI とは何なのか、なぜ WSGI では WebSocket が扱えないのか、その理由がわからなければ、設定をコピペしてもどこかで行き詰まります。
図1-2 内部知識の有無がトラブルシューティングを左右する
内部を理解するということは、フレームワークのソースコードをすべて暗記するということではありません。 「リクエストがどこを通って、どう処理されて、レスポンスになるのか」という流れを、自分の頭の中に地図として持つことです。 この地図があれば、エラーが発生したときに「この段階で問題が起きているはずだ」と仮説を立てられます。仮説が立てば、次の手が見えてきます。
重要
本書が目指すのは、あなたの頭の中にその「地図」を描くことです。 地図を持っている開発者は、エラーが起きたときに「自分がどこにいるのか」を把握できます。 そこから原因の絞り込みが始まります。
1.3 トラブルシューティングに強い開発者とは何か
経験豊富な開発者がトラブルに直面したとき、彼らの行動を観察していると、ある共通のパターンがあることに気づきます。 闇雲にコードを修正したり、Stack Overflow の回答を片っ端から試したりしません。 まず、問題がどのレイヤーで起きているのかを切り分けます。
「これはネットワークの問題か、サーバの問題か、アプリケーションの問題か」。この最初の問いに答えられるだけで、調査範囲は劇的に狭まります。
ネットワークの問題なら
curlでリクエストを送って確認するサーバの問題ならプロセスやログを調べる
アプリケーションの問題ならデバッガやプリントデバッグで追いかける
図1-3 トラブルシューティングの診断レイヤーと対応する確認手法
レイヤーを意識できる開発者は、このように的確に手を打てます。
逆に、レイヤーの区別がつかない開発者は、すべてが一枚岩に見えてしまいます。 「動かない」としか認識できず、どこから手をつければいいのかわかりません。 知識がないから見えないのであって、見る力がないわけではありません。
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 は何をやりとりしているのか)以降で扱います。まずは、どんな登場人物がいて、どの順番でバトンを渡しているのかを俯瞰します。全体像を先に知っておくと、各章で学ぶ個別の技術が地図のどこに位置するのかが見えてきます。
大まかな流れは次のとおりです。
図1-4 ブラウザからレスポンスまでの処理シーケンス
この図に登場する各コンポーネントを、ひとつずつ見ていきましょう。
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 ハンドシェイクが行われ、暗号化された通信路が構築されます。
図1-5 URL アクセス時の通信路確立の流れ
DNS 解決でドメイン名を IP アドレスに変換し、TCP の3ウェイハンドシェイクで接続を確立し、HTTPS なら 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行目以降はヘッダーで、リクエストに関する付加情報をキーと値のペアで伝えます。
ヘッダー名 |
役割 |
|---|---|
|
どのドメインに対するリクエストかを示す |
|
ブラウザの種類や OS 情報を伝える |
|
ブラウザが受け取れるコンテンツの形式を伝える |
|
優先する言語を伝える |
POST リクエストの場合は、ヘッダーの後に空行を挟んでボディ(本文)が続きます。 フォームの入力内容や JSON データがここに格納されます。
図1-6 HTTP リクエストの構造
注釈
HTTP リクエストは、単なるテキストの塊です。 魔法のような特別なバイナリプロトコルではありません。 この事実は第2章(HTTP は何をやりとりしているのか)で実際にソケットを使って HTTP リクエストを送受信するときに、実感として理解できます。
1.4.4 Web サーバ / アプリケーションサーバ
HTTP リクエストがネットワークを経由してサーバ側に届くと、まず受け取るのはWeb サーバです。 本番環境では、Nginx や Caddy といったソフトウェアがこの役割を担います。
Web サーバの主な仕事は次の2つです。
CSS や画像などの静的ファイルを直接返す。Python のアプリケーションサーバに比べて桁違いに高速です
アプリケーションの処理が必要なリクエストをアプリケーションサーバに転送する(リバースプロキシ)
アプリケーションサーバは、Gunicorn や Uvicorn といったソフトウェアです。
注釈
「Web サーバ」という言葉について: 本書では、リクエストを最初に受け取る Nginx や Caddy を「Web サーバ」、その後ろで Python アプリケーションを動かす Gunicorn や Uvicorn を「アプリケーションサーバ」と呼び分けます。ただし Gunicorn や Uvicorn も「Python の Web サーバ」と呼ばれることがあり(本章でも後半でこの呼び方を使います)、「Web サーバ」という語は文脈によって指す対象が変わる点に注意してください。この用語の混乱そのものは Vol.3「「Web サーバ」という言葉の混乱を解く」で整理します。
サーバ名 |
対応インタフェース |
特徴 |
|---|---|---|
Gunicorn |
WSGI |
複数ワーカープロセスで並行処理 |
uWSGI |
WSGI |
高機能で設定オプションが豊富 |
Uvicorn |
ASGI |
非同期 I/O ベース、高効率 |
図1-7 Web サーバとアプリケーションサーバの役割分担
注意
開発中に python manage.py runserver や uvicorn 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 なしでは動きません。
図1-8 WSGI による密結合と疎結合の比較
WSGI がなければサーバとフレームワークは専用コードで密結合になりますが、WSGI を挟むと両者は共通インタフェースを介して疎結合になり、自由に組み合わせられます。
WSGI は、この結合を断ち切るための取り決めです。
コラム: WSGI という「共通言語」
WSGI を一言で例えるなら「コンセント規格」のようなものです。 日本のコンセントの形が統一されているから、どのメーカーのプラグでも差し込めます。 同じように、「サーバはこういう形式でアプリケーションを呼び出す。アプリケーションはこういう形式でレスポンスを返す」という約束事さえ守れば、サーバとアプリケーションは自由に組み合わせられます。
Gunicorn の上で Django を動かすことも、Flask を動かすことも、Bottle を動かすことも、すべて WSGI という共通規格があるからこそ実現できています。
ASGI は、WSGI では扱えなかった非同期処理や WebSocket に対応するために、WSGI とは別に策定された仕様です。 WSGI は「1リクエストに対して1レスポンスを同期的に返す」というモデルしか扱えないため、WebSocket のような双方向通信や、非同期 I/O を活かした並行処理には対応できません。 ASGI は WSGI に着想を得ながらも、これらを可能にするために設計された別系統のインタフェースです。
WSGI の詳細は第4章(WSGI が生まれた背景)で、ASGI の詳細は第6章(なぜ ASGI が必要になったのか)で、それぞれ仕様を読み解きながら実装します。
1.4.6 フレームワーク
WSGI や ASGI を通じてリクエスト情報を受け取ったフレームワークは、まずミドルウェアを順番に通します。
ミドルウェアとは、リクエストがビュー関数に届く前(および、レスポンスがクライアントに返される前)に実行される処理のことです。 認証の確認、CSRF トークンの検証、セッション情報の読み込み、リクエストログの記録など、アプリケーション全体に共通する横断的な処理がここで行われます。
図1-9 ミドルウェアチェーン
リクエストは外側のミドルウェアから内側へ順に通過してビュー関数に届き、レスポンスは逆順に内側から外側へ通過します。
ミドルウェアを通過すると、次は URL ルーティングです。
リクエストされたパス(/users/42/)を見て、どのビュー関数を呼び出すべきかを決定します。
Django であれば
urlpatternsに定義されたパターンと照合しますFastAPI であれば
@app.get("/users/{user_id}")のようなデコレータで登録されたルートと照合します
URL パターンが一致すると、パスに含まれるパラメータ(この例では 42)が抽出され、ビュー関数に引数として渡されます。
一致するパターンがなければ、フレームワークは 404 Not Found レスポンスを生成します。
図1-10 URL ルーティング
リクエストパスを登録済みのパターンと照合し、一致したビュー関数へ振り分けます。一致するパターンがなければ 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)として返しています。
フレームワークの文法は異なりますが、「リクエストを受け取り、処理して、レスポンスを返す」という構造は変わりません。
注釈