Python 2 不再受社区支持。我们建议您将 Python 2 应用迁移到 Python 3

迁移出站请求

默认情况下,Python 2.7 运行时使用 URL Fetch 服务处理出站 HTTP(S) 请求,即使您使用 urlliburllib2httplib Python 库发出这些请求也是如此。除非明确启用,否则 URL Fetch 不会处理来自 requests 库的请求。

Python 3 运行时无需中间服务来处理出站请求,因此 URL Fetch 在 Python 3 运行时中不可用。如果您的应用使用标准 Python 库发出请求并且 Python 3 运行时中缺少 URL Fetch,将不会影响应用发出请求。不过,我们建议您在不使用 URL Fetch 功能的环境中测试应用,以确保这一点。

如果应用直接使用 URL Fetch API(例如,发出异步请求),您需要迁移这些请求以使用 requests 等标准 Python 库。

URL Fetch 与标准 Python 库之间的主要区别

  • 由 URL Fetch 处理的请求的大小限制配额由 URL Fetch 处理的请求的大小限制配额不同。

  • 在 Python 2 中,当您的应用向另一个 App Engine 应用发送请求时,URL Fetch 会添加 X-Appengine-Inbound-Appid 请求标头以声明应用的身份。接收请求的应用可以使用该身份来确定是否应处理该请求。

    此标头仅适用于从 Python 2 应用发送的请求。如果您或其他任何人将其添加到请求中,App Engine 会移除此标头。

    如需了解如何在不使用 URL Fetch 的情况下声明和验证身份,请参阅将 App Identity 迁移到 OIDC ID 令牌

  • 您可以使用 URL Fetch 设置所有请求的默认超时。大多数 Python 3 库(例如 requestsurllib)将默认超时设置为 None,因此您应该更新您的代码发出的每个请求以指定超时。

迁移过程概览

  1. 如果您的应用使用 URL Fetch API 发出请求,请更新您的代码以使用标准 Python 库。我们建议您为每个请求指定超时。

  2. 在本地开发服务器中测试出站请求。

  3. 将 Python 2 应用配置为在 App Engine 中运行时绕过 URL Fetch

  4. 部署应用。

使用 Python 库替换 URL Fetch API

  1. 如果您还没有使用标准 Python 库来发出传出请求,请选择库并将其添加到应用的依赖项中。

    例如,如需使用 Requests 库,请在 app.yaml 文件所在的文件夹中创建一个 requirements.txt 文件,并添加以下行:

    requests==2.24.0
    

    我们建议您将 requests 库固定到 2.24.0 版,因为它已知可与 Python 2 应用搭配使用。部署应用时,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

在本地测试

如果您更新任何出站请求,请在本地开发服务器中运行 Python 2 应用并确认请求成功。

绕过 URL Fetch

在向 App Engine 部署应用时,如需阻止 URL Fetch 处理请求,请执行以下操作:

  1. app.yaml 文件中,将 GAE_USE_SOCKETS_HTTPLIB 环境变量设置为任意值。该值可以是任何值,包括空字符串。 例如:

    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : ''
    
  2. 如果您启用了 URL Fetch 来处理从 requests 库发送的请求,您可以从应用中移除 Requests AppEngineAdapter

    例如,appengine_config.py 文件移除 requests_toolbelt.adapters.appengine,并从 Python 文件移除 requests_toolbelt.adapters.appengine.monkeypatch()

请注意,即使您按照前面的步骤绕过 URL Fetch,您的 Python 2 应用仍然可以直接使用 URL Fetch API。在 Python 3 环境中运行应用之前,请务必移除所有 google.appengine.api.urlfetch 模块的使用。

部署应用

准备好部署应用后,您应该执行以下操作:

  1. 在 App Engine 上测试应用

    查看 Google Cloud Console 中的“App Engine 配额”页面,以确认应用没有进行 URL Fetch API 调用。

    查看 URL Fetch API 调用

  2. 如果应用运行正常(没有错误),请使用流量拆分功能为更新后的应用缓慢增加流量。请先仔细监控应用是否存在任何问题,然后再将更多流量路由到更新后的应用。