Pure Python を使用した Web アプリの構築 (フレームワーク不要!)
Grace Collins
Solutions Engineer · Leapcell

WSGI を使用した最小限の Python Web アプリケーションの構築
Python で Web アプリケーションを作成したことがある人、特にオンラインデプロイメントを実行したことがある人は、WSGI プロトコルを聞いたことがあるはずです。WSGI プロトコルは、Python の Web サーバーと Web アプリケーション間のデータ交換インターフェースを定義します。この説明はやや抽象的なものになる可能性があるため、以下の実践的な例を通じて詳しく説明します。
本番環境での Web アプリケーションのデプロイ
Django や Flask などの Web アプリケーションフレームワークを使用して Web アプリケーションを開発したとします。これらのフレームワークの公式ドキュメントでは通常、Django の python manage.py runserver
や Flask の flask --app hello run
などのフレームワークの組み込みサーバーは、開発段階でのデバッグにのみ適しており、本番環境でのトラフィックを処理できないことが指摘されています。本番環境にデプロイする場合、Web アプリケーションは Web サーバーの背後で実行する必要があります。一般的な Web サーバーには、Gunicorn や uWSGI などがあります。Web サーバーは、Web アプリケーションの同時実行性能を向上させるために、プロセスモデルやスレッドモデルなどの同時実行オプションを提供します。
上記のシンプルなシナリオでは、Gunicorn + Django、Gunicorn + Flask、uWSGI + Django、uWSGI + Flask の 4 つの技術的な選択肢の組み合わせがあります。各組み合わせで、Web アプリケーションフレームワークが異なる Web サービス適合コードを提供する必要がある場合、複雑さは $N^2$ に達し、明らかに費用対効果が高くありません。WSGI の存在は、Web サーバーと Web アプリケーション間のインターフェースを定義することであり、フレームワーク開発者はこのインターフェースのコーディングのみを行う必要があります。これにより、Web アプリケーション開発者はより自由に選択できるようになります。たとえば、同じ Django コードを Gunicorn と uWSGI の両方で実行できます。
Web アプリケーションフレームワークを使用しない場合
Django と Flask が WSGI にどのように適応するかを議論する前に、まず問題を単純化しましょう。Web フレームワークの役割は、ルーティングや HTTP リクエストの解析など、Web アプリケーションをより簡単かつ迅速に開発できるように、いくつかの便利な機能を提供することです。ただし、非常にシンプルなアプリケーションの場合、フレームワークを使用しないことを選択することもできます。
PEP によって定義された WSGI インターフェースは非常にシンプルであり、Web フレームワークを使用する必要はありません (使用すべきではありません)。
HELLO_WORLD = b"Hello world!\n" def simple_app(environ, start_response): """Simplest possible application object""" status = '200 OK' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [HELLO_WORLD]
このシンプルな Web アプリケーションは、environ
環境変数ディクショナリと start_response
関数を介して Web サーバーと対話します。Web サーバーは、正しいパラメーターが渡されるようにします。
上記のコードが app.py
として保存され、Gunicorn がインストールされているとします。次のコマンドを使用してアプリケーションを起動できます。
gunicorn app:simple_app
デフォルトでは、Gunicorn はポート 8000 にバインドされます。curl
を使用してリクエストを送信してテストできます。
$ curl http://localhost:8000 Hello world!
ご覧のとおり、すべてが期待どおりに動作します。同時に、この Web アプリケーションのコードロジックは非常にシンプルです。リクエストパス(/
、/api
など)とリクエストメソッド(GET、POST、PUT など)は考慮せず、常にステータスコード 200 と Hello World!
をレスポンスボディとして返します。
$ curl http://localhost:8080/not-found Hello world! $ curl -X POST http://localhost:8080 Hello world!
Hello World を超えて
前に述べたように、通常の Web アプリケーションには通常、複数のエンドポイントがあり、異なるリクエストに応じて異なるレスポンスを返すことが期待されます。
Web サーバーは、リクエストのすべての情報を environ
ディクショナリに格納し、他の環境変数も格納します。すべてのキーの中で、次の 3 つに特に注意する必要があります。
REQUEST_METHOD
: リクエストメソッド(GET、POST など)。PATH_INFO
: リクエストパス。wsgi.input
: ファイルオブジェクト。リクエストボディにデータが含まれている場合、このオブジェクトを介して読み取ることができます。別のキーCONTENT_LENGTH
はリクエストボディの長さを示し、通常は一緒に使用されます。
/
パスに新しい POST インターフェースを実装して、JSON 型のパラメーターを受け取るとします。ユーザーが {"name": "xxx"}
を渡すと、Web アプリケーションは Hello, xxx!
を返し、GET インターフェースは変更されず、引き続き Hello, World!
を返します。コードは次のとおりです。
import json def simple_app(environ, start_response): request_method = environ["REQUEST_METHOD"] path_info = environ["PATH_INFO"] response_headers = [('Content-type', 'text/plain')] if path_info == '/': status = '200 OK' if request_method == 'GET': body = b'Hello world!\n' elif request_method == 'POST': request_body_size = int(environ["CONTENT_LENGTH"]) request_body = environ["wsgi.input"].read(request_body_size) payload = json.loads(request_body) name = payload.get("name", "") body = f"Hello {name}!\n".encode("utf-8") else: status = '405 Method Not Allowed' body = b'Method Not Allowed!\n' else: status = '404 NOT FOUND' body = b'Not Found!\n' start_response(status, response_headers) return [body]
リクエストパスとリクエストメソッドの処理に加えて、いくつかの簡単なクライアントエラー検出も追加しました。たとえば、/
以外のパスにアクセスすると 404 が返され、GET または POST 以外のメソッドで /
にアクセスすると 405 が返されます。簡単なテストをいくつか示します。
$ curl http://localhost:8080/ Hello World! $ curl -X POST http://localhost:8080/ -d '{"name": "leapcell"}' Hello leapcell! $ curl -X PUT http://localhost:8080/ Method Not Allowed! $ curl http://localhost:8080/not-found Not Found!
Flask のように
Web アプリケーションのロジックが複雑になるにつれて、simple_app
関数はますます長くなります。この種の「スパゲッティコード」は、明らかに優れたプログラミング慣行に準拠していません。Flask の API を参照して、簡単なカプセル化を行うことができます。
たとえば、関数を呼び出し可能なクラスに変換して、Web アプリケーション開発者が WSGI アプリケーションを取得できるようにします。routes
を使用して、パスからハンドラー関数へのすべてのマッピングを格納します。environ
を request
オブジェクトにカプセル化します。など。
class MyWebFramework: def __init__(self): self.routes = {} def route(self, path): def wrapper(handler): self.routes[path] = handler return handler return wrapper def __call__(self, environ, start_response): request = self.assemble_request(environ) path_info = environ["PATH_INFO"] if path_info in self.routes: handler = self.routes[path_info] return handler(request) else: status = '404 NOT FOUND' response_headers = [('Content-type', 'text/plain')] start_response(status, response_headers) return [b'Not Found!\n'] app = MyWebFramework() @app.route("/my_endpoint") def my_endpoint_handler(request): # business logic here to handle request and assemble response response_headers = [('Content-type', 'text/plain')] status = '200 OK' body = b'Endpoint response!\n' return [body]
このようにして、MyWebFramework
の部分は徐々に Web アプリケーションフレームワークに抽象化でき、Web アプリケーションの実際のビジネスロジックは各ハンドラー関数を記述するだけで済みます。flask.app.Flask
のソースコードを参照すると、同様の実装方法も使用されています。Flask アプリケーションは Flask
コアクラスから派生しており、このクラス自体が WSGI アプリケーションです。
Django の設計はわずかに異なります。非同期リクエストをサポートするために、ASGI (Asynchronous Server Gateway Interface) プロトコルを提案および実装しました。Django アプリケーションは、内部関数を介して ASGI アプリケーションまたは WSGI アプリケーションに変換できます。WSGI のみに焦点を当てると、その原理は前に紹介したものと似ています。
おすすめ書籍
Leapcell: 最高のサーバーレス Web ホスティング
最後に、Python サービスをデプロイするための最適なプラットフォームをおすすめします:Leapcell
🚀 お気に入りの言語で構築
JavaScript、Python、Go、または Rust で簡単に開発できます。
🌍 無制限のプロジェクトを無料でデプロイ
使用した分だけ支払い、リクエストも料金もかかりません。
⚡ 従量課金制、隠れたコストなし
アイドル料金はなく、シームレスなスケーラビリティだけです。
🔹 Twitter でフォローしてください: @LeapcellHQ