リクエストの処理方法

リージョン ID

REGION_ID は、アプリの作成時に選択したリージョンに基づいて Google が割り当てる省略形のコードです。一部のリージョン ID は、一般的に使用されている国や州のコードと類似しているように見える場合がありますが、このコードは国または州に対応するものではありません。2020 年 2 月以降に作成されたアプリの場合、REGION_ID.r は App Engine の URL に含まれています。この日付より前に作成されたアプリの場合、URL のリージョン ID は省略可能です。

詳しくは、リージョン ID をご覧ください。

このドキュメントでは、App Engine アプリケーションがリクエストを受信してレスポンスを送信する方法を説明します。

詳細については、リクエストのヘッダーとレスポンスのリファレンスをご覧ください。

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

リクエストの処理

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

App Engine がアプリケーションのウェブ リクエストを受け取ると、アプリケーションの WEB-INF/ ディレクトリの web.xml ファイルに記述されているように、その URL に対応するサーブレットを呼び出します。また、Java Servlet 2.5 または 3.1 API 仕様をサポートして、リクエスト データをサーブレットに渡し、レスポンス データを受け取ります。

App Engine はアプリケーションの複数のインスタンスを実行します。各インスタンスには、リクエストを処理する独自のウェブサーバーが割り当てられます。リクエストがルーティングされるインスタンスは任意に決まるため、同じユーザーから連続して送信されたリクエストが同じインスタンスに届くとは限りません。インスタンスの数は、トラフィックの変化に応じて自動的に調整されます。

デフォルトでは、各ウェブサーバーは一度に 1 つのリクエストのみを処理します。複数のリクエストを各ウェブサーバーに並列でディスパッチするには、appengine-web.xml ファイルに <threadsafe>true</threadsafe> 要素を追加して、アプリケーションをスレッドセーフとしてマークします。

次のサーブレット クラスの例は、ユーザーのブラウザに簡単なメッセージを表示します。

// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(name = "requests", description = "Requests: Trivial request", urlPatterns = "/requests")
public class RequestsServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    resp.setContentType("text/plain");
    resp.getWriter().println("Hello, world");
  }
}

割り当てと制限

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

  • App Engine は、1 秒未満でリクエストに応答するレイテンシが短いアプリケーション向けに、自動スケーリングのための容量を予約しています。

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

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

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

リクエスト ハンドラの使用には、それぞれ次の上限や時間制限が適用されます。

上限と時間制限
リクエスト サイズ 32 MB
レスポンス サイズ 32 MB
リクエストのタイムアウト アプリが使用するスケーリングのタイプに依存
最大合計ファイル数(アプリファイルと静的ファイル) 合計 10,000 ファイル
1 ディレクトリあたり 1,000 ファイル
アプリケーション ファイルの最大サイズ 32 MB
静的ファイルの最大サイズ 32 MB
すべてのアプリケーション ファイルと静的ファイルの最大合計サイズ 最初の 1 GB は無料
最初の 1 GB を超えると、以降は 1 GB あたり毎月 $ 0.026
保留中のリクエストのタイムアウト 10 秒
1 つのリクエスト ヘッダー フィールドの最大サイズ スタンダード環境の第 2 世代ランタイム用に 8 KB。これらのランタイムへのリクエストでヘッダー フィールドが 8 キロバイトを超えると、HTTP 400 エラーが返されます。

リクエストに関する上限

すべての HTTP/2 リクエストは、アプリケーション サーバーに転送される際に HTTP/1.1 リクエストに変換されます。

レスポンスに関する上限

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

  • 第 2 世代ランタイムでは、レスポンス ヘッダーの上限は 8 KB です。この上限を超えるレスポンス ヘッダーは HTTP 502 エラーを返し、ログに upstream sent too big header while reading response header from upstream が記録されます。

リクエスト ヘッダー

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

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

リクエスト タイムアウトの処理

App Engine はリクエストの存続時間が短いアプリケーション(通常は数百ミリ秒程度)向けに最適化されています。効率的なアプリは、大部分のリクエストに短時間で応答します。そうでないアプリは、App Engine のインフラストラクチャに合わせて適切にスケールされません。このレベルのパフォーマンスを実現するには、システムによって要求される最大リクエスト タイムアウト内に、すべてのアプリがレスポンスを返す必要があります。

アプリがこの期限を超過すると、App Engine はリクエスト ハンドラを中断します。Java Runtime Environment は、このサーブレットを中断するために、com.google.apphosting.api.DeadlineExceededException をスローします。この例外をキャッチするリクエスト ハンドラがない場合は、ランタイム環境から HTTP 500 サーバーエラーがクライアントに返されます。

リクエスト ハンドラがあり、DeadlineExceededException がキャッチされると、ランタイム環境によってリクエスト ハンドラに、カスタム レスポンスを用意するための時間(1 秒未満)が与えられます。例外が発生した後、リクエスト ハンドラがカスタム レスポンスを作成するのに要する時間が 1 秒を超えた場合は、HardDeadlineExceededError が生成されます。

DeadlineExceededExceptionsHardDeadlineExceededErrors のどちらの場合も、リクエストが強制終了され、インスタンスは停止します。

アプリケーションで com.google.apphosting.api.ApiProxy をインポートし、ApiProxy.getCurrentEnvironment().getRemainingMillis() を呼び出すことで、この期限までの残り時間を調べることができます。これが役立つのは、アプリケーションで予定している作業に長時間かかる可能性がある場合です。たとえば、ある作業の処理に 5 秒かかる場合に getRemainingMillis() から返される時間がそれよりも短いときは、その作業を開始しても意味がありません。

レスポンス

App Engine はリクエスト オブジェクトとレスポンス オブジェクトを使ってサーブレットを呼び出した後、サーブレットがレスポンス オブジェクトに値を設定して返すのを待機します。サーブレットが戻ると、レスポンス オブジェクトのデータがユーザーに送信されます。

生成するレスポンスにはサイズの上限があり、レスポンスはクライアントに返される前に変更される可能性があります。

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

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

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

レスポンスの圧縮

App Engine は、gzip をサポートするクライアントに gzip 圧縮されたコンテンツを可能な限り配信するように設計されています。コンテンツを圧縮する必要があるかどうかを判断するために、App Engine はリクエストを受信すると次の処理を行います。

  1. リクエスト内の Accept-Encoding ヘッダーと User-Agent ヘッダーの両方を確認して、クライアントが圧縮されたレスポンスを適切に受信できるかどうかを確認します。このアプローチにより、gzip 圧縮されたコンテンツを一般的なブラウザで利用する際に発生する、周知のバグを回避します。

  2. レスポンス ハンドラに構成した Content-Type ヘッダーを表示して、コンテンツを圧縮することが適切であるかどうかを確認します。一般的に、テキストベースのコンテンツ タイプは圧縮に適していますが、バイナリ コンテンツ タイプは適していません。

次の点にご注意ください。

  • クライアントは Accept-EncodingUser-Agent の両方のリクエスト ヘッダーを gzip に設定することにより、テキストベースのコンテンツ タイプの圧縮を強制できます。

  • リクエストで Accept-Encoding ヘッダーに gzip を指定しない場合、App Engine でレスポンス データが圧縮されません。

  • Google フロントエンドは、App Engine の静的ファイルとディレクトリ ハンドラからのレスポンスをキャッシュに保存します。最初にキャッシュに保存されるレスポンス データの種類、レスポンスに指定した Vary ヘッダー、リクエストに含まれるヘッダーなど、さまざまな要因によって、クライアントが圧縮データをリクエストしても圧縮されていないデータを受信する場合があります。また、その逆の場合もあります。詳細については、レスポンスのキャッシュ保存をご覧ください。

レスポンスのキャッシュ保存

Google フロントエンド、場合によってはユーザーのブラウザおよびその他の中間キャッシング プロキシ サーバーは、レスポンスに指定した標準キャッシング ヘッダーの指示に従って、アプリのレスポンスをキャッシュに保存します。これらのレスポンス ヘッダーは、フレームワークを介して、またはコード内で直接指定するか、App Engine の静的ファイルとディレクトリ ハンドラを使用して指定できます。

Google フロントエンドでは、キャッシュキーはリクエストの完全な URL です。

静的コンテンツのキャッシュ保存

更新された静的コンテンツの公開後すぐにクライアントが受信できるように、css/v1/styles.css などのバージョニングされたディレクトリから静的コンテンツを配信することをおすすめします。Google フロントエンドは、キャッシュが期限切れになるまで、キャッシュの検証(更新されたコンテンツの確認)を行いません。キャッシュが期限切れになった後でも、リクエスト URL のコンテンツが変更されるまでキャッシュは更新されません。

appengine-web.xml で設定できる次のレスポンス ヘッダーは、Google フロントエンドがコンテンツをキャッシュに保存する方法とタイミングに影響します。

  • Google フロントエンドでコンテンツがキャッシュに保存されるようにするには、Cache-Controlpublic に設定する必要があります。Cache-Control private または no-store ディレクティブを指定しなければ、Google フロントエンドによってキャッシュに保存されることもあります。appengine-web.xml でこのヘッダーを設定しない場合、App Engine で静的ファイルまたはディレクトリ ハンドラによって処理されるすべてのレスポンスにこのヘッダーが自動的に追加されます。詳しくは、追加または置換されるヘッダーをご覧ください。

  • Vary: リクエストで送信されるヘッダーに基づいて、URL に対してさまざまなレスポンスがキャッシュから返されるようにするには、AcceptAccept-EncodingOriginX-Origin のうち 1 つ以上を Vary レスポンス ヘッダーに設定します。

    カーディナリティが高い可能性があるため、他の Vary 値についてはデータがキャッシュに保存されません。

    次に例を示します。

    1. 次のレスポンス ヘッダーを指定します。

      Vary: Accept-Encoding

    2. アプリは Accept-Encoding: gzip ヘッダーを含むリクエストを受信します。App Engine は圧縮されたレスポンスを返し、Google フロントエンドはレスポンス データの gzip 圧縮されたバージョンをキャッシュに保存します。この URL に対する Accept-Encoding: gzip ヘッダーを含む後続のすべてのリクエストは、キャッシュが無効になる(キャッシュの有効期限が切れた後にコンテンツが変更されたことによる)までキャッシュから gzip 圧縮されたデータを受信します。

    3. アプリは、Accept-Encoding ヘッダーが含まれていないリクエストを受信します。App Engine は圧縮されていないレスポンスを返し、Google フロントエンドはレスポンス データの圧縮されていないバージョンをキャッシュに保存します。この URL に対する Accept-Encoding ヘッダーが含まれていない後続のすべてのリクエストは、キャッシュが無効になるまでキャッシュから圧縮されたデータを受信します。

    Vary レスポンス ヘッダーを指定しない場合、Google フロントエンドは URL に対して 1 つのキャッシュ エントリを作成し、作成したエントリをリクエストのヘッダーにかかわらず、すべてのリクエストで使用します。次に例を示します。

    1. Vary: Accept-Encoding レスポンス ヘッダーが指定されていません。
    2. リクエストには Accept-Encoding: gzip ヘッダーが含まれ、レスポンス データの gzip 圧縮されたバージョンがキャッシュに保存されます。
    3. 2 番目のリクエストには Accept-Encoding: gzip ヘッダーが含まれていません。ただし、キャッシュにはレスポンス データの gzip 圧縮されたバージョンが含まれているため、クライアントが圧縮されていないデータをリクエストした場合でも、レスポンスは gzip 圧縮されます。

リクエスト内のヘッダーもキャッシュ保存に影響します。

  • リクエストに Authorization ヘッダーが含まれている場合、コンテンツは Google フロントエンドによってキャッシュに保存されません。

キャッシュの有効期限

デフォルトでは、App Engine の静的ファイルとディレクトリ ハンドラによってレスポンスに追加されるキャッシュ ヘッダーは、クライアントとウェブプロキシ(Google フロントエンドなど)が 10 分後にキャッシュを期限切れにするよう指示します。

任意の有効期限が設定された状態でファイルが転送された場合、一般的にユーザーは自身のブラウザ キャッシュを消去しても、ウェブプロキシのキャッシュからファイルを消去することはできません。アプリの新しいバージョンを再度デプロイしても、キャッシュはリセットされません。静的ファイルを変更する場合には、有効期限は短く(1 時間未満)設定してください。多くの場合、デフォルトの 10 分で十分です。

appengine-web.xml ファイルで static-files 要素を指定すると、すべての静的ファイルとディレクトリ ハンドラのデフォルトの有効期限を変更できます。

ロギング

アプリケーションは、java.util.logging.Logger を使用してアプリケーション ログに情報を書き込むことができます。アプリケーションのログデータは、Cloud Logging を使用して、Google Cloud コンソールで表示できます。ログに記録された各リクエストには、リクエスト ID が割り当てられます。これは、リクエストの開始時間に基づく、グローバルで一意の識別子です。Google Cloud コンソールは、Logger クラスのログレベルを認識し、異なるレベルのメッセージをインタラクティブに表示できます。

App Engine は、サーブレットが標準出力ストリーム(System.out)と標準エラー ストリーム(System.err)に書き込むすべてのデータをキャプチャし、アプリケーション ログに記録します。標準出力ストリームに書き込まれた行は「INFO」レベルで記録され、標準エラー ストリームに書き込まれた行は「WARNING」レベルで記録されます。出力ストリームやエラー ストリームにログの書き込みを行うすべてのロギング フレームワーク(log4j など)を使用できますが、Google Cloud コンソールのログレベルの表示をより詳細に制御するには、ロギング フレームワークで java.util.logging アダプタを使用する必要があります。

// With @WebServlet annotation the webapp/WEB-INF/web.xml is no longer required.
@WebServlet(
    name = "RequestLogging",
    description = "Requests: Logging example",
    urlPatterns = "/requests/log"
)
public class LoggingServlet extends HttpServlet {

  private static final Logger log = Logger.getLogger(LoggingServlet.class.getName());

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    log.info("An informational message.");
    log.warning("A warning message.");
    log.severe("An error message.");
    // ...
  }
}

App Engine Java SDK では、テンプレート logging.properties ファイルが appengine-java-sdk/config/user/ ディレクトリに用意されています。これを使用するには、このファイルを WEB-INF/classes ディレクトリ(または WAR 内の任意の場所)にコピーしてから、システム プロパティ java.util.logging.config.file"WEB-INF/logging.properties"(またはアプリケーション ルートを基準とする任意の相対パス)に設定します。システム プロパティを appengine-web.xml ファイル内に設定するには、次のようにします。

<appengine-web-app xmlns="http://appengine.google.com/ns/1.0"> ... <system-properties> <property name="java.util.logging.config.file" value="WEB-INF/logging.properties" /> </system-properties> </appengine-web-app>

サーブレットは、INFO ログレベル(log.info() を使用)を使用してメッセージをログに書き込みます。デフォルトのログレベルは WARNING であり、これを使用すると、INFO メッセージは出力されません。ログレベルを変更するには、logging.properties ファイルを編集します。

環境

すべてのシステム プロパティと環境変数は、そのアプリケーションにのみ適用されます。あるシステム プロパティを設定したときに、その影響を受けるのはそのアプリケーションから見たそのプロパティの値だけであり、JVM から見た値は変化しません。

アプリのシステム プロパティと環境変数は、デプロイ記述子で設定できます。

App Engine は、ランタイム環境を表すために次のようなシステム プロパティを設定します。

  • com.google.appengine.runtime.environment は、App Engine で実行する場合は "Production" であり、開発用サーバーで実行する場合は "Development" です。

    System.getProperty() を使用するほかに、type-safe API を使用してシステム プロパティにアクセスすることもできます。例:

    if (SystemProperty.environment.value() ==
        SystemProperty.Environment.Value.Production) {
        // The app is running on App Engine...
    }
    
  • com.google.appengine.runtime.version はランタイム環境のバージョン ID です(たとえば、"1.3.0")。バージョンを取得するには、String version = SystemProperty.version.get(); を実行します。

  • com.google.appengine.application.id は、アプリケーションの ID です。ID を取得するには、String ID = SystemProperty.applicationId.get(); を実行します。

  • com.google.appengine.application.version は、現在実行しているアプリケーション サービスのメジャー バージョンとマイナー バージョンです(「X.Y」の形式)。メジャー バージョン番号(X)は、サービスの appengine-web.xml ファイルに指定されています。マイナー バージョン番号("Y")は、アプリの各バージョンが App Engine にアップロードされたときに自動的に設定されます。ID を取得するには、String ID = SystemProperty.applicationVersion.get(); を実行します。

    開発用ウェブサーバー上では、返されるメジャー バージョンは常にデフォルト サービスのバージョンであり、マイナー バージョンは常に「1」です。

次のシステム プロパティも、App Engine がアプリケーション サーバーで JVM を初期化するときに設定されます。

  • file.separator
  • path.separator
  • line.separator
  • java.version
  • java.vendor
  • java.vendor.url
  • java.class.version
  • java.specification.version
  • java.specification.vendor
  • java.specification.name
  • java.vm.vendor
  • java.vm.name
  • java.vm.specification.version
  • java.vm.specification.vendor
  • java.vm.specification.name
  • user.dir

インスタンス ID

リクエストを処理するインスタンスの ID を取得するには、次のコードを使用します。

com.google.apphosting.api.ApiProxy.getCurrentEnvironment().getAttributes().get("com.google.appengine.instance.id")

本番環境では、ログインした管理者は ID を https://INSTANCE_ID-dot-VERSION_ID-dot-SERVICE_ID-dot-PROJECT_ID.REGION_ID.r.appspot.com のような URL で使用できます。リクエストは指定したインスタンスにルーティングされます。リクエストを処理できない場合、そのインスタンスはすぐに 503 を返します。

リクエスト ID

リクエストのときに、そのリクエストに固有のリクエスト ID を保存できます。リクエスト ID は、後でリクエストとそのリクエストのログを関連付けるのに使用できます。

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

com.google.apphosting.api.ApiProxy.getCurrentEnvironment().getAttributes().get("com.google.appengine.runtime.request_log_id")

HTTPS 接続の強制

セキュリティ上の理由から、すべてのアプリケーションは、https で接続するようクライアントに促すべきです。特定のページまたはドメイン全体で http よりも https を優先するようにブラウザに指示するには、レスポンスに Strict-Transport-Security ヘッダーを設定します。次に例を示します。

Strict-Transport-Security: max-age=31536000; includeSubDomains
アプリによって配信される静的コンテンツにこのヘッダーを設定するには、アプリの静的ファイルとディレクトリ ハンドラにヘッダーを追加します。

ほとんどのアプリ フレームワークとウェブサーバーで、コードから生成されるレスポンスにこのヘッダーを設定できます。Spring Boot の Strict-Transport-Security ヘッダーの詳細については、HTTP Strict Transport Security(HSTS)をご覧ください。

非同期バックグラウンド作業の処理

バックグラウンド作業とは、HTTP レスポンスを配信した後、アプリがリクエストに対して行う作業です。バックグラウンド作業をアプリ内で実施することは避け、コードを見直して、レスポンスを配信する前にすべての非同期オペレーションが完了するようにしてください。

長時間実行ジョブには、Cloud Tasks の使用をおすすめします。Cloud Tasks では、HTTP リクエストが長時間継続し、非同期処理が終了した後にのみレスポンスを返します。