遷移傳出要求

根據預設,Python 2.7 執行階段會使用網址擷取服務處理傳出 HTTP(S) 要求,即使您使用 urlliburllib2httplib Python 程式庫發出這些要求也一樣。除非您明確啟用,否則網址擷取服務不會處理來自 requests 程式庫的要求。

Python 3 執行階段不需要中介服務來處理輸出要求。如果您想停用 URL Fetch API,但仍需要類似功能,請將這些要求遷移至標準 Python 程式庫,例如 requests 程式庫

網址擷取和標準 Python 程式庫的主要差異

  • 網址擷取服務處理的要求,其大小限制配額由網址擷取服務處理的要求不同,後者的大小限制配額如下。

  • 使用網址擷取服務時,應用程式傳送要求至其他 App Engine 應用程式時,網址擷取服務會新增 X-Appengine-Inbound-Appid 要求標頭,以確認應用程式的身分。接收要求的應用程式可以使用身分識別資訊,判斷是否應處理要求。

    只有在應用程式使用網址擷取服務時,從應用程式傳送的要求才會包含這個標頭。如果您或第三方將標頭新增至要求,App Engine 會移除該標頭。

    如要瞭解如何在不使用網址擷取服務的情況下,聲明及驗證身分,請參閱「將 App Identity 遷移至 OIDC ID 權杖」。

    如要瞭解如何使用要求標頭,在 App Engine 應用程式之間傳送要求時驗證呼叫應用程式的身分,請參閱 App Engine 對 App Engine 要求範例

  • 您可以使用 URL Fetch 為所有要求設定預設逾時時間。大多數 Python 3 程式庫 (例如 requestsurllib) 會將預設逾時時間設為 None,因此您應更新程式碼發出的每個要求,指定逾時時間。

轉換程序總覽

  1. 如果應用程式使用 URL Fetch API 發出要求,請更新程式碼,改用標準 Python 程式庫。建議您為每項要求指定逾時時間。

  2. 在本機開發伺服器中測試外送要求。

  3. 將應用程式設定為在 App Engine 中執行時略過網址擷取

  4. 部署應用程式。

以 Python 程式庫取代 URL Fetch API

  1. 如果您尚未透過標準 Python 程式庫發出外送要求,請選擇程式庫並新增至應用程式的依附元件。

    舉例來說,如要使用 Requests 程式庫,請在與 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 time import sleep

from flask import Flask
from flask import make_response
from requests_futures.sessions import FuturesSession


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,
    )

在本機測試

如果您更新了任何外送要求,請在本機開發伺服器中執行應用程式,並確認要求成功。

略過網址擷取

將應用程式部署至 App Engine 時,如要停止網址擷取服務處理要求,請按照下列步驟操作:

  1. app.yaml 檔案中,將 GAE_USE_SOCKETS_HTTPLIB 環境變數設為任何值。此值可為包括空白字串在內的任何值。 例如:

    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : ''
    
  2. 如果您已啟用網址擷取功能,處理從 requests 程式庫傳送的要求,可以從應用程式中移除要求 AppEngineAdapter

    舉例來說,從 appengine_config.py 檔案中移除 requests_toolbelt.adapters.appengine,並從 Python 檔案中移除 requests_toolbelt.adapters.appengine.monkeypatch()

請注意,即使您如先前步驟所述略過 URL Fetch,應用程式仍可直接使用 URL Fetch API。

部署您的應用程式

準備好部署應用程式後,請按照下列步驟操作:

  1. 在 App Engine 上測試應用程式

    在 Google Cloud 控制台中查看 App Engine 配額頁面,確認應用程式未發出 URL 擷取 API 呼叫。

    查看 URL Fetch API 呼叫數

  2. 如果應用程式順利執行,請使用流量分配,逐步增加更新版應用程式的流量。在將更多流量導向更新版應用程式前,請密切監控應用程式,確保沒有任何問題。