Migrar solicitudes salientes

De forma predeterminada, el tiempo de ejecución de Python 2.7 usa el servicio de obtención de URLs para gestionar las solicitudes salientes de HTTP(S), aunque utilices las bibliotecas de Python urllib, urllib2 o httplib para enviar esas solicitudes. URL Fetch no gestiona las solicitudes de la biblioteca requests a menos que la habilites explícitamente.

El entorno de ejecución de Python 3 no necesita un servicio intermediario para gestionar las solicitudes salientes. Si quieres dejar de usar las APIs URL Fetch, pero necesitas una función similar, debes migrar esas solicitudes para que usen una biblioteca estándar de Python, como la biblioteca requests.

Diferencias clave entre URL Fetch y las bibliotecas estándar de Python

  • El límite de tamaño y las cuotas de las solicitudes que gestiona URL Fetch son diferentes del límite de tamaño y las cuotas de las solicitudes que no gestiona URL Fetch.

  • Con URL Fetch, cuando tu aplicación envía una solicitud a otra aplicación de App Engine, URL Fetch añade el encabezado de solicitud X-Appengine-Inbound-Appid para confirmar la identidad de la aplicación. La aplicación que recibe la solicitud puede usar la identidad para determinar si debe procesarla.

    Esta encabezado solo está disponible en las solicitudes que se envían desde tu aplicación si usa URL Fetch. App Engine elimina el encabezado si tú o un tercero lo añade a una solicitud.

    Para obtener información sobre cómo afirmar y verificar la identidad sin usar URL Fetch, consulta el artículo Migrar App Identity a tokens de ID OIDC.

    Para ver un ejemplo de cómo usar el encabezado de solicitud para verificar la identidad de la aplicación que llama cuando se envían solicitudes entre aplicaciones de App Engine, consulta el ejemplo de solicitud de App Engine a App Engine.

  • Puedes usar URL Fetch para definir un tiempo de espera predeterminado para todas las solicitudes. La mayoría de las bibliotecas de Python 3, como requests y urllib, definen el tiempo de espera predeterminado en None, por lo que debes actualizar cada solicitud que haga tu código para especificar un tiempo de espera.

Visión general del proceso de migración

  1. Si tu aplicación usa las APIs URL Fetch para hacer solicitudes, actualiza el código para usar una biblioteca estándar de Python. Te recomendamos que especifiques un tiempo de espera para cada solicitud.

  2. Prueba tus solicitudes salientes en el servidor de desarrollo local.

  3. Configura tu aplicación para omitir URL Fetch cuando se ejecute en App Engine.

  4. Despliega tu aplicación.

Sustituir las APIs de obtención de URLs por una biblioteca de Python

  1. Si aún no usas una biblioteca estándar de Python para enviar solicitudes salientes, elige una biblioteca y añádela a las dependencias de tu aplicación.

    Por ejemplo, para usar la biblioteca Requests, crea un archivo requirements.txt en la misma carpeta que tu archivo app.yaml y añade la siguiente línea:

    requests==2.24.0
    

    Para que sea compatible con Python 2, te recomendamos que fijes la versión 2.24.0 de la biblioteca requests. Cuando despliegues tu aplicación, App Engine descargará todas las dependencias definidas en el archivo requirements.txt.

    Para el desarrollo local, te recomendamos que instales las dependencias en un entorno virtual, como venv.

  2. Busca en tu código cualquier uso del módulo google.appengine.api.urlfetch y actualiza el código para usar tu biblioteca de Python.

Hacer solicitudes HTTPS sencillas

En el siguiente ejemplo se muestra cómo hacer una solicitud HTTPS estándar con la biblioteca requests:

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

Hacer solicitudes HTTPS asíncronas

En el siguiente ejemplo se muestra cómo hacer una solicitud HTTPS asíncrona con la biblioteca requests:

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

Pruebas locales

Si has actualizado alguna de tus solicitudes salientes, ejecuta tu aplicación en el servidor de desarrollo local y confirma que las solicitudes se completan correctamente.

Saltarse la obtención de URLs

Para evitar que la obtención de URLs gestione las solicitudes al desplegar tu aplicación en App Engine, haz lo siguiente:

  1. En el archivo app.yaml, asigna cualquier valor a la variable de entorno GAE_USE_SOCKETS_HTTPLIB. El valor puede ser cualquiera, incluida una cadena vacía. Por ejemplo:

    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : ''
    
  2. Si has habilitado URL Fetch para gestionar las solicitudes enviadas desde la biblioteca requests, puedes quitar la AppEngineAdapter de tu aplicación.

    Por ejemplo, puedes quitar requests_toolbelt.adapters.appengine de tu archivo appengine_config.py y requests_toolbelt.adapters.appengine.monkeypatch() de tus archivos de Python.

Ten en cuenta que, aunque omitas la obtención de URLs como se describe en los pasos anteriores, tu aplicación podrá seguir usando la API de obtención de URLs directamente.

Desplegar una aplicación

Cuando tengas todo listo para desplegar tu aplicación, debes hacer lo siguiente:

  1. Prueba la aplicación en App Engine.

    Consulta la página Cuotas de App Engine en la Google Cloud consola para confirmar que tu aplicación no está haciendo llamadas a la API Url Fetch.

    Ver llamadas a la API de obtención de URLs

  2. Si la aplicación se ejecuta sin errores, usa la división del tráfico para aumentar lentamente el tráfico de la aplicación actualizada. Monitoriza la aplicación de cerca para detectar cualquier problema antes de dirigir más tráfico a la aplicación actualizada.