遷移傳出要求

根據預設,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. 如果應用程式順利執行,請使用流量分配,逐步增加更新版應用程式的流量。在將更多流量導向更新版應用程式前,請密切監控應用程式,確保沒有任何問題。