アウトバウンド リクエストの移行

デフォルトでは、Python 2.7 ランタイムは URL 取得サービスを使用してアウトバウンド HTTP(S) リクエストを処理します。Python ライブラリの urlliburllib2、または httplib を使用してリクエストを発行する場合も同様です。URL 取得は、明示的に有効にしない限り、requests ライブラリからのリクエストは処理しません。

Python 3 ランタイムでは、アウトバウンド リクエストを処理するための中間サービスは必要ありません。URL Fetch API の使用から移行後も類似の機能が必要な場合は、これらのリクエストを移行し、requests ライブラリなどの標準 Python ライブラリを使用する必要があります。

URL 取得と標準の Python ライブラリの主な違い

  • URL 取得によって処理されるリクエストのサイズの上限割り当ては、URL 取得によって処理されないリクエストのサイズの上限割り当てとは異なります。

  • URL 取得では、アプリが別の App Engine アプリにリクエストを送信すると、X-Appengine-Inbound-Appid リクエスト ヘッダーを追加してアプリの ID のアサーションを行います。リクエストを受信したアプリは、この ID を使用して、リクエストを処理するかどうかを決定できます。

    このヘッダーは、URL 取得を使用するアプリから送信されたリクエストでのみ使用できます。ユーザーまたは第三者がリクエストに追加したヘッダーは、App Engine によって削除されます。

    URL 取得を使用せずに ID のアサーションと検証を行う方法については、アプリ ID の OIDC ID トークンへの移行をご覧ください。

    App Engine アプリ間でリクエストが送信される際に、リクエスト ヘッダーを使用して呼び出し元アプリの ID を確認する方法については、App Engine から App Engine へのリクエスト サンプルをご覧ください。

  • URL 取得を使用して、すべてのリクエストに対してデフォルトのタイムアウトを設定できます。requestsurllib などのほとんどの Python 3 ライブラリでは、None にデフォルトのタイムアウトが設定されているため、タイムアウトを指定するにはコードが行う各リクエストを更新する必要があります。

移行プロセスの概要

  1. アプリで URL Fetch API を使用してリクエストを行う場合は、代わりに標準の Python ライブラリを使用するようにコードを更新します。リクエストごとにタイムアウトを指定することをおすすめします。

  2. ローカルの開発サーバーで送信リクエストをテストします。

  3. App Engine で実行するときに、URL 取得をバイパスするようにアプリを構成します。

  4. アプリをデプロイします。

URL Fetch API を Python ライブラリに置き換える

  1. 送信リクエストの発行に標準の Python ライブラリをまだ使用していない場合は、ライブラリを選択して、アプリの依存関係に追加します。

    たとえば、リクエスト ライブラリを使用するには、app.yaml ファイルと同じフォルダに requirements.txt ファイルを作成し、次の行を追加します。

    requests==2.24.0
    

    Python 2 との互換性を維持するため、requests ライブラリをバージョン 2.24.0 に固定することをおすすめします。アプリのデプロイ時に、App Engine は requirements.txt ファイル内に定義されているすべての依存関係をダウンロードします。

    ローカル開発では、venv などの仮想環境に依存関係をインストールすることをおすすめします。

  2. google.appengine.api.urlfetch モジュールが使用されているコードを検索し、Python ライブラリを使用するようにコードを更新します。

単純な HTTPS リクエストの作成

次の例は、requests ライブラリを使用して標準の HTTPS リクエストを作成する方法を示しています。

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging

from flask import Flask

import requests


app = Flask(__name__)


@app.route("/")
def index():
    url = "http://www.google.com/humans.txt"
    response = requests.get(url)
    response.raise_for_status()
    return response.text


@app.errorhandler(500)
def server_error(e):
    logging.exception("An error occurred during a request.")
    return (
        """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(
            e
        ),
        500,
    )




if __name__ == "__main__":
    # This is used when running locally.
    app.run(host="127.0.0.1", port=8080, debug=True)

非同期 HTTPS リクエストの作成

次の例は、requests ライブラリを使用して非同期 HTTPS リクエストを作成する方法を示しています。

# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import logging

from flask import Flask, make_response

from requests_futures.sessions import FuturesSession
from time import sleep



TIMEOUT = 10  # Wait this many seconds for background calls to finish
app = Flask(__name__)


@app.route("/")  # Fetch and return remote page asynchronously
def get_async():
    session = FuturesSession()
    url = "http://www.google.com/humans.txt"

    rpc = session.get(url)

    # ... do other things ...

    resp = make_response(rpc.result().text)
    resp.headers["Content-type"] = "text/plain"
    return resp


@app.route("/callback")  # Fetch and return remote pages using callback
def get_callback():
    global response_text
    global counter

    response_text = ""
    counter = 0

    def cb(resp, *args, **kwargs):
        global response_text
        global counter

        if 300 <= resp.status_code < 400:
            return  # ignore intermediate redirection responses

        counter += 1
        response_text += "Response number {} is {} bytes from {}\n".format(
            counter, len(resp.text), resp.url
        )

    session = FuturesSession()
    urls = [
        "https://google.com/",
        "https://www.google.com/humans.txt",
        "https://www.github.com",
        "https://www.travis-ci.org",
    ]

    futures = [session.get(url, hooks={"response": cb}) for url in urls]

    # No wait functionality in requests_futures, so check every second to
    # see if all callbacks are done, up to TIMEOUT seconds
    for elapsed_time in range(TIMEOUT + 1):
        all_done = True
        for future in futures:
            if not future.done():
                all_done = False
                break
        if all_done:
            break
        sleep(1)

    resp = make_response(response_text)
    resp.headers["Content-type"] = "text/plain"
    return resp


@app.errorhandler(500)
def server_error(e):
    logging.exception("An error occurred during a request.")
    return (
        """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(
            e
        ),
        500,
    )


ローカルでのテスト

送信リクエストを更新した場合は、ローカルの開発サーバーでアプリを実行し、リクエストが成功したことを確認します。

URL 取得のバイパス

アプリを App Engine にデプロイしたときに URL 取得がリクエストを処理しないようにするには:

  1. app.yaml ファイルで、GAE_USE_SOCKETS_HTTPLIB 環境変数を任意の値に設定します。任意の値(空の文字列を含む)を指定できます。次に例を示します。

    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : ''
    
  2. URL 取得を有効にして requests ライブラリから送信されたリクエストを処理する場合は、アプリから Requests AppEngineAdapter を削除できます。

    たとえば、appengine_config.py ファイルから requests_toolbelt.adapters.appengine を削除し、Python ファイルから requests_toolbelt.adapters.appengine.monkeypatch() を削除します。

上記の手順で URL 取得をバイパスした場合でも、アプリは URL Fetch API を直接使用できます。

アプリのデプロイ

アプリをデプロイする準備ができたら、以下を行います。

  1. App Engine でアプリをテストします。

    Google Cloud Console の App Engine の割り当てページを表示して、アプリで URL Fetch API 呼び出しが行われていないことを確認します。

    URL Fetch API の呼び出しを表示します。

  2. アプリがエラーなしで実行されている場合、トラフィック分割を使用して、更新したアプリのトラフィックを徐々に増やします。更新したアプリへのトラフィックを増やす前に、問題が発生していないか細かくモニタリングして確認してください。