リクエストの処理方法

このドキュメントでは、App Engine アプリケーションでリクエストを受信してレスポンスを送信する方法について説明します。詳細については、リクエストのヘッダーとレスポンスのリファレンスをご覧ください。

アプリケーションでサービスが使用されている場合は、特定のサービスまたはそのサービスの特定のバージョンへのリクエストを指定できます。サービスのアドレス指定の方法について詳しくは、リクエストのルーティング方法をご覧ください。

リクエストの処理

アプリケーションは、ウェブサーバーの起動とリクエストの処理を行う役割を果たします。使用する開発言語に対応している任意のウェブ フレームワークを使用できます。

App Engine はアプリケーションの複数のインスタンスを実行します。各インスタンスには、リクエストを処理する専用のウェブサーバーが割り当てられます。リクエストのルーティング先インスタンスは任意に決まるので、同じユーザーが連続して送信したリクエストが同じインスタンスに届くとは限りません。インスタンスでは、複数のリクエストを同時に処理できます。トラフィックの変化に応じてインスタンスの数が自動的に調整されます。また、app.yaml ファイルの max_concurrent_requests 要素を設定することで、インスタンスで処理できる同時リクエストの数を変更することもできます。

App Engine がアプリケーションのウェブ リクエストを受け取ると、アプリケーションの app.yaml 構成ファイルに記載されている URL に対応するハンドラ スクリプトを呼び出します。Python 2.7 ランタイムは、下位互換性を維持するために、WSGI 標準CGI 標準をサポートします。WSGI の方をおすすめします。これがないと Python 2.7 の一部の機能が動作しません。アプリケーションのスクリプト ハンドラの構成に応じて、リクエストが WSGI と CGI のどちらを使用して処理されるかが決まります。

次の Python スクリプトはリクエストに対して、HTTP ヘッダーとメッセージ Hello, World! で応答します。

import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        self.response.write('Hello, World!')

app = webapp2.WSGIApplication([
    ('/', MainPage),
], debug=True)

複数のリクエストを各ウェブサーバーに並列にディスパッチするには、app.yaml ファイルに threadsafe: true 要素を追加して、アプリケーションをスレッドセーフとしてマークします。スクリプト ハンドラで CGI が使用されている場合は、同時リクエストを使用できません。

割り当てと制限

Google App Engine は、トラフィックが増加するとアプリケーションにリソースを自動的に割り当てます。ただし、次のような制限があります。

  • App Engine は、自動的にスケーリングできる容量を予約していますが、アプリケーションのレイテンシが短く、1 秒未満でリクエストに応答できることが前提となっています。レイテンシが長く(リクエストの多くで 1 件あたり 1 秒を超えるようなレイテンシ)、スループットが大きいアプリケーションでは、シルバー、ゴールド、プラチナのいずれかのサポートが必要です。これらのレベルのサポートを契約済みの場合は、サポート担当者に連絡してスループットの上限を引き上げることができます。

  • また、CPU の制約を大きく受けるアプリケーションでも、同じサーバー上の他のアプリケーションとリソースを効率的に共有するために、追加のレイテンシが生じる場合があります。静的ファイルへのリクエストには、このようなレイテンシの制限がありません。

アプリケーションが受信する各リクエストは、[リクエスト数] の制限対象としてカウントされます。リクエストへのレスポンスとして送信されるデータは、[送信帯域幅(課金対象)] の制限対象としてカウントされます。

HTTP リクエストと HTTPS(セキュア)リクエストのどちらにも、リクエスト数受信帯域幅(課金対象)送信帯域幅(課金対象)の制限が適用されます。GCP Console の [割り当ての詳細] ページには、参考のために [安全なリクエスト数]、[安全な受信帯域幅]、[安全な送信帯域幅] もそれぞれ報告されます。これらの値としてカウントされるのは、HTTPS リクエストのみです。詳細については、[割り当て] ページをご覧ください。

リクエスト ハンドラの使用には、具体的には次の制限が適用されます。

制限
リクエスト サイズ 32 MB
レスポンス サイズ 32 MB
リクエスト期間 60 秒
最大合計ファイル数(アプリファイルと静的ファイル) 合計 10,000 ファイル
1 ディレクトリあたり 1,000 ファイル
アプリケーション ファイルの最大サイズ 32 MB
静的ファイルの最大サイズ 32 MB
すべてのアプリケーション ファイルと静的ファイルの最大合計サイズ 最初の 1 GB は無料
最初の 1 GB を超えると、以降は 1 GB あたり毎月 $ 0.026

レスポンスに関する制限事項

動的レスポンスの上限は 32 MB です。スクリプト ハンドラが生成したレスポンスの大きさがこの制限を超える場合は、サーバーから 500 内部サーバーエラー ステータス コードを示す空のレスポンスが返されます。Blobstore または Google Cloud Storage からのデータを処理するレスポンスには、この上限は適用されません。

リクエスト ヘッダー

着信した HTTP リクエストには、クライアントから送信された HTTP ヘッダーが含まれています。セキュリティ上の理由から、一部のヘッダーは、アプリケーションに到達する前に中間プロキシによってサニタイズ(リスクのある部分などを削除)または修正されます。

詳しくは、リクエスト ヘッダーのリファレンスをご覧ください。

リクエストのレスポンス

App Engine は、Request を使用してハンドラ スクリプトを呼び出し、スクリプトが戻ってくるまで待ちます。標準出力ストリームに書き込まれたすべてのデータが HTTP レスポンスとして送信されます。

ここで生成するレスポンスには制限があり、それに基づいて変更されたレスポンスがクライアントに返されることがあります。

詳細については、リクエストに対するレスポンスのリファレンスをご覧ください。

レスポンスのストリーミング

App Engine は、レスポンスのストリーミングをサポートしていません。つまり、リクエスト 1 件のデータをチャンクに分けて順に送信することはできません。コードからのデータ全体が前述のように収集されて、単一の HTTP レスポンスとして送信されます。

レスポンスの圧縮

クライアントから送信された HTTP ヘッダーに記述された元のリクエストから、そのクライアントが圧縮(gzip 圧縮)コンテンツを受け入れ可能であることがわかると、App Engine はハンドラのレスポンス データを自動的に圧縮し、適切なレスポンス ヘッダーを付加します。圧縮されたレスポンスをクライアントが適切に受信できるかどうかを、Accept-EncodingUser-Agent の両方のリクエスト ヘッダーを使用して判断します。

カスタム クライアントが圧縮されたレスポンスを受信できることを示すには、Accept-EncodingUser-Agent の両方のヘッダーに値 gzip を指定します。レスポンスの Content-Type も、圧縮が適切かどうかを判断するために使用されます。一般に、テキストベースのコンテンツ タイプは圧縮されますが、バイナリ コンテンツ タイプは圧縮されません。

レスポンスが App Engine で自動的に圧縮された場合は、Content-Encoding ヘッダーがレスポンスに追加されます。

リクエスト期限の指定

リクエスト ハンドラがリクエストに対するレスポンスを生成して返すまでの時間には一定の制限があり、通常は約 60 秒です。この期限に達すると、リクエスト ハンドラは中断されます。Python ランタイム環境は、パッケージ google.appengine.runtime から DeadlineExceededError を発生させることにより、リクエスト ハンドラを中断します。この例外がリクエスト ハンドラでキャッチされないと、すべてのキャッチされない例外と同様に、ランタイム環境からクライアントに HTTP 500 サーバーエラーが返されます。

リクエスト ハンドラは、このエラーをキャッチしてレスポンスをカスタマイズできます。例外発生後、ランタイム環境によりリクエスト ハンドラに対して、カスタム レスポンスを作成するためのわずかな時間(1 秒未満)が与えられます。

class TimerHandler(webapp2.RequestHandler):
    def get(self):
        from google.appengine.runtime import DeadlineExceededError

        try:
            time.sleep(70)
            self.response.write('Completed.')
        except DeadlineExceededError:
            self.response.clear()
            self.response.set_status(500)
            self.response.out.write(
                'The request did not complete in time.')

2 度目の期限までにハンドラがレスポンスを返さないか、例外が発生しない場合、ハンドラは停止され、デフォルトのエラー レスポンスが返されます。

リクエスト 1 件の処理に 60 秒かかる場合もありますが、App Engine はリクエストの存続時間が短いアプリケーション向け(通常は数百ミリ秒程度)に最適化されています。効率的なアプリは、大部分のリクエストに短時間で応答します。そうでないアプリは、App Engine のインフラストラクチャに合わせて適切にスケールされません。

DeadlineExceededError の一般的な原因と推奨される回避方法については、DeadlineExceededError に対処するをご覧ください。

ログ

App Engine ウェブサーバーは、ウェブ リクエストへのレスポンスとしてハンドラ スクリプトが標準出力ストリームに書き込むすべての内容をキャプチャします。また、ハンドラ スクリプトが標準エラー ストリームに書き込むすべての内容もキャプチャし、ログデータとして保存します。各リクエストには request_id が割り当てられます。これは、リクエストの開始時刻に基づくグローバル一意識別子です。アプリケーションのログデータは、GCP Console の [ログ] ページで見ることも、gcloud app logs read コマンドを使用してダウンロードすることもできます。

gcloud app logs read --logs=request_log

App Engine Python ランタイム環境には、ログレベル(「debug」、「info」、「warning」、「error」、「critical」)などのロギングの概念を理解するために、Python 標準ライブラリからのロギング モジュールに対する特別なサポートが含まれています。

import logging

import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        logging.debug('This is a debug message')
        logging.info('This is an info message')
        logging.warning('This is a warning message')
        logging.error('This is an error message')
        logging.critical('This is a critical message')

        try:
            raise ValueError('This is a sample value error.')
        except ValueError:
            logging.exception('A example exception log.')

        self.response.out.write('Logging example.')

app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

環境

実行環境は、自動的にいくつかの環境変数を設定します。他の環境変数は app.yaml で設定することができます。自動的に設定される変数の中には、App Engine 専用のものや、WSGI 標準や CGI 標準に含まれるものがあります。Python コードは、os.environ ディクショナリを使用して、これらの変数にアクセスできます。

次の環境変数は App Engine に固有のものです:

  • CURRENT_VERSION_ID: 「X.Y」のような現在実行中のアプリケーションのメジャー バージョンとマイナー バージョン。メジャー バージョン番号(X)はアプリの app.yaml ファイルで指定されます。マイナー バージョン番号(Y)は、アプリの各バージョンが App Engine にアップロードされたときに自動的に設定されます。開発用ウェブサーバーでは、マイナー バージョンは常に「1」です。

  • AUTH_DOMAIN: Users API でユーザーを認証するために使用されるドメイン。appspot.com 上でホストされているアプリは gmail.comAUTH_DOMAIN が割り当てられ、すべての Google アカウントを受け入れます。カスタム ドメイン上でホストされているアプリは、カスタム ドメインと同じ AUTH_DOMAIN が割り当てられます。

  • INSTANCE_ID: リクエストを処理するフロントエンド インスタンスのインスタンス ID が含まれています。この ID は 16 進文字列(00c61b117c7f7fd0ce9e1325a04b8f0df30deaaf など)です。ログインしている管理者は、URL の http://[INSTANCE_ID].myApp.appspot.com/ でこの ID を使用できます。リクエストはこの特定のフロントエンド インスタンスにルーティングされます。リクエストを処理できない場合、そのインスタンスはすぐに 503 を返します。

次の環境変数は WSGI 標準と CGI 標準に含まれており、App Engine では特殊な動作をします。

  • SERVER_SOFTWARE: 開発用ウェブサーバーでは、この値は「Development/X.Y」です。ここで、「X.Y」はランタイムのバージョンです。App Engine 上で動作している場合、この値は「Google App Engine/X.Y.Z」です。

他の環境変数は、WSGI 標準や CGI 標準に従って設定されます。これらの変数の詳細については、必要に応じて、WSGI 標準または CGI 標準をご覧ください。

app.yaml ファイルで環境変数を設定することもできます。

env_variables:
  DJANGO_SETTINGS_MODULE: 'myapp.settings'

次の webapp2 リクエスト ハンドラは、アプリケーションが認識できるすべての環境変数をブラウザに表示します。

class PrintEnvironmentHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        for key, value in os.environ.iteritems():
            self.response.out.write(
                "{} = {}\n".format(key, value))

リクエスト ID

リクエスト時に、そのリクエストに固有のリクエスト ID を保存できます。このリクエスト ID は、後で、fetch() 関数の request_ids パラメータを使用してリクエストのログを検索するために使用できます。

次のサンプルコードは、リクエストのコンテキストでリクエスト ID を取得する方法を示しています。

class RequestIdHandler(webapp2.RequestHandler):
    def get(self):
        self.response.headers['Content-Type'] = 'text/plain'
        request_id = os.environ.get('REQUEST_LOG_ID')
        self.response.write(
            'REQUEST_LOG_ID={}'.format(request_id))

アプリ キャッシング

Python ランタイム環境では、リクエスト間でインポートされたモジュールが単一のウェブサーバーにキャッシュされます。これは、スタンドアロン Python アプリケーションにおいて、モジュールが複数のファイルによってインポートされた場合でも、1 回しか読み込まれないのに似ています。WSGI ハンドラはモジュールのため、リクエスト間でキャッシュされます。CGI ハンドラ スクリプトは、main() ルーチンを提供する場合にのみキャッシュされます。それ以外の場合、CGI ハンドラ スクリプトはリクエストごとに読み込まれます。WSGI と CGI の違いの詳細については、リクエストをご覧ください。

アプリ キャッシングにより、レスポンスの時間が大幅に短縮されます。後述するように、すべての CGI ハンドラ スクリプトで main() ルーチンを使用することをおすすめします。

インポートのキャッシュ

効率を考えて、ウェブサーバーは、インポートしたモジュールをメモリに保持し、以降の同じサーバー上の同じアプリケーションに対するリクエストでそれらの再読み込みや再評価を行いません。ほとんどのモジュールは、インポート時にグローバル データを初期化するなどの副次的な影響を及さないため、それらをキャッシュしてもアプリケーションの動作は変わりません。

アプリケーションでリクエストのたびに評価対象モジュールに依存するモジュールをインポートする場合は、アプリケーションでこのキャッシング動作に対応する必要があります。

CGI ハンドラ スクリプトのキャッシュ

インポートしたモジュールだけでなく、CGI ハンドラ スクリプト自体をキャッシュに保存するように App Engine に指示することができます。ハンドラ スクリプトで main() という名前の関数を定義すると、インポートしたモジュールと同様に、スクリプトとそのグローバル環境がキャッシュされます。特定のウェブサーバーでスクリプトを初めてリクエストした場合は、通常どおりスクリプトの評価が行われます。以降のリクエストでは、App Engine がキャッシュ環境内の main() 関数を呼び出します。

ハンドラ スクリプトをキャッシュに保存するには、App Engine が引数なしで main() を呼び出すことができる必要があります。ハンドラ スクリプトが main() 関数を定義しない場合または main() 関数に引数(デフォルトを持たない)が必要な場合は、App Engine がリクエストごとにスクリプト全体を読み込んで評価します。

解析済みの Python コードをメモリに保持することで時間が節約され、迅速なレスポンスが可能になります。グローバル環境のキャッシュには、他にも次の用途があります:

  • コンパイル済みの正規表現。すべての正規表現が解析され、コンパイル済みの形式で保存されます。コンパイル済みの正規表現をグローバル変数に保存しておくと、アプリ キャッシングを使用して、コンパイル済みのオブジェクトをリクエスト間で再利用できます。

  • GqlQuery オブジェクト。GqlQuery オブジェクトの作成時に、GQL クエリ文字列が解析されます。パラメータ バインディングを伴う GqlQuery オブジェクトと bind() メソッドの再利用の方が、毎回オブジェクトを再構築するより高速です。値のパラメータ バインディングを伴う GqlQuery オブジェクトをグローバル変数に保存しておくと、リクエストのたびに新しいパラメータ値をバインドすることにより再利用することができます。

  • 構成ファイルとデータファイル。アプリケーションがファイルから構成データを読み込んで解析する場合は、解析済みデータをメモリに保持することで、リクエストごとにファイルを再読み込みする必要がなくなります。

ハンドラ スクリプトは、インポート時に main() を呼び出す必要があります。App Engine は、スクリプトのインポート時に main() が呼び出されることを想定しているため、サーバーで初めてリクエスト ハンドラを読み込むときに、App Engine がこの関数を呼び出すことはありません。

main() を使用したアプリ キャッシングにより、CGI ハンドラのレスポンス タイムが大幅に短縮されます。CGI を使用するすべてのアプリケーションでこの方法を採用することをおすすめします。

このページは役立ちましたか?評価をお願いいたします。

フィードバックを送信...

Python の App Engine スタンダード環境