StarletteによるFastAPIの柱を理解する
Emily Parker
Product Engineer · Leapcell

はじめに
FastAPIは、その驚異的な速度、自動的なデータ検証、シームレスなOpenAPI統合により、Pythonエコシステムにおいて最も愛されているWebフレームワークの1つへと急速に台頭しました。開発者はしばしばそのエレガントなAPIと印象的なパフォーマンスに驚嘆しますが、それがすべて可能になる基盤となるアーキテクチャを真に理解している人はほとんどいません。秘密の材料、あるいはむしろ強力な基盤はStarletteです。Starletteは、高パフォーマンスな非同期サービスを構築するために設計された軽量ASGIフレームワーク/ツールキットであり、FastAPIが構築されている基礎です。Starletteのコアコンポーネント、すなわちRequest、Response、Routing、Middlewareを理解することは、FastAPIの潜在能力を最大限に引き出し、複雑な問題をトラブルシューティングし、あるいはその開発に貢献したいと考える人にとって不可欠です。この詳細な解説では、FastAPIの堅牢な機能を可能にする本質的な仕組みを解き明かし、単にFastAPIを使用することから、その強力な基盤を真に理解することへと移行していきます。
Starletteのコアコンポーネント
実用的な部分に入る前に、Starlette、ひいてはFastAPIの根幹をなす基本的な概念を定義しましょう。
- ASGI(Asynchronous Server Gateway Interface): これは、非同期Python Webサーバーと非同期Python Webアプリケーション間の共通インターフェースの仕様です。StarletteはASGIフレームワークであり、この仕様に準拠しているため、UvicornやHypercornのようなASGIサーバーとシームレスに連携できます。
 - Request: Webアプリケーションのコンテキストにおいて、
Requestオブジェクトはクライアント(例:Webブラウザ)からサーバーに送信されたすべての情報をカプセル化します。これには、HTTPヘッダー、URLパラメータ、クエリ文字列、ボディコンテンツ(JSONやフォームデータなど)、HTTPメソッドが含まれます。 - Response: 逆に、
Responseオブジェクトはサーバーからクライアントに送信されるデータを表します。これには、HTTPステータスコード、ヘッダー、および実際のコンテンツ(例:HTML、JSON、ファイル)が含まれます。 - Routing: これは、受信したHTTPリクエストをアプリケーション内の特定のハンドラ関数またはビューにマッピングするプロセスです。リクエストのURLパスとHTTPメソッドに基づいて、ルーターはどのコードを実行すべきかを決定します。
 - Middleware: Middlewareコンポーネントは、サーバーとアプリケーションの間に位置する関数またはクラスです。これらは、受信リクエストと送信レスポンスをインターセプトでき、認証、ロギング、エラー処理、ヘッダーの追加などの一般的なタスクを、メインのアプリケーションロジックを煩雑にすることなく実行できます。
 
リクエスト処理
StarletteのRequestオブジェクトは、受信クライアントリクエストのすべての側面にアクセスするための包括的なユーティリティです。リクエストボディを読み取るための非同期メソッドを提供し、大規模なデータストリームの処理を効率的に行えます。
# main.py from starlette.applications import Starlette from starlette.routing import Route from starlette.responses import JSONResponse import uvicorn async def homepage(request): # クエリパラメータにアクセス name = request.query_params.get("name", "World") # ヘッダーにアクセス user_agent = request.headers.get("user-agent", "Unknown") return JSONResponse({"message": f"Hello, {name}!", "user_agent": user_agent}) async def submit_data(request): if request.method == "POST": # JSONボディを非同期で読み込む data = await request.json() return JSONResponse({"received_data": data, "message": "Data processed!"}) return JSONResponse({"message": "Please send a POST request with JSON data."}, status_code=400) routes = [ Route("/", homepage), Route("/submit", submit_data, methods=["POST"]), ] app = Starlette(routes=routes) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)
この例では、homepageはクエリパラメータとヘッダーの取得を示しています。submit_data関数は、await request.json()を使用して、JSONリクエストボディを非同期で解析する方法を示しています。FastAPIはStarletteのRequestオブジェクトを活用しますが、ルート関数の引数でPydanticモデルを介して、このような手動解析の多くを便利に抽象化し、データの検証とデシリアライゼーションを自動的に行います。
レスポンス生成
Starletteは、さまざまなコンテンツタイプに対応するResponseクラスのスイートを提供し、正しいContent-Typeヘッダーと効率的なデータシリアライゼーションを保証します。一般的なタイプには、JSONResponse、HTMLResponse、PlainTextResponse、RedirectResponse、StreamingResponse、FileResponseなどがあります。
# main.py (前の例から継続) from starlette.responses import HTMLResponse, PlainTextResponse, RedirectResponse async def html_page(request): content = "<h1>Welcome to Starlette!</h1><p>This is an HTML response.</p>" return HTMLResponse(content) async def plain_text(request): return PlainTextResponse("This is a plain text response.") async def redirect_example(request): return RedirectResponse(url="/", status_code=302) routes = [ Route("/", homepage), Route("/submit", submit_data, methods=["POST"]), Route("/html", html_page), Route("/text", plain_text), Route("/redirect", redirect_example), ] app = Starlette(routes=routes)
ここでは、HTMLResponse、PlainTextResponse、RedirectResponseを示すルートを追加しました。FastAPIルートからPydanticモデルまたは辞書を返すと、FastAPIは内部的にStarletteのJSONResponseを使用してデータをJSONにシリアライズします。
ルーティングメカニズム
Starletteのルーティングは宣言的で強力であり、特定の非同期関数にマッピングされるURLパスを定義できます。URL内の動的なセグメントであるパスパラメータをサポートしています。
# main.py (継続) async def user_profile(request): user_id = request.path_params["user_id"] return JSONResponse({"user_id": user_id, "message": f"Fetching profile for user {user_id}"}) routes = [ Route("/", homepage), Route("/submit", submit_data, methods=["POST"]), Route("/html", html_page), Route("/text", plain_text), Route("/redirect", redirect_example), Route("/users/{user_id}", user_profile), # パスパラメータ ] app = Starlette(routes=routes)
Route("/users/{user_id}", user_profile)という行は、パスパラメータ{user_id}を持つルートを定義しています。この値は、 request.path_params["user_id"]を介してアクセスできます。FastAPIは、関数シグネチャ(例:def read_user(user_id: int):)でパスパラメータを直接宣言できることで、これをさらに簡素化します。
クロス・カッティング・コンサーンのためのミドルウェア
Middlewareは、Starletteのクロス・カッティング・コンサーン(共通関心事)を処理する多くの強力な機能が活きるところです。アプリケーションをラップし、リクエストの前処理とレスポンスの後処理を可能にします。Starletteは、CORSMiddleware、GZipMiddleware、Middleware(カスタムミドルウェア用)など、いくつかの組み込みミドルウェアを提供しています。
# main.py from starlette.middleware import Middleware from starlette.middleware.cors import CORSMiddleware from starlette.middleware.gzip import GZipMiddleware import time # カスタムミドルウェア async def custom_middleware(request, call_next): start_time = time.time() response = await call_next(request) process_time = time.time() - start_time response.headers["X-Process-Time"] = str(process_time) print(f"Request to {request.url.path} processed in {process_time:.4f} seconds") return response middleware = [ Middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]), Middleware(GZipMiddleware, minimum_size=1000), # コンテンツサイズが1KBを超える場合はレスポンスをgzip圧縮 Middleware(custom_middleware), # カスタムロギング/タイミングミドルウェア ] app = Starlette(routes=routes, middleware=middleware)
ここでは、3つのミドルウェアを連鎖させています。クロスオリジンリソース共有を処理するCORSMiddleware、レスポンスを自動的に圧縮するGZipMiddleware、およびリクエストのタイミングを測定するcustom_middlewareです。各ミドルウェアは、requestオブジェクトとcall_next関数を受け取ります。await call_next(request)を呼び出すことで、制御が次のミドルウェアまたはアプリケーション自体に渡されます。call_next関数からのレスポンスは、返される前に変更できます。FastAPIは、同様のapp.add_middleware()インターフェースを提供し、Starletteのミドルウェアシステムを透過的に利用します。
結論
Starletteは、FastAPIにその高性能とモジュール性をもたらす堅牢な非同期基盤を提供します。そのRequestおよびResponseオブジェクト、宣言的なRouting、そして汎用的なMiddlewareシステムを理解することで、開発者はFastAPIがリクエストをどのように処理し、レスポンスを生成し、一般的なWebアプリケーションの懸念事項をどのように処理するかについて、より深い洞察を得ることができます。この基礎知識は、より効率的で、保守しやすく、強力なWebサービスをFastAPIで構築するための鍵となり、単なるファサードを使用するのではなく、基盤となるメカニズムを真に活用できるようになります。最終的に、Starletteは、FastAPIを両方の世界のベスト、つまり、非常に高速なコアの上に構築された開発者フレンドリーなエクスペリエンスにする、高性能な非同期プリミティブを提供します。