遷移傳出要求

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

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

URL Fetch 和標準 Python 程式庫之間的主要差異

  • 由網址擷取處理的要求,其大小限制配額,與由網址擷取處理的要求,其大小限制配額不同。

  • 使用網址擷取服務時,當應用程式向其他 App Engine 應用程式傳送要求時,網址擷取服務會加入 X-Appengine-Inbound-Appid 要求標頭,以表明應用程式的身分。接收要求的應用程式可以使用身分來判斷是否應處理要求。

    只有在應用程式使用網址擷取功能時,從應用程式傳送的要求中才會出現此標頭。如果您或第三方將標頭新增至要求,App Engine 就會移除該標頭。

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

    如需使用要求標頭驗證呼叫應用程式身分的範例,請參閱 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 檔案和 Python 檔案中移除 requests_toolbelt.adapters.appenginerequests_toolbelt.adapters.appengine.monkeypatch()

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

部署您的應用程式

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

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

    請前往 Google Cloud 主控台的「App Engine 配額」頁面,確認您的應用程式並未發出 Url Fetch API 呼叫。

    查看 URL Fetch API 呼叫

  2. 如果應用程式運作時沒有發生錯誤,請使用流量分配功能,逐步增加更新版應用程式的流量。在將更多流量導向更新版應用程式之前,請密切監控應用程式是否有任何問題。