Migra solicitudes salientes

Según la configuración predeterminada, el entorno de ejecución de Python 2.7 usa el servicio de recuperación de URL a fin de controlar las solicitudes HTTP(S) salientes, incluso si usas las bibliotecas de Python urllib, urllib2 o httplib para emitir esas solicitudes. La recuperación de URL no controla las solicitudes de la biblioteca requests a menos que la habilites de forma explícita.

El entorno de ejecución de Python 3 no necesita un servicio intermediario para controlar las solicitudes salientes. Si deseas dejar de usar las API de recuperación de URL, pero aún necesitas una funcionalidad similar, debes migrar esas solicitudes para usar una biblioteca estándar de Python, como la biblioteca requests.

Diferencias fundamentales entre la recuperación de URL y las bibliotecas estándar de Python

  • El límite de tamaño y las cuotas de las solicitudes que controla la recuperación de URL son diferentes al límite de tamaño y las cuotas de las solicitudes que no se controlan mediante la recuperación de URL.

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

    Este encabezado solo está disponible en solicitudes que se envían desde tu aplicación si usa la recuperación de URL. App Engine quita el encabezado si tú o un tercero lo agregan a una solicitud.

    Para obtener información sobre cómo confirmar y verificar la identidad sin usar la recuperación de URL, consulta Migra la identidad de la app a tokens de ID de OIDC.

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

  • Podrías usar la recuperación de URL para establecer un tiempo de espera predeterminado en todas las solicitudes. La mayoría de las bibliotecas de Python 3, como requests y urllib, establecen el tiempo de espera predeterminado en None, por lo que debes actualizar cada solicitud que realice el código para especificar un tiempo de espera.

Descripción general del proceso de migración

  1. Si la app usa las API de recuperación de URL para realizar solicitudes, actualiza tu código a fin de usar una biblioteca de Python estándar en su lugar. Te recomendamos que especifiques un tiempo de espera para cada solicitud.

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

  3. Configura la aplicación para omitir la recuperación de URL cuando se ejecute en App Engine.

  4. Implementa la app.

Reemplaza las API de recuperación de URL por una biblioteca de Python

  1. Si todavía no usas una biblioteca estándar de Python para emitir solicitudes salientes, elige una biblioteca y agrégala a las dependencias de tu app.

    Por ejemplo, para usar la biblioteca de solicitudes, crea un archivo requirements.txt en la misma carpeta que tu archivo app.yaml y agrega la siguiente línea:

    requests==2.24.0
    

    Para la compatibilidad con Python 2, te recomendamos fijar la biblioteca requests en la versión 2.24.0. Cuando implementes la app, App Engine descargará todas las dependencias que se definen en el archivo requirements.txt.

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

  2. Busca cualquier código para usar el módulo google.appengine.api.urlfetch y actualízalo a fin de usar la biblioteca de Python.

Realiza solicitudes HTTPS simples

En el siguiente ejemplo, se muestra cómo realizar una solicitud HTTPS estándar mediante 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)

Realiza solicitudes HTTPS asíncronas

En el siguiente ejemplo, se muestra cómo realizar una solicitud HTTPS asíncrona mediante 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, 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,
    )


Pruebas locales

Si actualizaste alguna de tus solicitudes salientes, ejecuta la aplicación en el servidor de desarrollo local y confirma que las solicitudes se realicen de forma correcta.

Omite la recuperación de URL

Para evitar que la recuperación de URL controle las solicitudes cuando implementas la app en App Engine, haz lo siguiente:

  1. En tu archivo app.yaml, configura la variable de entorno GAE_USE_SOCKETS_HTTPLIB en cualquier valor. El valor puede ser cualquier valor, incluida una string vacía. Por ejemplo:

    env_variables:
      GAE_USE_SOCKETS_HTTPLIB : ''
    
  2. Si habilitaste la recuperación de URL para controlar las solicitudes enviadas desde la biblioteca requests, puedes quitar las solicitudes AppEngineAdapter de tu app.

    Por ejemplo, quita 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, incluso si omites la recuperación de URL como se describe en los pasos anteriores, tu aplicación puede seguir usando la API de recuperación de URL directamente.

Implementa tu app

Cuando estés listo para implementar tu app, debes hacer lo siguiente:

  1. Prueba la app en App Engine.

    Consulta la página Cuotas de App Engine en la consola de Google Cloud para confirmar que la app no realiza llamadas a la API de recuperación de URL.

    Ver llamadas a la API de recuperación de URL

  2. Si la app se ejecuta sin errores, usa la división de tráfico a fin de aumentar de forma gradual el tráfico de tu app actualizada. Supervisa la app para detectar cualquier problema antes de enrutar más tráfico a la app actualizada.